reactos/win32ss/gdi/ntgdi/line.c
Timo Kreuzer 402bc38ba7 [NTGDI] Rewrite NtGdiPolyDraw
It is now a wrapper around GdiPolyDraw, which implements the main body of the function, while the Nt function only probes and captures the user mode data into safe kernel mode buffers.
Previously some people thought it was a great idea to just wrap the entire implementation in SEH, so when something throws an exception somewhere for whatever reason, we can just catch it here... and LEAK ALL RESOURCES IN THE PROCESS!
TODO: Rewrite all of this. It's broken everywhere.
2024-09-12 15:52:30 +03:00

797 lines
20 KiB
C

/*
* PROJECT: ReactOS Win32k Subsystem
* LICENSE: GPL - See COPYING in the top level directory
* FILE: win32ss/gdi/ntgdi/line.c
* PURPOSE: Line functions
* PROGRAMMERS: ...
*/
#include <win32k.h>
#define NDEBUG
#include <debug.h>
DBG_DEFAULT_CHANNEL(GdiLine);
// Some code from the WINE project source (www.winehq.com)
VOID FASTCALL
AddPenLinesBounds(PDC dc, int count, POINT *points)
{
DWORD join, endcap;
RECTL bounds, rect;
LONG lWidth;
PBRUSH pbrLine;
/* Get BRUSH from current pen. */
pbrLine = dc->dclevel.pbrLine;
ASSERT(pbrLine);
lWidth = 0;
// Setup bounds
bounds.left = bounds.top = INT_MAX;
bounds.right = bounds.bottom = INT_MIN;
if (((pbrLine->ulPenStyle & PS_TYPE_MASK) & PS_GEOMETRIC) || pbrLine->lWidth > 1)
{
/* Windows uses some heuristics to estimate the distance from the point that will be painted */
lWidth = pbrLine->lWidth + 2;
endcap = (PS_ENDCAP_MASK & pbrLine->ulPenStyle);
join = (PS_JOIN_MASK & pbrLine->ulPenStyle);
if (join == PS_JOIN_MITER)
{
lWidth *= 5;
if (endcap == PS_ENDCAP_SQUARE) lWidth = (lWidth * 3 + 1) / 2;
}
else
{
if (endcap == PS_ENDCAP_SQUARE) lWidth -= lWidth / 4;
else lWidth = (lWidth + 1) / 2;
}
}
while (count-- > 0)
{
rect.left = points->x - lWidth;
rect.top = points->y - lWidth;
rect.right = points->x + lWidth + 1;
rect.bottom = points->y + lWidth + 1;
RECTL_bUnionRect(&bounds, &bounds, &rect);
points++;
}
DPRINT("APLB dc %p l %d t %d\n",dc,rect.left,rect.top);
DPRINT(" r %d b %d\n",rect.right,rect.bottom);
{
RECTL rcRgn = dc->erclClip; // Use the clip box for now.
if (RECTL_bIntersectRect( &rcRgn, &rcRgn, &bounds ))
IntUpdateBoundsRect(dc, &rcRgn);
else
IntUpdateBoundsRect(dc, &bounds);
}
}
// Should use Fx in Point
//
BOOL FASTCALL
IntGdiMoveToEx(DC *dc,
int X,
int Y,
LPPOINT Point)
{
PDC_ATTR pdcattr = dc->pdcattr;
if ( Point )
{
if ( pdcattr->ulDirty_ & DIRTY_PTLCURRENT ) // Double hit!
{
Point->x = pdcattr->ptfxCurrent.x; // ret prev before change.
Point->y = pdcattr->ptfxCurrent.y;
IntDPtoLP ( dc, Point, 1); // Reconvert back.
}
else
{
Point->x = pdcattr->ptlCurrent.x;
Point->y = pdcattr->ptlCurrent.y;
}
}
pdcattr->ptlCurrent.x = X;
pdcattr->ptlCurrent.y = Y;
pdcattr->ptfxCurrent = pdcattr->ptlCurrent;
CoordLPtoDP(dc, &pdcattr->ptfxCurrent); // Update fx
pdcattr->ulDirty_ &= ~(DIRTY_PTLCURRENT|DIRTY_PTFXCURRENT|DIRTY_STYLESTATE);
return TRUE;
}
BOOL FASTCALL
GreMoveTo( HDC hdc,
INT x,
INT y,
LPPOINT pptOut)
{
BOOL Ret;
PDC dc;
if (!(dc = DC_LockDc(hdc)))
{
EngSetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
Ret = IntGdiMoveToEx(dc, x, y, pptOut);
DC_UnlockDc(dc);
return Ret;
}
// Should use Fx in pt
//
VOID FASTCALL
IntGetCurrentPositionEx(PDC dc, LPPOINT pt)
{
PDC_ATTR pdcattr = dc->pdcattr;
if ( pt )
{
if (pdcattr->ulDirty_ & DIRTY_PTFXCURRENT)
{
pdcattr->ptfxCurrent = pdcattr->ptlCurrent;
CoordLPtoDP(dc, &pdcattr->ptfxCurrent); // Update fx
pdcattr->ulDirty_ &= ~(DIRTY_PTFXCURRENT|DIRTY_STYLESTATE);
}
pt->x = pdcattr->ptlCurrent.x;
pt->y = pdcattr->ptlCurrent.y;
}
}
BOOL FASTCALL
IntGdiLineTo(DC *dc,
int XEnd,
int YEnd)
{
SURFACE *psurf;
BOOL Ret = TRUE;
PBRUSH pbrLine;
RECTL Bounds;
POINT Points[2];
PDC_ATTR pdcattr;
PPATH pPath;
ASSERT_DC_PREPARED(dc);
pdcattr = dc->pdcattr;
if (PATH_IsPathOpen(dc->dclevel))
{
Ret = PATH_LineTo(dc, XEnd, YEnd);
}
else
{
psurf = dc->dclevel.pSurface;
if (NULL == psurf)
{
EngSetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
Points[0].x = pdcattr->ptlCurrent.x;
Points[0].y = pdcattr->ptlCurrent.y;
Points[1].x = XEnd;
Points[1].y = YEnd;
IntLPtoDP(dc, Points, 2);
/* The DCOrg is in device coordinates */
Points[0].x += dc->ptlDCOrig.x;
Points[0].y += dc->ptlDCOrig.y;
Points[1].x += dc->ptlDCOrig.x;
Points[1].y += dc->ptlDCOrig.y;
Bounds.left = min(Points[0].x, Points[1].x);
Bounds.top = min(Points[0].y, Points[1].y);
Bounds.right = max(Points[0].x, Points[1].x);
Bounds.bottom = max(Points[0].y, Points[1].y);
/* Get BRUSH from current pen. */
pbrLine = dc->dclevel.pbrLine;
ASSERT(pbrLine);
if (dc->fs & (DC_ACCUM_APP|DC_ACCUM_WMGR))
{
DPRINT("Bounds dc %p l %d t %d\n",dc,Bounds.left,Bounds.top);
DPRINT(" r %d b %d\n",Bounds.right,Bounds.bottom);
AddPenLinesBounds(dc, 2, Points);
}
if (!(pbrLine->flAttrs & BR_IS_NULL))
{
if (IntIsEffectiveWidePen(pbrLine))
{
/* Clear the path */
PATH_Delete(dc->dclevel.hPath);
dc->dclevel.hPath = NULL;
/* Begin a path */
pPath = PATH_CreatePath(2);
dc->dclevel.flPath |= DCPATH_ACTIVE;
dc->dclevel.hPath = pPath->BaseObject.hHmgr;
IntGetCurrentPositionEx(dc, &pPath->pos);
IntLPtoDP(dc, &pPath->pos, 1);
PATH_MoveTo(dc, pPath);
PATH_LineTo(dc, XEnd, YEnd);
/* Close the path */
pPath->state = PATH_Closed;
dc->dclevel.flPath &= ~DCPATH_ACTIVE;
/* Actually stroke a path */
Ret = PATH_StrokePath(dc, pPath);
/* Clear the path */
PATH_UnlockPath(pPath);
PATH_Delete(dc->dclevel.hPath);
dc->dclevel.hPath = NULL;
}
else
{
Ret = IntEngLineTo(&psurf->SurfObj,
(CLIPOBJ *)&dc->co,
&dc->eboLine.BrushObject,
Points[0].x, Points[0].y,
Points[1].x, Points[1].y,
&Bounds,
ROP2_TO_MIX(pdcattr->jROP2));
}
}
}
if (Ret)
{
pdcattr->ptlCurrent.x = XEnd;
pdcattr->ptlCurrent.y = YEnd;
pdcattr->ptfxCurrent = pdcattr->ptlCurrent;
CoordLPtoDP(dc, &pdcattr->ptfxCurrent); // Update fx
pdcattr->ulDirty_ &= ~(DIRTY_PTLCURRENT|DIRTY_PTFXCURRENT|DIRTY_STYLESTATE);
}
return Ret;
}
BOOL FASTCALL
IntGdiPolyBezier(DC *dc,
LPPOINT pt,
DWORD Count)
{
BOOL ret = FALSE; // Default to FAILURE
if ( PATH_IsPathOpen(dc->dclevel) )
{
return PATH_PolyBezier ( dc, pt, Count );
}
/* We'll convert it into line segments and draw them using Polyline */
{
POINT *Pts;
INT nOut;
Pts = GDI_Bezier ( pt, Count, &nOut );
if ( Pts )
{
ret = IntGdiPolyline(dc, Pts, nOut);
ExFreePoolWithTag(Pts, TAG_BEZIER);
}
}
return ret;
}
BOOL FASTCALL
IntGdiPolyBezierTo(DC *dc,
LPPOINT pt,
DWORD Count)
{
BOOL ret = FALSE; // Default to failure
PDC_ATTR pdcattr = dc->pdcattr;
if ( PATH_IsPathOpen(dc->dclevel) )
ret = PATH_PolyBezierTo ( dc, pt, Count );
else /* We'll do it using PolyBezier */
{
POINT *npt;
npt = ExAllocatePoolWithTag(PagedPool,
sizeof(POINT) * (Count + 1),
TAG_BEZIER);
if ( npt )
{
npt[0].x = pdcattr->ptlCurrent.x;
npt[0].y = pdcattr->ptlCurrent.y;
memcpy(npt + 1, pt, sizeof(POINT) * Count);
ret = IntGdiPolyBezier(dc, npt, Count+1);
ExFreePoolWithTag(npt, TAG_BEZIER);
}
}
if ( ret )
{
pdcattr->ptlCurrent.x = pt[Count-1].x;
pdcattr->ptlCurrent.y = pt[Count-1].y;
pdcattr->ptfxCurrent = pdcattr->ptlCurrent;
CoordLPtoDP(dc, &pdcattr->ptfxCurrent); // Update fx
pdcattr->ulDirty_ &= ~(DIRTY_PTLCURRENT|DIRTY_PTFXCURRENT|DIRTY_STYLESTATE);
}
return ret;
}
BOOL FASTCALL
IntGdiPolyline(DC *dc,
LPPOINT pt,
int Count)
{
SURFACE *psurf;
BRUSH *pbrLine;
LPPOINT Points;
BOOL Ret = TRUE;
LONG i;
PDC_ATTR pdcattr = dc->pdcattr;
PPATH pPath;
if (!dc->dclevel.pSurface)
{
return TRUE;
}
DC_vPrepareDCsForBlit(dc, NULL, NULL, NULL);
psurf = dc->dclevel.pSurface;
/* Get BRUSHOBJ from current pen. */
pbrLine = dc->dclevel.pbrLine;
ASSERT(pbrLine);
if (!(pbrLine->flAttrs & BR_IS_NULL))
{
Points = EngAllocMem(0, Count * sizeof(POINT), GDITAG_TEMP);
if (Points != NULL)
{
RtlCopyMemory(Points, pt, Count * sizeof(POINT));
IntLPtoDP(dc, Points, Count);
/* Offset the array of points by the DC origin */
for (i = 0; i < Count; i++)
{
Points[i].x += dc->ptlDCOrig.x;
Points[i].y += dc->ptlDCOrig.y;
}
if (dc->fs & (DC_ACCUM_APP|DC_ACCUM_WMGR))
{
AddPenLinesBounds(dc, Count, Points);
}
if (IntIsEffectiveWidePen(pbrLine))
{
/* Clear the path */
PATH_Delete(dc->dclevel.hPath);
dc->dclevel.hPath = NULL;
/* Begin a path */
pPath = PATH_CreatePath(Count);
dc->dclevel.flPath |= DCPATH_ACTIVE;
dc->dclevel.hPath = pPath->BaseObject.hHmgr;
pPath->pos = pt[0];
IntLPtoDP(dc, &pPath->pos, 1);
PATH_MoveTo(dc, pPath);
for (i = 1; i < Count; ++i)
{
PATH_LineTo(dc, pt[i].x, pt[i].y);
}
/* Close the path */
pPath->state = PATH_Closed;
dc->dclevel.flPath &= ~DCPATH_ACTIVE;
/* Actually stroke a path */
Ret = PATH_StrokePath(dc, pPath);
/* Clear the path */
PATH_UnlockPath(pPath);
PATH_Delete(dc->dclevel.hPath);
dc->dclevel.hPath = NULL;
}
else
{
Ret = IntEngPolyline(&psurf->SurfObj,
(CLIPOBJ *)&dc->co,
&dc->eboLine.BrushObject,
Points,
Count,
ROP2_TO_MIX(pdcattr->jROP2));
}
EngFreeMem(Points);
}
else
{
Ret = FALSE;
}
}
DC_vFinishBlit(dc, NULL);
return Ret;
}
BOOL FASTCALL
IntGdiPolylineTo(DC *dc,
LPPOINT pt,
DWORD Count)
{
BOOL ret = FALSE; // Default to failure
PDC_ATTR pdcattr = dc->pdcattr;
if (PATH_IsPathOpen(dc->dclevel))
{
ret = PATH_PolylineTo(dc, pt, Count);
}
else /* Do it using Polyline */
{
POINT *pts = ExAllocatePoolWithTag(PagedPool,
sizeof(POINT) * (Count + 1),
TAG_SHAPE);
if ( pts )
{
pts[0].x = pdcattr->ptlCurrent.x;
pts[0].y = pdcattr->ptlCurrent.y;
memcpy( pts + 1, pt, sizeof(POINT) * Count);
ret = IntGdiPolyline(dc, pts, Count + 1);
ExFreePoolWithTag(pts, TAG_SHAPE);
}
}
if ( ret )
{
pdcattr->ptlCurrent.x = pt[Count-1].x;
pdcattr->ptlCurrent.y = pt[Count-1].y;
pdcattr->ptfxCurrent = pdcattr->ptlCurrent;
CoordLPtoDP(dc, &pdcattr->ptfxCurrent); // Update fx
pdcattr->ulDirty_ &= ~(DIRTY_PTLCURRENT|DIRTY_PTFXCURRENT|DIRTY_STYLESTATE);
}
return ret;
}
BOOL FASTCALL
IntGdiPolyPolyline(DC *dc,
LPPOINT pt,
PULONG PolyPoints,
DWORD Count)
{
ULONG i;
LPPOINT pts;
PULONG pc;
BOOL ret = FALSE; // Default to failure
pts = pt;
pc = PolyPoints;
if (PATH_IsPathOpen(dc->dclevel))
{
return PATH_PolyPolyline( dc, pt, PolyPoints, Count );
}
for (i = 0; i < Count; i++)
{
ret = IntGdiPolyline ( dc, pts, *pc );
if (ret == FALSE)
{
return ret;
}
pts+=*pc++;
}
return ret;
}
/******************************************************************************/
BOOL
APIENTRY
NtGdiLineTo(HDC hDC,
int XEnd,
int YEnd)
{
DC *dc;
BOOL Ret;
RECT rcLockRect ;
dc = DC_LockDc(hDC);
if (!dc)
{
EngSetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
rcLockRect.left = dc->pdcattr->ptlCurrent.x;
rcLockRect.top = dc->pdcattr->ptlCurrent.y;
rcLockRect.right = XEnd;
rcLockRect.bottom = YEnd;
IntLPtoDP(dc, (PPOINT)&rcLockRect, 2);
/* The DCOrg is in device coordinates */
rcLockRect.left += dc->ptlDCOrig.x;
rcLockRect.top += dc->ptlDCOrig.y;
rcLockRect.right += dc->ptlDCOrig.x;
rcLockRect.bottom += dc->ptlDCOrig.y;
DC_vPrepareDCsForBlit(dc, &rcLockRect, NULL, NULL);
Ret = IntGdiLineTo(dc, XEnd, YEnd);
DC_vFinishBlit(dc, NULL);
DC_UnlockDc(dc);
return Ret;
}
// FIXME: This function is completely broken
static
BOOL
GdiPolyDraw(
IN HDC hdc,
IN LPPOINT lppt,
IN LPBYTE lpbTypes,
IN ULONG cCount)
{
PDC dc;
PDC_ATTR pdcattr;
POINT bzr[4];
volatile PPOINT line_pts, line_pts_old, bzr_pts;
INT num_pts, num_bzr_pts, space, space_old, size;
ULONG i;
BOOL result = FALSE;
dc = DC_LockDc(hdc);
if (!dc) return FALSE;
pdcattr = dc->pdcattr;
if (pdcattr->ulDirty_ & (DIRTY_FILL | DC_BRUSH_DIRTY))
DC_vUpdateFillBrush(dc);
if (pdcattr->ulDirty_ & (DIRTY_LINE | DC_PEN_DIRTY))
DC_vUpdateLineBrush(dc);
if (!cCount)
{
DC_UnlockDc(dc);
return TRUE;
}
line_pts = NULL;
line_pts_old = NULL;
bzr_pts = NULL;
{
if (PATH_IsPathOpen(dc->dclevel))
{
result = PATH_PolyDraw(dc, (const POINT *)lppt, (const BYTE *)lpbTypes, cCount);
goto Cleanup;
}
/* Check for valid point types */
for (i = 0; i < cCount; i++)
{
switch (lpbTypes[i])
{
case PT_MOVETO:
case PT_LINETO | PT_CLOSEFIGURE:
case PT_LINETO:
break;
case PT_BEZIERTO:
if((i + 2 < cCount) && (lpbTypes[i + 1] == PT_BEZIERTO) &&
((lpbTypes[i + 2] & ~PT_CLOSEFIGURE) == PT_BEZIERTO))
{
i += 2;
break;
}
default:
goto Cleanup;
}
}
space = cCount + 300;
line_pts = ExAllocatePoolWithTag(PagedPool, space * sizeof(POINT), TAG_SHAPE);
if (line_pts == NULL)
{
result = FALSE;
goto Cleanup;
}
num_pts = 1;
line_pts[0].x = pdcattr->ptlCurrent.x;
line_pts[0].y = pdcattr->ptlCurrent.y;
for ( i = 0; i < cCount; i++ )
{
switch (lpbTypes[i])
{
case PT_MOVETO:
if (num_pts >= 2) IntGdiPolyline( dc, line_pts, num_pts );
num_pts = 0;
line_pts[num_pts++] = lppt[i];
break;
case PT_LINETO:
case (PT_LINETO | PT_CLOSEFIGURE):
line_pts[num_pts++] = lppt[i];
break;
case PT_BEZIERTO:
bzr[0].x = line_pts[num_pts - 1].x;
bzr[0].y = line_pts[num_pts - 1].y;
RtlCopyMemory( &bzr[1], &lppt[i], 3 * sizeof(POINT) );
if ((bzr_pts = GDI_Bezier( bzr, 4, &num_bzr_pts )))
{
size = num_pts + (cCount - i) + num_bzr_pts;
if (space < size)
{
space_old = space;
space = size * 2;
line_pts_old = line_pts;
line_pts = ExAllocatePoolWithTag(PagedPool, space * sizeof(POINT), TAG_SHAPE);
if (!line_pts) goto Cleanup;
RtlCopyMemory(line_pts, line_pts_old, space_old * sizeof(POINT));
ExFreePoolWithTag(line_pts_old, TAG_SHAPE);
line_pts_old = NULL;
}
RtlCopyMemory( &line_pts[num_pts], &bzr_pts[1], (num_bzr_pts - 1) * sizeof(POINT) );
num_pts += num_bzr_pts - 1;
ExFreePoolWithTag(bzr_pts, TAG_BEZIER);
bzr_pts = NULL;
}
i += 2;
break;
}
if (lpbTypes[i] & PT_CLOSEFIGURE) line_pts[num_pts++] = line_pts[0];
}
if (num_pts >= 2) IntGdiPolyline( dc, line_pts, num_pts );
IntGdiMoveToEx( dc, line_pts[num_pts - 1].x, line_pts[num_pts - 1].y, NULL );
result = TRUE;
}
Cleanup:
if (line_pts != NULL)
{
ExFreePoolWithTag(line_pts, TAG_SHAPE);
}
if ((line_pts_old != NULL) && (line_pts_old != line_pts))
{
ExFreePoolWithTag(line_pts_old, TAG_SHAPE);
}
if (bzr_pts != NULL)
{
ExFreePoolWithTag(bzr_pts, TAG_BEZIER);
}
DC_UnlockDc(dc);
return result;
}
__kernel_entry
W32KAPI
BOOL
APIENTRY
NtGdiPolyDraw(
_In_ HDC hdc,
_In_reads_(cpt) LPPOINT ppt,
_In_reads_(cpt) LPBYTE pjAttr,
_In_ ULONG cpt)
{
PBYTE pjBuffer;
PPOINT pptSafe;
PBYTE pjSafe;
SIZE_T cjSizePt;
BOOL bResult;
if (cpt == 0)
{
ERR("cpt is 0\n");
return FALSE;
}
/* Validate that cpt isn't too large */
if (cpt > ((MAXULONG - cpt) / sizeof(*ppt)))
{
ERR("cpt is too large\n", cpt);
return FALSE;
}
/* Calculate size for a buffer */
cjSizePt = cpt * sizeof(*ppt);
ASSERT(cjSizePt + cpt > cjSizePt);
/* Allocate a buffer for all data */
pjBuffer = ExAllocatePoolWithTag(PagedPool, cjSizePt + cpt, TAG_SHAPE);
if (pjBuffer == NULL)
{
ERR("Failed to allocate buffer\n");
return FALSE;
}
pptSafe = (PPOINT)pjBuffer;
pjSafe = pjBuffer + cjSizePt;
_SEH2_TRY
{
ProbeArrayForRead(ppt, sizeof(*ppt), cpt, sizeof(ULONG));
ProbeArrayForRead(pjAttr, sizeof(*pjAttr), cpt, sizeof(BYTE));
/* Copy the arrays */
RtlCopyMemory(pptSafe, ppt, cjSizePt);
RtlCopyMemory(pjSafe, pjAttr, cpt);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
SetLastNtError(_SEH2_GetExceptionCode());
bResult = FALSE;
goto Cleanup;
}
_SEH2_END;
/* Call the internal function */
bResult = GdiPolyDraw(hdc, pptSafe, pjSafe, cpt);
Cleanup:
/* Free the buffer */
ExFreePoolWithTag(pjBuffer, TAG_SHAPE);
return bResult;
}
/*
* @implemented
*/
_Success_(return != FALSE)
BOOL
APIENTRY
NtGdiMoveTo(
IN HDC hdc,
IN INT x,
IN INT y,
OUT OPTIONAL LPPOINT pptOut)
{
PDC pdc;
BOOL Ret;
POINT Point;
pdc = DC_LockDc(hdc);
if (!pdc) return FALSE;
Ret = IntGdiMoveToEx(pdc, x, y, &Point);
if (Ret && pptOut)
{
_SEH2_TRY
{
ProbeForWrite(pptOut, sizeof(POINT), 1);
RtlCopyMemory(pptOut, &Point, sizeof(POINT));
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
SetLastNtError(_SEH2_GetExceptionCode());
Ret = FALSE; // CHECKME: is this correct?
}
_SEH2_END;
}
DC_UnlockDc(pdc);
return Ret;
}
/* EOF */