You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1517 lines
49 KiB
1517 lines
49 KiB
/******************************Module*Header**********************************\
|
|
*
|
|
* *******************
|
|
* * GDI SAMPLE CODE *
|
|
* *******************
|
|
*
|
|
* Module Name: fillpath.c
|
|
*
|
|
* Copyright (c) 1994-1998 3Dlabs Inc. Ltd. All rights reserved.
|
|
* Copyright (c) 1995-1999 Microsoft Corporation. All rights reserved.
|
|
\*****************************************************************************/
|
|
// LATER identify convex polygons and special-case?
|
|
// LATER identify vertical edges and special-case?
|
|
// LATER move pointed-to variables into automatics in search loops
|
|
// LATER punt to the engine with segmented framebuffer callbacks
|
|
// LATER handle complex clipping
|
|
// LATER coalesce rectangles
|
|
|
|
#include "precomp.h"
|
|
#include "log.h"
|
|
#include "gdi.h"
|
|
#include "clip.h"
|
|
#define ALLOC_TAG ALLOC_TAG_IF2P
|
|
//-----------------------------Public*Routine----------------------------------
|
|
//
|
|
// DrvFillPath
|
|
//
|
|
// This function fills the specified path with the specified brush and ROP.
|
|
// This function detects single convex polygons, and will call to separate
|
|
// faster convex polygon code for those cases. This routine also detects
|
|
// polygons that are really rectangles, and handles those separately as well.
|
|
//
|
|
// Parameters
|
|
// pso---------Points to a SURFOBJ structure that defines the surface on which
|
|
// to draw.
|
|
// ppo---------Points to a PATHOBJ structure that defines the path to be filled
|
|
// The PATHOBJ_Xxx service routines are provided to enumerate the
|
|
// lines, Bezier curves, and other data that make up the path.
|
|
// pco---------Points to a CLIPOBJ structure. The CLIPOBJ_Xxx service routines
|
|
// are provided to enumerate the clip region as a set of rectangles.
|
|
// pbo---------Points to a BRUSHOBJ structure that defines the pattern and
|
|
// colors to fill with.
|
|
// pptlBrushOrg-Points to a POINTL structure that defines the brush origin,
|
|
// which is used to align the brush pattern on the device.
|
|
// mix---------Defines the foreground and background raster operations to use
|
|
// for the brush.
|
|
// flOptions---Specifies either FP_WINDINGMODE, indicating that a winding mode
|
|
// fill should be performed, or FP_ALTERNATEMODE, indicating that
|
|
// an alternating mode fill should be performed. All other flags
|
|
// should be ignored.
|
|
//
|
|
// Return Value
|
|
// The return value is TRUE if the driver is able to fill the path. If the
|
|
// path or clipping is too complex to be handled by the driver and should be
|
|
// handled by GDI, the return value is FALSE, and an error code is not logged.
|
|
// If the driver encounters an unexpected error, such as not being able to
|
|
// realize the brush, the return value is DDI_ERROR, and an error code is
|
|
// logged.
|
|
//
|
|
// Comments
|
|
// GDI can call DrvFillPath to fill a path on a device-managed surface. When
|
|
// deciding whether to call this function, GDI compares the fill requirements
|
|
// with the following flags in the flGraphicsCaps member of the DEVINFO
|
|
// structure: GCAPS_BEZIERS, GCAPS_ALTERNATEFILL, and GCAPS_WINDINGFILL.
|
|
//
|
|
// The mix mode defines how the incoming pattern should be mixed with the data
|
|
// already on the device surface. The MIX data type consists of two ROP2 values
|
|
// packed into a single ULONG. The low-order byte defines the foreground raster
|
|
// operation; the next byte defines the background raster operation.
|
|
//
|
|
// Multiple polygons in a path cannot be treated as being disjoint; The fill
|
|
// must consider all the points in the path. That is, if the path contains
|
|
// multiple polygons, you cannot simply draw one polygon after the other
|
|
// (unless they don't overlap).
|
|
//
|
|
// This function is an optional entry point for the driver. but is recommended
|
|
// for good performance. To get GDI to call this function, not only do you
|
|
// have to HOOK_FILLPATH, you have to set GCAPS_ALTERNATEFILL and/or
|
|
// GCAPS_WINDINGFILL.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
BOOL
|
|
DrvFillPath(SURFOBJ* pso,
|
|
PATHOBJ* ppo,
|
|
CLIPOBJ* pco,
|
|
BRUSHOBJ* pbo,
|
|
POINTL* pptlBrush,
|
|
MIX mix,
|
|
FLONG flOptions)
|
|
{
|
|
GFNPB pb;
|
|
BYTE jClipping; // Clipping type
|
|
EDGE* pCurrentEdge;
|
|
EDGE AETHead; // Dummy head/tail node & sentinel for Active Edge
|
|
// Table
|
|
EDGE* pAETHead; // Pointer to AETHead
|
|
EDGE GETHead; // Dummy head/tail node & sentinel for Global Edge
|
|
// Table
|
|
EDGE* pGETHead; // Pointer to GETHead
|
|
EDGE* pFreeEdges = NULL; // Pointer to memory free for use to store edges
|
|
ULONG ulNumRects; // # of rectangles to draw currently in rectangle
|
|
// list
|
|
RECTL* prclRects; // Pointer to start of rectangle draw list
|
|
INT iCurrentY; // Scan line for which we're currently scanning out
|
|
// the fill
|
|
|
|
BOOL bMore;
|
|
PATHDATA pd;
|
|
RECTL ClipRect;
|
|
PDev* ppdev;
|
|
|
|
BOOL bRetVal=FALSE; // FALSE until proven TRUE
|
|
BOOL bMemAlloced=FALSE; // FALSE until proven TRUE
|
|
|
|
FLONG flFirstRecord;
|
|
POINTFIX* pPtFxTmp;
|
|
ULONG ulPtFxTmp;
|
|
POINTFIX aptfxBuf[NUM_BUFFER_POINTS];
|
|
ULONG ulRop4;
|
|
|
|
DBG_GDI((6, "DrvFillPath called"));
|
|
|
|
pb.psurfDst = (Surf*)pso->dhsurf;
|
|
|
|
pb.pco = pco;
|
|
ppdev = pb.psurfDst->ppdev;
|
|
pb.ppdev = ppdev;
|
|
pb.ulRop4 = gaMix[mix & 0xFF] | (gaMix[mix >> 8] << 8);
|
|
ulRop4 = pb.ulRop4;
|
|
|
|
//@@BEGIN_DDKSPLIT
|
|
#if MULTITHREADED
|
|
if(pb.ppdev->ulLockCount)
|
|
{
|
|
DBG_GDI((MT_LOG_LEVEL, "DrvBitBlt: re-entered! %d", pb.ppdev->ulLockCount));
|
|
}
|
|
EngAcquireSemaphore(pb.ppdev->hsemLock);
|
|
pb.ppdev->ulLockCount++;
|
|
#endif
|
|
//@@END_DDKSPLIT
|
|
|
|
vCheckGdiContext(ppdev);
|
|
|
|
//
|
|
// There's nothing to do if there are only one or two points
|
|
//
|
|
if ( ppo->cCurves <= 2 )
|
|
{
|
|
goto ReturnTrue;
|
|
}
|
|
|
|
//
|
|
// Pass the surface off to GDI if it's a device bitmap that we've uploaded
|
|
// to the system memory.
|
|
//
|
|
if ( pb.psurfDst->flags == SF_SM )
|
|
{
|
|
DBG_GDI((1, "dest surface is in system memory. Punt it back"));
|
|
|
|
//@@BEGIN_DDKSPLIT
|
|
#if MULTITHREADED
|
|
pb.ppdev->ulLockCount--;
|
|
EngReleaseSemaphore(pb.ppdev->hsemLock);
|
|
#endif
|
|
//@@END_DDKSPLIT
|
|
|
|
return ( EngFillPath(pso, ppo, pco, pbo, pptlBrush, mix, flOptions));
|
|
}
|
|
|
|
//
|
|
// Set up the clipping
|
|
//
|
|
if ( pco == (CLIPOBJ*)NULL )
|
|
{
|
|
//
|
|
// No CLIPOBJ provided, so we don't have to worry about clipping
|
|
//
|
|
jClipping = DC_TRIVIAL;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Use the CLIPOBJ-provided clipping
|
|
//
|
|
jClipping = pco->iDComplexity;
|
|
}
|
|
|
|
//
|
|
// Now we are sure the surface we are going to draw is in the video memory
|
|
//
|
|
// Set default fill as solid fill
|
|
//
|
|
pb.pgfn = vSolidFillWithRop;
|
|
pb.solidColor = 0; //Assume we don't need a pattern
|
|
pb.prbrush = NULL;
|
|
|
|
//
|
|
// It is too difficult to determine interaction between
|
|
// multiple paths, if there is more than one, skip this
|
|
//
|
|
PATHOBJ_vEnumStart(ppo);
|
|
bMore = PATHOBJ_bEnum(ppo, &pd);
|
|
|
|
//
|
|
// First we need to check if we need a pattern or not
|
|
//
|
|
if ( (((ulRop4 & 0xff00) >> 8) != (ulRop4 & 0x00ff))
|
|
|| ((((ulRop4 >> 4) ^ (ulRop4)) & 0xf0f) != 0) )
|
|
{
|
|
pb.solidColor = pbo->iSolidColor;
|
|
|
|
//
|
|
// Check to see if it is a non-solid brush (-1)
|
|
//
|
|
if ( pbo->iSolidColor == -1 )
|
|
{
|
|
//
|
|
// Get the driver's realized brush
|
|
//
|
|
pb.prbrush = (RBrush*)pbo->pvRbrush;
|
|
|
|
//
|
|
// If it hasn't been realized, do it
|
|
// Note: GDI will call DrvRealizeBrsuh to fullfill this task. So the
|
|
// driver should have this function ready
|
|
//
|
|
if ( pb.prbrush == NULL )
|
|
{
|
|
DBG_GDI((7, "Realizing brush"));
|
|
|
|
pb.prbrush = (RBrush*)BRUSHOBJ_pvGetRbrush(pbo);
|
|
if ( pb.prbrush == NULL )
|
|
{
|
|
//
|
|
// If we can't realize it, nothing we can do
|
|
//
|
|
|
|
//@@BEGIN_DDKSPLIT
|
|
#if MULTITHREADED
|
|
pb.ppdev->ulLockCount--;
|
|
EngReleaseSemaphore(pb.ppdev->hsemLock);
|
|
#endif
|
|
//@@END_DDKSPLIT
|
|
|
|
return(FALSE);
|
|
}
|
|
DBG_GDI((7, "Brsuh realizing done"));
|
|
}// Realize brush
|
|
|
|
pb.pptlBrush = pptlBrush;
|
|
|
|
//
|
|
// Check if brush pattern is 1 BPP or not
|
|
// Note: This is set in DrvRealizeBrush
|
|
//
|
|
if ( pb.prbrush->fl & RBRUSH_2COLOR )
|
|
{
|
|
//
|
|
// 1 BPP pattern. Do a Mono fill
|
|
//
|
|
pb.pgfn = vMonoPatFill;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Pattern is more than 1 BPP. Do color pattern fill
|
|
//
|
|
pb.pgfn = vPatFill;
|
|
DBG_GDI((7, "Skip Fast Fill Color Pattern"));
|
|
|
|
//
|
|
// P2 can not handle fast filled patterns
|
|
//
|
|
goto SkipFastFill;
|
|
}
|
|
}// Handle non-solid brush
|
|
}// Blackness check
|
|
|
|
//
|
|
// For solid brush, we can use FastFill
|
|
//
|
|
if ( bMore )
|
|
{
|
|
//
|
|
// FastFill only knows how to take a single contiguous buffer
|
|
// of points. Unfortunately, GDI sometimes hands us paths
|
|
// that are split over multiple path data records. Convex
|
|
// figures such as Ellipses, Pies and RoundRects are almost
|
|
// always given in multiple records. Since probably 90% of
|
|
// multiple record paths could still be done by FastFill, for
|
|
// those cases we simply copy the points into a contiguous
|
|
// buffer...
|
|
//
|
|
// First make sure that the entire path would fit in the
|
|
// temporary buffer, and make sure the path isn't comprised
|
|
// of more than one subpath:
|
|
//
|
|
if ( (ppo->cCurves >= NUM_BUFFER_POINTS)
|
|
||(pd.flags & PD_ENDSUBPATH) )
|
|
{
|
|
goto SkipFastFill;
|
|
}
|
|
|
|
pPtFxTmp = &aptfxBuf[0];
|
|
|
|
//
|
|
// Copy one vertex over to pPtFxTmp from pd(path data)
|
|
//
|
|
RtlCopyMemory(pPtFxTmp, pd.pptfx, sizeof(POINTFIX) * pd.count);
|
|
|
|
//
|
|
// Move the memory pointer over to next structure
|
|
//
|
|
pPtFxTmp += pd.count;
|
|
ulPtFxTmp = pd.count;
|
|
flFirstRecord = pd.flags; // Remember PD_BEGINSUBPATH flag
|
|
|
|
//
|
|
// Loop to get all the vertex info. After the loop, all the vertex info
|
|
// will be in array aptfxBuf[]
|
|
//
|
|
do
|
|
{
|
|
bMore = PATHOBJ_bEnum(ppo, &pd);
|
|
|
|
RtlCopyMemory(pPtFxTmp, pd.pptfx, sizeof(POINTFIX) * pd.count);
|
|
ulPtFxTmp += pd.count;
|
|
pPtFxTmp += pd.count;
|
|
} while ( !(pd.flags & PD_ENDSUBPATH) );
|
|
|
|
//
|
|
// Fake up the path data record
|
|
//
|
|
pd.pptfx = &aptfxBuf[0];
|
|
pd.count = ulPtFxTmp;
|
|
pd.flags |= flFirstRecord;
|
|
|
|
//
|
|
// If there's more than one subpath, we can't call FastFill
|
|
//
|
|
DBG_GDI((7, "More than one subpath!"));
|
|
if ( bMore )
|
|
{
|
|
goto SkipFastFill;
|
|
}
|
|
}// if ( bMore )
|
|
|
|
//
|
|
// Fast polygon fill
|
|
//
|
|
if ( bFillPolygon(ppdev, (Surf*)pso->dhsurf, pd.count,
|
|
pd.pptfx, pb.solidColor,
|
|
ulRop4,
|
|
pco, pb.prbrush, pptlBrush) )
|
|
{
|
|
DBG_GDI((7, "Fast Fill Succeeded"));
|
|
InputBufferFlush(ppdev);
|
|
|
|
//@@BEGIN_DDKSPLIT
|
|
#if MULTITHREADED
|
|
pb.ppdev->ulLockCount--;
|
|
EngReleaseSemaphore(pb.ppdev->hsemLock);
|
|
#endif
|
|
//@@END_DDKSPLIT
|
|
|
|
return (TRUE);
|
|
}
|
|
|
|
SkipFastFill:
|
|
DBG_GDI((7, "Fast Fill Skipped"));
|
|
if ( jClipping != DC_TRIVIAL )
|
|
{
|
|
if ( jClipping != DC_RECT )
|
|
{
|
|
DBG_GDI((7, "Complex Clipping"));
|
|
|
|
//
|
|
// There is complex clipping; let GDI fill the path
|
|
//
|
|
goto ReturnFalse;
|
|
}
|
|
|
|
//
|
|
// Clip to the clip rectangle
|
|
//
|
|
ClipRect = pco->rclBounds;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// So the y-clipping code doesn't do any clipping
|
|
// We don't blow the values out when we scale up to GIQ
|
|
//
|
|
ClipRect.top = (LONG_MIN + 1) / 16; // +1 to avoid compiler problem
|
|
ClipRect.bottom = LONG_MAX / 16;
|
|
}
|
|
|
|
//
|
|
// Set up working storage in the temporary buffer, storage for list of
|
|
// rectangles to draw
|
|
// Note: ppdev->pvTmpBuffer is allocated in DrvEnableSurface() in enable.c
|
|
// The purpose of using ppdev->pvTmpBuffer is to save us from having to
|
|
// allocate and free the temp space inside high frequency calls. It was
|
|
// allocated for TMP_BUFFER_SIZE bytes and will be freed in
|
|
// DrvDeleteSurface()
|
|
//
|
|
prclRects = (RECTL*)ppdev->pvTmpBuffer;
|
|
|
|
if ( !bMore )
|
|
{
|
|
RECTL* pTmpRect;
|
|
INT cPoints = pd.count;
|
|
|
|
//
|
|
// The count can't be less than three, because we got all the edges
|
|
// in this subpath, and above we checked that there were at least
|
|
// three edges
|
|
//
|
|
// If the count is four, check to see if the polygon is really a
|
|
// rectangle since we can really speed that up. We'll also check for
|
|
// five with the first and last points the same.
|
|
//
|
|
// ??? we have already done the memcpy for the pd data. shall we use it
|
|
//
|
|
if ( ( cPoints == 4 )
|
|
||( ( cPoints == 5 )
|
|
&&(pd.pptfx[0].x == pd.pptfx[4].x)
|
|
&&(pd.pptfx[0].y == pd.pptfx[4].y) ) )
|
|
{
|
|
//
|
|
// Get storage space for this temp rectangle
|
|
//
|
|
pTmpRect = prclRects;
|
|
|
|
//
|
|
// We have to start somewhere to assume that most
|
|
// applications specify the top left point first
|
|
// We want to check that the first two points are
|
|
// either vertically or horizontally aligned. If
|
|
// they are then we check that the last point [3]
|
|
// is either horizontally or vertically aligned,
|
|
// and finally that the 3rd point [2] is aligned
|
|
// with both the first point and the last point
|
|
//
|
|
pTmpRect->top = pd.pptfx[0].y - 1 & FIX_MASK;
|
|
pTmpRect->left = pd.pptfx[0].x - 1 & FIX_MASK;
|
|
pTmpRect->right = pd.pptfx[1].x - 1 & FIX_MASK;
|
|
|
|
//
|
|
// Check if the first two points are vertically alligned
|
|
//
|
|
if ( pTmpRect->left ^ pTmpRect->right )
|
|
{
|
|
//
|
|
// The first two points are not vertically alligned
|
|
// Let's see if these two points are horizontal alligned
|
|
//
|
|
if ( pTmpRect->top ^ (pd.pptfx[1].y - 1 & FIX_MASK) )
|
|
{
|
|
//
|
|
// The first two points are not horizontally alligned
|
|
// So it is not a rectangle
|
|
//
|
|
goto not_rectangle;
|
|
}
|
|
|
|
//
|
|
// Up to now, the first two points are horizontally alligned,
|
|
// but not vertically alligned. We need to check if the first
|
|
// point vertically alligned with the 4th point
|
|
//
|
|
if ( pTmpRect->left ^ (pd.pptfx[3].x - 1 & FIX_MASK) )
|
|
{
|
|
//
|
|
// The first point is not vertically alligned with the 4th
|
|
// point either. So this is not a rectangle
|
|
//
|
|
goto not_rectangle;
|
|
}
|
|
|
|
//
|
|
// Check if the 2nd point and the 3rd point are vertically aligned
|
|
//
|
|
if ( pTmpRect->right ^ (pd.pptfx[2].x - 1 & FIX_MASK) )
|
|
{
|
|
//
|
|
// The 2nd point and the 3rd point are not vertically aligned
|
|
// So this is not a rectangle
|
|
//
|
|
goto not_rectangle;
|
|
}
|
|
|
|
//
|
|
// Check to see if the 3rd and 4th points are horizontally
|
|
// alligned. If not, then it is not a rectangle
|
|
//
|
|
pTmpRect->bottom = pd.pptfx[2].y - 1 & FIX_MASK;
|
|
if ( pTmpRect->bottom ^ (pd.pptfx[3].y - 1 & FIX_MASK) )
|
|
{
|
|
goto not_rectangle;
|
|
}
|
|
}// Check if the first two points are vertically alligned
|
|
else
|
|
{
|
|
//
|
|
// The first two points are vertically alligned. Now we need to
|
|
// check if the 1st point and the 4th point are horizontally
|
|
// aligned. If not, then this is not a rectangle
|
|
//
|
|
if ( pTmpRect->top ^ (pd.pptfx[3].y - 1 & FIX_MASK) )
|
|
{
|
|
goto not_rectangle;
|
|
}
|
|
|
|
//
|
|
// Check if the 2nd point and the 3rd point are horizontally
|
|
// aligned. If not, then this is not a rectangle
|
|
//
|
|
pTmpRect->bottom = pd.pptfx[1].y - 1 & FIX_MASK;
|
|
if ( pTmpRect->bottom ^ (pd.pptfx[2].y - 1 & FIX_MASK) )
|
|
{
|
|
goto not_rectangle;
|
|
}
|
|
|
|
//
|
|
// Check if the 3rd point and the 4th point are vertically
|
|
// aligned. If not, then this is not a rectangle
|
|
//
|
|
pTmpRect->right = pd.pptfx[2].x - 1 & FIX_MASK;
|
|
if ( pTmpRect->right ^ (pd.pptfx[3].x - 1 & FIX_MASK) )
|
|
{
|
|
goto not_rectangle;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We have a rectangle now. Do some adjustment here first
|
|
// If the left is greater than the right then
|
|
// swap them so the blt code won't have problem
|
|
//
|
|
if ( pTmpRect->left > pTmpRect->right )
|
|
{
|
|
FIX temp;
|
|
|
|
temp = pTmpRect->left;
|
|
pTmpRect->left = pTmpRect->right;
|
|
pTmpRect->right = temp;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// If left == right there's nothing to draw
|
|
//
|
|
if ( pTmpRect->left == pTmpRect->right )
|
|
{
|
|
DBG_GDI((7, "Nothing to draw"));
|
|
goto ReturnTrue;
|
|
}
|
|
}// Adjust right and left edge
|
|
|
|
//
|
|
// Shift the values to get pixel coordinates
|
|
//
|
|
pTmpRect->left = (pTmpRect->left >> FIX_SHIFT) + 1;
|
|
pTmpRect->right = (pTmpRect->right >> FIX_SHIFT) + 1;
|
|
|
|
//
|
|
// Adjust the top and bottom coordiantes if necessary
|
|
//
|
|
if ( pTmpRect->top > pTmpRect->bottom )
|
|
{
|
|
FIX temp;
|
|
|
|
temp = pTmpRect->top;
|
|
pTmpRect->top = pTmpRect->bottom;
|
|
pTmpRect->bottom = temp;
|
|
}
|
|
else
|
|
{
|
|
if ( pTmpRect->top == pTmpRect->bottom )
|
|
{
|
|
DBG_GDI((7, "Nothing to draw"));
|
|
goto ReturnTrue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Shift the values to get pixel coordinates
|
|
//
|
|
pTmpRect->top = (pTmpRect->top >> FIX_SHIFT) + 1;
|
|
pTmpRect->bottom = (pTmpRect->bottom >> FIX_SHIFT) + 1;
|
|
|
|
//
|
|
// Finally, check for clipping
|
|
//
|
|
if ( jClipping == DC_RECT )
|
|
{
|
|
//
|
|
// Clip to the clip rectangle
|
|
//
|
|
if ( !bIntersect(pTmpRect, &ClipRect, pTmpRect) )
|
|
{
|
|
//
|
|
// Totally clipped, nothing to do
|
|
//
|
|
DBG_GDI((7, "Nothing to draw"));
|
|
goto ReturnTrue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we get here then the polygon is a rectangle,
|
|
// set count to 1 and goto bottom to draw it
|
|
//
|
|
ulNumRects = 1;
|
|
goto draw_remaining_rectangles;
|
|
}// Check to see if it is a rectangle
|
|
|
|
not_rectangle:
|
|
;
|
|
}// if ( !bMore )
|
|
|
|
//
|
|
// Do we have enough memory for all the edges?
|
|
// LATER does cCurves include closure????
|
|
//
|
|
if ( ppo->cCurves > MAX_EDGES )
|
|
{
|
|
//
|
|
// Try to allocate enough memory
|
|
//
|
|
pFreeEdges = (EDGE*)ENGALLOCMEM(0, (ppo->cCurves * sizeof(EDGE)),
|
|
ALLOC_TAG);
|
|
if ( pFreeEdges == NULL )
|
|
{
|
|
DBG_GDI((1, "Can't allocate memory for %d edges", ppo->cCurves));
|
|
|
|
//
|
|
// Too many edges; let GDI fill the path
|
|
//
|
|
goto ReturnFalse;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Set a flag to indicate that we have allocate the memory so that
|
|
// we can free it later
|
|
//
|
|
bMemAlloced = TRUE;
|
|
}
|
|
}// if ( ppo->cCurves > MAX_EDGES )
|
|
else
|
|
{
|
|
//
|
|
// If the total number of edges doesn't exceed the MAX_EDGES, then just
|
|
// use our handy temporary buffer (it's big enough)
|
|
//
|
|
pFreeEdges = (EDGE*)((BYTE*)ppdev->pvTmpBuffer + RECT_BYTES);
|
|
}
|
|
|
|
//
|
|
// Initialize an empty list of rectangles to fill
|
|
//
|
|
ulNumRects = 0;
|
|
|
|
//
|
|
// Enumerate the path edges and build a Global Edge Table (GET) from them
|
|
// in YX-sorted order.
|
|
//
|
|
pGETHead = &GETHead;
|
|
if ( !bConstructGET(pGETHead, pFreeEdges, ppo, &pd, bMore, &ClipRect) )
|
|
{
|
|
DBG_GDI((7, "Outside Range"));
|
|
goto ReturnFalse; // outside GDI's 2**27 range
|
|
}
|
|
|
|
//
|
|
// Create an empty AET with the head node also a tail sentinel
|
|
//
|
|
pAETHead = &AETHead;
|
|
AETHead.pNext = pAETHead; // Mark that the AET is empty
|
|
AETHead.X = 0x7FFFFFFF; // This is greater than any valid X value, so
|
|
// searches will always terminate
|
|
|
|
//
|
|
// Top scan of polygon is the top of the first edge we come to
|
|
//
|
|
iCurrentY = ((EDGE*)GETHead.pNext)->Y;
|
|
|
|
//
|
|
// Loop through all the scans in the polygon, adding edges from the GET to
|
|
// the Active Edge Table (AET) as we come to their starts, and scanning out
|
|
// the AET at each scan into a rectangle list. Each time it fills up, the
|
|
// rectangle list is passed to the filling routine, and then once again at
|
|
// the end if any rectangles remain undrawn. We continue so long as there
|
|
// are edges to be scanned out
|
|
//
|
|
while ( 1 )
|
|
{
|
|
//
|
|
// Advance the edges in the AET one scan, discarding any that have
|
|
// reached the end (if there are any edges in the AET)
|
|
//
|
|
if ( AETHead.pNext != pAETHead )
|
|
{
|
|
vAdvanceAETEdges(pAETHead);
|
|
}
|
|
|
|
//
|
|
// If the AET is empty, done if the GET is empty, else jump ahead to
|
|
// the next edge in the GET; if the AET isn't empty, re-sort the AET
|
|
//
|
|
if ( AETHead.pNext == pAETHead )
|
|
{
|
|
if ( GETHead.pNext == pGETHead )
|
|
{
|
|
//
|
|
// Done if there are no edges in either the AET or the GET
|
|
//
|
|
break;
|
|
}
|
|
|
|
//
|
|
// There are no edges in the AET, so jump ahead to the next edge in
|
|
// the GET
|
|
//
|
|
iCurrentY = ((EDGE*)GETHead.pNext)->Y;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Re-sort the edges in the AET by X coordinate, if there are at
|
|
// least two edges in the AET (there could be one edge if the
|
|
// balancing edge hasn't yet been added from the GET)
|
|
//
|
|
if ( ((EDGE*)AETHead.pNext)->pNext != pAETHead )
|
|
{
|
|
vXSortAETEdges(pAETHead);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Move any new edges that start on this scan from the GET to the AET;
|
|
// bother calling only if there's at least one edge to add
|
|
//
|
|
if ( ((EDGE*)GETHead.pNext)->Y == iCurrentY )
|
|
{
|
|
vMoveNewEdges(pGETHead, pAETHead, iCurrentY);
|
|
}
|
|
|
|
//
|
|
// Scan the AET into rectangles to fill (there's always at least one
|
|
// edge pair in the AET)
|
|
//
|
|
pCurrentEdge = (EDGE*)AETHead.pNext; // point to the first edge
|
|
do
|
|
{
|
|
INT iLeftEdge;
|
|
|
|
//
|
|
// The left edge of any given edge pair is easy to find; it's just
|
|
// wherever we happen to be currently
|
|
//
|
|
iLeftEdge = pCurrentEdge->X;
|
|
|
|
//
|
|
// Find the matching right edge according to the current fill rule
|
|
//
|
|
if ( (flOptions & FP_WINDINGMODE) != 0 )
|
|
{
|
|
|
|
INT iWindingCount;
|
|
|
|
//
|
|
// Do winding fill; scan across until we've found equal numbers
|
|
// of up and down edges
|
|
//
|
|
iWindingCount = pCurrentEdge->iWindingDirection;
|
|
do
|
|
{
|
|
pCurrentEdge = (EDGE*)pCurrentEdge->pNext;
|
|
iWindingCount += pCurrentEdge->iWindingDirection;
|
|
} while ( iWindingCount != 0 );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Odd-even fill; the next edge is the matching right edge
|
|
//
|
|
pCurrentEdge = (EDGE*)pCurrentEdge->pNext;
|
|
}
|
|
|
|
//
|
|
// See if the resulting span encompasses at least one pixel, and
|
|
// add it to the list of rectangles to draw if so
|
|
//
|
|
if ( iLeftEdge < pCurrentEdge->X )
|
|
{
|
|
//
|
|
// We've got an edge pair to add to the list to be filled; see
|
|
// if there's room for one more rectangle
|
|
//
|
|
if ( ulNumRects >= MAX_PATH_RECTS )
|
|
{
|
|
//
|
|
// No more room; draw the rectangles in the list and reset
|
|
// it to empty
|
|
//
|
|
pb.lNumRects = ulNumRects;
|
|
pb.pRects = prclRects;
|
|
|
|
pb.pgfn(&pb);
|
|
|
|
//
|
|
// Reset the list to empty
|
|
//
|
|
ulNumRects = 0;
|
|
}
|
|
|
|
//
|
|
// Add the rectangle representing the current edge pair
|
|
//
|
|
if ( jClipping == DC_RECT )
|
|
{
|
|
//
|
|
// Clipped
|
|
// Clip to left
|
|
//
|
|
prclRects[ulNumRects].left = max(iLeftEdge, ClipRect.left);
|
|
|
|
//
|
|
// Clip to right
|
|
//
|
|
prclRects[ulNumRects].right =
|
|
min(pCurrentEdge->X, ClipRect.right);
|
|
|
|
//
|
|
// Draw only if not fully clipped
|
|
//
|
|
if ( prclRects[ulNumRects].left
|
|
< prclRects[ulNumRects].right )
|
|
{
|
|
prclRects[ulNumRects].top = iCurrentY;
|
|
prclRects[ulNumRects].bottom = iCurrentY + 1;
|
|
ulNumRects++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Unclipped
|
|
//
|
|
prclRects[ulNumRects].top = iCurrentY;
|
|
prclRects[ulNumRects].bottom = iCurrentY + 1;
|
|
prclRects[ulNumRects].left = iLeftEdge;
|
|
prclRects[ulNumRects].right = pCurrentEdge->X;
|
|
ulNumRects++;
|
|
}
|
|
}
|
|
} while ( (pCurrentEdge = (EDGE*)pCurrentEdge->pNext) != pAETHead );
|
|
|
|
iCurrentY++; // next scan
|
|
}// Loop through all the scans in the polygon
|
|
|
|
//
|
|
// Draw the remaining rectangles, if there are any
|
|
//
|
|
draw_remaining_rectangles:
|
|
|
|
if ( ulNumRects > 0 )
|
|
{
|
|
pb.lNumRects = ulNumRects;
|
|
pb.pRects = prclRects;
|
|
|
|
pb.pgfn(&pb);
|
|
}
|
|
|
|
ReturnTrue:
|
|
DBG_GDI((7, "Drawn"));
|
|
bRetVal = TRUE; // done successfully
|
|
|
|
ReturnFalse:
|
|
//
|
|
// bRetVal is originally false. If you jumped to ReturnFalse from somewhere,
|
|
// then it will remain false, and be returned.
|
|
//
|
|
if ( bMemAlloced )
|
|
{
|
|
//
|
|
// We did allocate memory, so release it
|
|
//
|
|
ENGFREEMEM(pFreeEdges);
|
|
}
|
|
|
|
DBG_GDI((6, "Returning %s", bRetVal ? "True" : "False"));
|
|
|
|
InputBufferFlush(ppdev);
|
|
|
|
//@@BEGIN_DDKSPLIT
|
|
#if MULTITHREADED
|
|
pb.ppdev->ulLockCount--;
|
|
EngReleaseSemaphore(pb.ppdev->hsemLock);
|
|
#endif
|
|
//@@END_DDKSPLIT
|
|
|
|
return (bRetVal);
|
|
}// DrvFillPath()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// void vAdvanceAETEdges(EDGE* pAETHead)
|
|
//
|
|
// Advance the edges in the AET to the next scan, dropping any for which we've
|
|
// done all scans. Assumes there is at least one edge in the AET.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
VOID
|
|
vAdvanceAETEdges(EDGE* pAETHead)
|
|
{
|
|
EDGE* pLastEdge;
|
|
EDGE* pCurrentEdge;
|
|
|
|
pLastEdge = pAETHead;
|
|
pCurrentEdge = (EDGE*)pLastEdge->pNext;
|
|
do
|
|
{
|
|
//
|
|
// Count down this edge's remaining scans
|
|
//
|
|
if ( --pCurrentEdge->iScansLeft == 0 )
|
|
{
|
|
//
|
|
// We've done all scans for this edge; drop this edge from the AET
|
|
//
|
|
pLastEdge->pNext = pCurrentEdge->pNext;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Advance the edge's X coordinate for a 1-scan Y advance
|
|
// Advance by the minimum amount
|
|
//
|
|
pCurrentEdge->X += pCurrentEdge->iXWhole;
|
|
|
|
//
|
|
// Advance the error term and see if we got one extra pixel this
|
|
// time
|
|
//
|
|
pCurrentEdge->iErrorTerm += pCurrentEdge->iErrorAdjustUp;
|
|
|
|
if ( pCurrentEdge->iErrorTerm >= 0 )
|
|
{
|
|
//
|
|
// The error term turned over, so adjust the error term and
|
|
// advance the extra pixel
|
|
//
|
|
pCurrentEdge->iErrorTerm -= pCurrentEdge->iErrorAdjustDown;
|
|
pCurrentEdge->X += pCurrentEdge->iXDirection;
|
|
}
|
|
|
|
pLastEdge = pCurrentEdge;
|
|
}
|
|
} while ((pCurrentEdge = (EDGE *)pLastEdge->pNext) != pAETHead);
|
|
}// vAdvanceAETEdges()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// VOID vXSortAETEdges(EDGE* pAETHead)
|
|
//
|
|
// X-sort the AET, because the edges may have moved around relative to
|
|
// one another when we advanced them. We'll use a multipass bubble
|
|
// sort, which is actually okay for this application because edges
|
|
// rarely move relative to one another, so we usually do just one pass.
|
|
// Also, this makes it easy to keep just a singly-linked list. Assumes there
|
|
// are at least two edges in the AET.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
VOID
|
|
vXSortAETEdges(EDGE *pAETHead)
|
|
{
|
|
BOOL bEdgesSwapped;
|
|
EDGE* pLastEdge;
|
|
EDGE* pCurrentEdge;
|
|
EDGE* pNextEdge;
|
|
|
|
do
|
|
{
|
|
bEdgesSwapped = FALSE;
|
|
pLastEdge = pAETHead;
|
|
pCurrentEdge = (EDGE *)pLastEdge->pNext;
|
|
pNextEdge = (EDGE *)pCurrentEdge->pNext;
|
|
|
|
do
|
|
{
|
|
if ( pNextEdge->X < pCurrentEdge->X )
|
|
{
|
|
//
|
|
// Next edge is to the left of the current edge; swap them
|
|
//
|
|
pLastEdge->pNext = pNextEdge;
|
|
pCurrentEdge->pNext = pNextEdge->pNext;
|
|
pNextEdge->pNext = pCurrentEdge;
|
|
bEdgesSwapped = TRUE;
|
|
|
|
//
|
|
// Continue sorting before the edge we just swapped; it might
|
|
// move farther yet
|
|
//
|
|
pCurrentEdge = pNextEdge;
|
|
}
|
|
|
|
pLastEdge = pCurrentEdge;
|
|
pCurrentEdge = (EDGE *)pLastEdge->pNext;
|
|
} while ( (pNextEdge = (EDGE*)pCurrentEdge->pNext) != pAETHead );
|
|
} while ( bEdgesSwapped );
|
|
}// vXSortAETEdges()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// VOID vMoveNewEdges(EDGE* pGETHead, EDGE* pAETHead, INT iCurrentY)
|
|
//
|
|
// Moves all edges that start on the current scan from the GET to the AET in
|
|
// X-sorted order. Parameters are pointer to head of GET and pointer to dummy
|
|
// edge at head of AET, plus current scan line. Assumes there's at least one
|
|
// edge to be moved.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
VOID
|
|
vMoveNewEdges(EDGE* pGETHead,
|
|
EDGE* pAETHead,
|
|
INT iCurrentY)
|
|
{
|
|
EDGE* pCurrentEdge = pAETHead;
|
|
EDGE* pGETNext = (EDGE*)pGETHead->pNext;
|
|
|
|
do
|
|
{
|
|
//
|
|
// Scan through the AET until the X-sorted insertion point for this
|
|
// edge is found. We can continue from where the last search left
|
|
// off because the edges in the GET are in X sorted order, as is
|
|
// the AET. The search always terminates because the AET sentinel
|
|
// is greater than any valid X
|
|
//
|
|
while ( pGETNext->X > ((EDGE *)pCurrentEdge->pNext)->X )
|
|
{
|
|
pCurrentEdge = (EDGE*)pCurrentEdge->pNext;
|
|
}
|
|
|
|
//
|
|
// We've found the insertion point; add the GET edge to the AET, and
|
|
// remove it from the GET
|
|
//
|
|
pGETHead->pNext = pGETNext->pNext;
|
|
pGETNext->pNext = pCurrentEdge->pNext;
|
|
pCurrentEdge->pNext = pGETNext;
|
|
pCurrentEdge = pGETNext; // continue insertion search for the next
|
|
// GET edge after the edge we just added
|
|
pGETNext = (EDGE*)pGETHead->pNext;
|
|
} while (pGETNext->Y == iCurrentY);
|
|
}// vMoveNewEdges()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// BOOL (EDGE* pGETHead, EDGE* pAETHead, INT iCurrentY)
|
|
//
|
|
// Build the Global Edge Table from the path. There must be enough memory in
|
|
// the free edge area to hold all edges. The GET is constructed in Y-X order,
|
|
// and has a head/tail/sentinel node at pGETHead.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
BOOL
|
|
bConstructGET(EDGE* pGETHead,
|
|
EDGE* pFreeEdges,
|
|
PATHOBJ* ppo,
|
|
PATHDATA* pd,
|
|
BOOL bMore,
|
|
RECTL* pClipRect)
|
|
{
|
|
POINTFIX pfxPathStart; // point that started the current subpath
|
|
POINTFIX pfxPathPrevious; // point before the current point in a subpath;
|
|
// starts the current edge
|
|
|
|
//
|
|
// Create an empty GET with the head node also a tail sentinel
|
|
//
|
|
pGETHead->pNext = pGETHead; // mark that the GET is empty
|
|
pGETHead->Y = 0x7FFFFFFF; // this is greater than any valid Y value, so
|
|
// Searches will always terminate
|
|
|
|
//
|
|
// Note: PATHOBJ_vEnumStart is implicitly performed by engine
|
|
// already and first path is enumerated by the caller
|
|
// so here we don't need to call it again.
|
|
//
|
|
next_subpath:
|
|
|
|
//
|
|
// Make sure the PATHDATA is not empty (is this necessary)???
|
|
//
|
|
if ( pd->count != 0 )
|
|
{
|
|
//
|
|
// If first point starts a subpath, remember it as such
|
|
// and go on to the next point, so we can get an edge
|
|
//
|
|
if ( pd->flags & PD_BEGINSUBPATH )
|
|
{
|
|
//
|
|
// The first point starts the subpath; Remember it
|
|
//
|
|
pfxPathStart = *pd->pptfx; // the subpath starts here
|
|
pfxPathPrevious = *pd->pptfx; // this point starts the next edge
|
|
pd->pptfx++; // advance to the next point
|
|
pd->count--; // count off this point
|
|
}
|
|
|
|
//
|
|
// Add edges in PATHDATA to GET, in Y-X sorted order
|
|
//
|
|
while ( pd->count-- )
|
|
{
|
|
if ( (pFreeEdges =
|
|
pAddEdgeToGET(pGETHead, pFreeEdges, &pfxPathPrevious,
|
|
pd->pptfx, pClipRect)) == NULL )
|
|
{
|
|
goto ReturnFalse;
|
|
}
|
|
|
|
pfxPathPrevious = *pd->pptfx; // current point becomes previous
|
|
pd->pptfx++; // advance to the next point
|
|
}// Loop through all the points
|
|
|
|
//
|
|
// If last point ends the subpath, insert the edge that
|
|
// connects to first point (is this built in already?)
|
|
//
|
|
if ( pd->flags & PD_ENDSUBPATH )
|
|
{
|
|
if ( (pFreeEdges = pAddEdgeToGET(pGETHead, pFreeEdges, &pfxPathPrevious,
|
|
&pfxPathStart, pClipRect)) == NULL )
|
|
{
|
|
goto ReturnFalse;
|
|
}
|
|
}
|
|
}// if ( pd->count != 0 )
|
|
|
|
//
|
|
// The initial loop conditions preclude a do, while or for
|
|
//
|
|
if ( bMore )
|
|
{
|
|
bMore = PATHOBJ_bEnum(ppo, pd);
|
|
goto next_subpath;
|
|
}
|
|
|
|
return(TRUE); // done successfully
|
|
|
|
ReturnFalse:
|
|
return(FALSE); // failed
|
|
}// bConstructGET()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// EDGE* pAddEdgeToGET(EDGE* pGETHead, EDGE* pFreeEdge, POINTFIX* ppfxEdgeStart,
|
|
// POINTFIX* ppfxEdgeEnd, RECTL* pClipRect)
|
|
//
|
|
// Adds the edge described by the two passed-in points to the Global Edge
|
|
// Table (GET), if the edge spans at least one pixel vertically.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
EDGE*
|
|
pAddEdgeToGET(EDGE* pGETHead,
|
|
EDGE* pFreeEdge,
|
|
POINTFIX* ppfxEdgeStart,
|
|
POINTFIX* ppfxEdgeEnd,
|
|
RECTL* pClipRect)
|
|
{
|
|
int iYStart;
|
|
int iYEnd;
|
|
int iXStart;
|
|
int iXEnd;
|
|
int iYHeight;
|
|
int iXWidth;
|
|
int yJump;
|
|
int yTop;
|
|
|
|
//
|
|
// Set the winding-rule direction of the edge, and put the endpoints in
|
|
// top-to-bottom order
|
|
//
|
|
iYHeight = ppfxEdgeEnd->y - ppfxEdgeStart->y;
|
|
|
|
if ( iYHeight == 0 )
|
|
{
|
|
//
|
|
// Zero height; ignore this edge
|
|
//
|
|
return(pFreeEdge);
|
|
}
|
|
else if ( iYHeight > 0 )
|
|
{
|
|
//
|
|
// Top-to-bottom
|
|
//
|
|
iXStart = ppfxEdgeStart->x;
|
|
iYStart = ppfxEdgeStart->y;
|
|
iXEnd = ppfxEdgeEnd->x;
|
|
iYEnd = ppfxEdgeEnd->y;
|
|
|
|
pFreeEdge->iWindingDirection = 1;
|
|
}
|
|
else
|
|
{
|
|
iYHeight = -iYHeight;
|
|
iXEnd = ppfxEdgeStart->x;
|
|
iYEnd = ppfxEdgeStart->y;
|
|
iXStart = ppfxEdgeEnd->x;
|
|
iYStart = ppfxEdgeEnd->y;
|
|
|
|
pFreeEdge->iWindingDirection = -1;
|
|
}
|
|
|
|
if ( iYHeight & 0x80000000 )
|
|
{
|
|
//
|
|
// Too large; outside 2**27 GDI range
|
|
//
|
|
return(NULL);
|
|
}
|
|
|
|
//
|
|
// Set the error term and adjustment factors, all in GIQ coordinates for
|
|
// now
|
|
//
|
|
iXWidth = iXEnd - iXStart;
|
|
if ( iXWidth >= 0 )
|
|
{
|
|
//
|
|
// Left to right, so we change X as soon as we move at all
|
|
//
|
|
pFreeEdge->iXDirection = 1;
|
|
pFreeEdge->iErrorTerm = -1;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Right to left, so we don't change X until we've moved a full GIQ
|
|
// coordinate
|
|
//
|
|
iXWidth = -iXWidth;
|
|
pFreeEdge->iXDirection = -1;
|
|
pFreeEdge->iErrorTerm = -iYHeight;
|
|
}
|
|
|
|
if ( iXWidth & 0x80000000 )
|
|
{
|
|
//
|
|
// Too large; outside 2**27 GDI range
|
|
//
|
|
return(NULL);
|
|
}
|
|
|
|
if ( iXWidth >= iYHeight )
|
|
{
|
|
//
|
|
// Calculate base run length (minimum distance advanced in X for a 1-
|
|
// scan advance in Y)
|
|
//
|
|
pFreeEdge->iXWhole = iXWidth / iYHeight;
|
|
|
|
//
|
|
// Add sign back into base run length if going right to left
|
|
//
|
|
if ( pFreeEdge->iXDirection == -1 )
|
|
{
|
|
pFreeEdge->iXWhole = -pFreeEdge->iXWhole;
|
|
}
|
|
|
|
pFreeEdge->iErrorAdjustUp = iXWidth % iYHeight;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Base run length is 0, because line is closer to vertical than
|
|
// horizontal
|
|
//
|
|
pFreeEdge->iXWhole = 0;
|
|
pFreeEdge->iErrorAdjustUp = iXWidth;
|
|
}
|
|
|
|
pFreeEdge->iErrorAdjustDown = iYHeight;
|
|
|
|
//
|
|
// Calculate the number of pixels spanned by this edge, accounting for
|
|
// clipping
|
|
//
|
|
// Top true pixel scan in GIQ coordinates
|
|
// Shifting to divide and multiply by 16 is okay because the clip rect
|
|
// always contains positive numbers
|
|
//
|
|
yTop = max(pClipRect->top << 4, (iYStart + 15) & ~0x0F);
|
|
|
|
//
|
|
// Initial scan line on which to fill edge
|
|
//
|
|
pFreeEdge->Y = yTop >> 4;
|
|
|
|
//
|
|
// Calculate # of scans to actually fill, accounting for clipping
|
|
//
|
|
if ( (pFreeEdge->iScansLeft = min(pClipRect->bottom, ((iYEnd + 15) >> 4))
|
|
- pFreeEdge->Y) <= 0 )
|
|
{
|
|
//
|
|
// No pixels at all are spanned, so we can ignore this edge
|
|
//
|
|
return(pFreeEdge);
|
|
}
|
|
|
|
//
|
|
// If the edge doesn't start on a pixel scan (that is, it starts at a
|
|
// fractional GIQ coordinate), advance it to the first pixel scan it
|
|
// intersects. Ditto if there's top clipping. Also clip to the bottom if
|
|
// needed
|
|
//
|
|
if ( iYStart != yTop )
|
|
{
|
|
//
|
|
// Jump ahead by the Y distance in GIQ coordinates to the first pixel
|
|
// to draw
|
|
//
|
|
yJump = yTop - iYStart;
|
|
|
|
//
|
|
// Advance x the minimum amount for the number of scans traversed
|
|
//
|
|
iXStart += pFreeEdge->iXWhole * yJump;
|
|
|
|
vAdjustErrorTerm(&pFreeEdge->iErrorTerm, pFreeEdge->iErrorAdjustUp,
|
|
pFreeEdge->iErrorAdjustDown, yJump, &iXStart,
|
|
pFreeEdge->iXDirection);
|
|
}
|
|
|
|
//
|
|
// Turn the calculations into pixel rather than GIQ calculations
|
|
//
|
|
// Move the X coordinate to the nearest pixel, and adjust the error term
|
|
// accordingly
|
|
// Dividing by 16 with a shift is okay because X is always positive
|
|
pFreeEdge->X = (iXStart + 15) >> 4; // convert from GIQ to pixel coordinates
|
|
|
|
//
|
|
// LATER adjust only if needed (if prestepped above)?
|
|
//
|
|
if ( pFreeEdge->iXDirection == 1 )
|
|
{
|
|
//
|
|
// Left to right
|
|
//
|
|
pFreeEdge->iErrorTerm -= pFreeEdge->iErrorAdjustDown
|
|
* (((iXStart + 15) & ~0x0F) - iXStart);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Right to left
|
|
//
|
|
pFreeEdge->iErrorTerm -= pFreeEdge->iErrorAdjustDown
|
|
* ((iXStart - 1) & 0x0F);
|
|
}
|
|
|
|
//
|
|
// Scale the error term down 16 times to switch from GIQ to pixels.
|
|
// Shifts work to do the multiplying because these values are always
|
|
// non-negative
|
|
//
|
|
pFreeEdge->iErrorTerm >>= 4;
|
|
|
|
//
|
|
// Insert the edge into the GET in YX-sorted order. The search always ends
|
|
// because the GET has a sentinel with a greater-than-possible Y value
|
|
//
|
|
while ( (pFreeEdge->Y > ((EDGE*)pGETHead->pNext)->Y)
|
|
||( (pFreeEdge->Y == ((EDGE*)pGETHead->pNext)->Y)
|
|
&&(pFreeEdge->X > ((EDGE*)pGETHead->pNext)->X) ) )
|
|
{
|
|
pGETHead = (EDGE*)pGETHead->pNext;
|
|
}
|
|
|
|
pFreeEdge->pNext = pGETHead->pNext; // link the edge into the GET
|
|
pGETHead->pNext = pFreeEdge;
|
|
|
|
//
|
|
// Point to the next edge storage location for next time
|
|
//
|
|
return(++pFreeEdge);
|
|
}// pAddEdgeToGET()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// void vAdjustErrorTerm(int *pErrorTerm, int iErrorAdjustUp,
|
|
// int iErrorAdjustDown, int yJump, int *pXStart,
|
|
// int iXDirection)
|
|
// Adjust the error term for a skip ahead in y. This is in ASM because there's
|
|
// a multiply/divide that may involve a larger than 32-bit value.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
vAdjustErrorTerm(int* pErrorTerm,
|
|
int iErrorAdjustUp,
|
|
int iErrorAdjustDown,
|
|
int yJump,
|
|
int* pXStart,
|
|
int iXDirection)
|
|
|
|
{
|
|
#if defined(_X86_) || defined(i386)
|
|
//
|
|
// Adjust the error term up by the number of y coordinates we'll skip
|
|
// *pErrorTerm += iErrorAdjustUp * yJump;
|
|
//
|
|
_asm mov ebx,pErrorTerm
|
|
_asm mov eax,iErrorAdjustUp
|
|
_asm mul yJump
|
|
_asm add eax,[ebx]
|
|
_asm adc edx,-1 // the error term starts out negative
|
|
|
|
//
|
|
// See if the error term turned over even once while skipping
|
|
//
|
|
_asm js short NoErrorTurnover
|
|
|
|
//
|
|
// # of times we'll turn over the error term and step an extra x
|
|
// coordinate while skipping
|
|
// NumAdjustDowns = (*pErrorTerm / iErrorAdjustDown) + 1;
|
|
//
|
|
_asm div iErrorAdjustDown
|
|
_asm inc eax
|
|
|
|
//
|
|
// Note that EDX is the remainder; (EDX - iErrorAdjustDown) is where
|
|
// the error term ends up ultimately
|
|
//
|
|
// Advance x appropriately for the # of times the error term
|
|
// turned over
|
|
// if (iXDirection == 1)
|
|
// {
|
|
// *pXStart += NumAdjustDowns;
|
|
// }
|
|
// else
|
|
// {
|
|
// *pXStart -= NumAdjustDowns;
|
|
// }
|
|
//
|
|
_asm mov ecx,pXStart
|
|
_asm cmp iXDirection,1
|
|
_asm jz short GoingRight
|
|
_asm neg eax
|
|
GoingRight:
|
|
_asm add [ecx],eax
|
|
|
|
// Adjust the error term down to its proper post-skip value
|
|
// *pErrorTerm -= iErrorAdjustDown * NumAdjustDowns;
|
|
_asm sub edx,iErrorAdjustDown
|
|
_asm mov eax,edx // put into EAX for storing to pErrorTerm next
|
|
NoErrorTurnover:
|
|
_asm mov [ebx],eax
|
|
|
|
#else
|
|
//
|
|
// LONGLONGS are 64 bit integers (We hope!) as the multiply could
|
|
// overflow 32 bit integers. If 64 bit ints are unsupported, the
|
|
// LONGLONG will end up as a double. Hopefully there will be no
|
|
// noticable difference in accuracy.
|
|
LONGLONG NumAdjustDowns;
|
|
LONGLONG tmpError = *pErrorTerm;
|
|
|
|
//
|
|
// Adjust the error term up by the number of y coordinates we'll skip
|
|
//
|
|
tmpError += (LONGLONG)iErrorAdjustUp * (LONGLONG)yJump;
|
|
|
|
//
|
|
// See if the error term turned over even once while skipping
|
|
//
|
|
if ( tmpError >= 0 )
|
|
{
|
|
//
|
|
// # of times we'll turn over the error term and step an extra x
|
|
// coordinate while skipping
|
|
//
|
|
NumAdjustDowns = (tmpError / (LONGLONG)iErrorAdjustDown) + 1;
|
|
|
|
//
|
|
// Advance x appropriately for the # of times the error term
|
|
// turned over
|
|
//
|
|
if ( iXDirection == 1 )
|
|
{
|
|
*pXStart += (LONG)NumAdjustDowns;
|
|
}
|
|
else
|
|
{
|
|
*pXStart -= (LONG) NumAdjustDowns;
|
|
}
|
|
|
|
//
|
|
// Adjust the error term down to its proper post-skip value
|
|
//
|
|
tmpError -= (LONGLONG)iErrorAdjustDown * NumAdjustDowns;
|
|
}
|
|
*pErrorTerm = (LONG)tmpError;
|
|
|
|
#endif // X86
|
|
}// vAdjustErrorTerm()
|