2025-04-27 07:49:33 -04:00

810 lines
29 KiB
C

/******************************Module*Header*******************************\
* Module Name: fillpath.c
*
* DrvFillPath
*
* Copyright (c) 1992-1995 Microsoft Corporation
\**************************************************************************/
// LATER identify convex polygons and special-case?
// LATER identify vertical edges and special-case?
// LATER move pointed-to variables into automatics in search loops
#include "driver.h"
#include "bitblt.h"
// Maximum number of rects we'll fill per call to
// the fill code
#define MAX_PATH_RECTS 50 // 16 bytes per == 800 bytes
#define MAX_EDGES 50 // 40 bytes per == 2000 bytes
// Describe a single non-horizontal edge of a path to fill.
typedef struct _EDGE {
PVOID pNext;
INT iScansLeft;
INT X;
INT Y;
INT iErrorTerm;
INT iErrorAdjustUp;
INT iErrorAdjustDown;
INT iXWhole;
INT iXDirection;
INT iWindingDirection;
} EDGE, *PEDGE;
//MIX translation table. Translates a mix 1-16, into an old style Rop 0-255.
extern BYTE gaMix[];
VOID AdvanceAETEdges(EDGE *pAETHead);
VOID XSortAETEdges(EDGE *pAETHead);
VOID MoveNewEdges(EDGE *pGETHead, EDGE *pAETHead, INT iCurrentY);
EDGE * AddEdgeToGET(EDGE *pGETHead, EDGE *pFreeEdge, POINTFIX *ppfxEdgeStart,
POINTFIX *ppfxEdgeEnd);
VOID ConstructGET(EDGE *pGETHead, EDGE *pFreeEdges, PATHOBJ *ppo,
PATHDATA *pd, BOOL bMore);
/******************************Public*Routine******************************\
* DrvFillPath
*
* Fill the specified path with the specified brush and ROP.
*
\**************************************************************************/
BOOL DrvFillPath
(
SURFOBJ *pso,
PATHOBJ *ppo,
CLIPOBJ *pco,
BRUSHOBJ *pbo,
POINTL *pptlBrush,
MIX mix,
FLONG flOptions
)
{
ULONG iSolidColor; // Solid color for solid brushes
PDEVSURF pdsurf;
BRUSHINST *pbri; // Pointer to a brush instance
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;
BOOL retval;
RECTL aRectBuf[MAX_PATH_RECTS];
VOID (*pfnFill)(PDEVSURF,ULONG,PRECTL,MIX,BRUSHINST*,PPOINTL);
VOID (*pfnFillSolid)(PDEVSURF,ULONG,PRECTL,MIX,ULONG);
// We don't handle ROP4s
if ((mix & 0xFF) != ((mix >> 8) & 0xFF)) {
return(FALSE); // it's a ROP4; let GDI fill the path
}
pfnFillSolid = vTrgBlt;
if (DRAW_TO_DFB((PDEVSURF)pso->dhsurf))
{
pfnFillSolid = vDFBFILL;
switch (mix & 0xff)
{
case R2_NOP:
return(TRUE); // make sure this doesn't cause a punt!
case R2_WHITE:
case R2_BLACK:
break;
case R2_NOTCOPYPEN:
case R2_COPYPEN:
if (pbo->iSolidColor != 0xffffffff) {
break; // solid color
}
//
// WE ARE FALLING THROUGH BECAUSE PENS MUST BE SOLID!
//
default:
return EngFillPath(pso, ppo, pco, pbo, pptlBrush, mix, flOptions);
}
}
// The drawing surface
pdsurf = (PDEVSURF) pso->dhsurf;
// Is there clipping? We don't handle that
// LATER handle rectangle clipping
// Set up the clipping type
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;
}
if (jClipping != DC_TRIVIAL) {
return(FALSE); // there is clipping; let GDI fill the path
}
// There's nothing to do if there's only one point
// LATER is this needed?
if (ppo->cCurves <= 1) {
return(TRUE);
}
// See if we can use the solid brush accelerators, or have to draw a
// pattern
mix &= 0xFF;
ASSERT(mix != 0, "DrvFillPath: Mix was 0");
switch (mix) {
case R2_MASKNOTPEN:
case R2_NOTCOPYPEN:
case R2_XORPEN:
case R2_MASKPEN:
case R2_NOTXORPEN:
case R2_MERGENOTPEN:
case R2_COPYPEN:
case R2_MERGEPEN:
case R2_NOTMERGEPEN:
case R2_MASKPENNOT:
case R2_NOTMASKPEN:
case R2_MERGEPENNOT:
if (pbo->iSolidColor != 0xffffffff) {
// Solid fill
iSolidColor = pbo->iSolidColor;
pbri = (BRUSHINST *)NULL; // marks fill as solid
} else {
// We'll use our special case pattern code
if (pbo->pvRbrush == (PVOID)NULL)
{
pbri = (BRUSHINST *)BRUSHOBJ_pvGetRbrush(pbo);
if (pbri == (BRUSHINST *)NULL)
return(FALSE); // couldn't realize the brush; let GDI
// fill the path
}
else
{
pbri = (BRUSHINST *)pbo->pvRbrush;
}
// Handle color and mono patterns differently
if (pbri->usStyle == BRI_MONO_PATTERN) {
pfnFill = vMonoPatBlt;
} else {
pfnFill = vClrPatBlt;
}
// We only support non-8 wide brushes with R2_COPYPEN
// LATER do we need to check for 16 wide for R2_COPYPEN?
if ((mix != R2_COPYPEN) && (pbri->RealWidth != 8)) {
return(FALSE); // not R2_COPYPEN and not 8 wide; let GDI
// fill the path
}
break;
}
// Rops that are implicit solid colors
case R2_NOT:
case R2_WHITE:
case R2_BLACK:
// Brush color parameter doesn't matter for these rops
// LATER then why do we set it?
iSolidColor = pbo->iSolidColor;
pbri = (BRUSHINST *)NULL; // marks fill as solid
break;
case R2_NOP:
return(TRUE);
}
// set up working storage in the temporary buffer
prclRects = aRectBuf; // storage for list of rectangles
// to draw
// enumerate path here first time to check for special
// cases (rectangles, single pixel and monotone polygons)
// it is too difficult to determine interaction between
// multiple paths, if there is more than one, skip this
if (! (bMore = PATHOBJ_bEnum(ppo, &pd))) {
// if the count is less than three than it is at best a
// line which means nothing gets drawn so get out now
if (pd.count < 3) {
return(TRUE);
}
// if the count is four, check to see if the polygon is
// really a rectangle since we can really speed that up
if (pd.count == 4) {
prclRects = prclRects;
// we have to start somewhere so 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 second point and the last point
// start by taking floor of the points to deter-
// mine if the GIQ points are in the same pixel
#define FIX_SHIFT 4L
#define FIX_MASK (- (1L << FIX_SHIFT))
prclRects->top = pd.pptfx[0].y + 15 & FIX_MASK;
prclRects->left = pd.pptfx[0].x + 15 & FIX_MASK;
prclRects->right = pd.pptfx[1].x + 15 & FIX_MASK;
if (prclRects->left ^ prclRects->right) {
if (prclRects->top ^ (pd.pptfx[1].y + 15 & FIX_MASK))
goto not_rectangle;
if (prclRects->left ^ (pd.pptfx[3].x + 15 & FIX_MASK))
goto not_rectangle;
if (prclRects->right ^ (pd.pptfx[2].x + 15 & FIX_MASK))
goto not_rectangle;
prclRects->bottom = pd.pptfx[2].y + 15 & FIX_MASK;
if (prclRects->bottom ^ (pd.pptfx[3].y + 15 & FIX_MASK))
goto not_rectangle;
} else {
if (prclRects->top ^ (pd.pptfx[3].y + 15 & FIX_MASK))
goto not_rectangle;
prclRects->bottom = pd.pptfx[1].y + 15 & FIX_MASK;
if (prclRects->bottom ^ (pd.pptfx[2].y + 15 & FIX_MASK))
goto not_rectangle;
prclRects->right = pd.pptfx[2].x + 15 & FIX_MASK;
if (prclRects->right ^ (pd.pptfx[3].x + 15 & FIX_MASK))
goto not_rectangle;
}
// if the left is greater than the right then
// swap them so the blt code doesn't wig out
if (prclRects->left > prclRects->right) {
FIX temp;
temp = prclRects->left;
prclRects->left = prclRects->right;
prclRects->right = temp;
} else {
// if left == right there's nothing to draw
if (prclRects->left == prclRects->right) {
return(TRUE);
}
}
// shift the values to get pixel coordinates
prclRects->left = prclRects->left >> FIX_SHIFT;
prclRects->right = prclRects->right >> FIX_SHIFT;
if (prclRects->top > prclRects->bottom) {
FIX temp;
temp = prclRects->top;
prclRects->top = prclRects->bottom;
prclRects->bottom = temp;
} else {
if (prclRects->top == prclRects->bottom) {
return(TRUE);
}
}
// shift the values to get pixel coordinates
prclRects->top = prclRects->top >> FIX_SHIFT;
prclRects->bottom = prclRects->bottom >> FIX_SHIFT;
// 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;
}
}
// if this is not one of the special cases then we find
// ourselves here and we scan convert by building edges
not_rectangle:
pFreeEdges = (EDGE *) EngAllocMem(0, ppo->cCurves * sizeof(EDGE),
ALLOC_TAG);
if (pFreeEdges == (EDGE *) NULL)
{
return(FALSE); // too many edges; let GDI fill the path
}
// 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;
ConstructGET(pGETHead, pFreeEdges, ppo, &pd, bMore);
// 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) {
AdvanceAETEdges(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) {
XSortAETEdges(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) {
MoveNewEdges(pGETHead, pAETHead, iCurrentY);
}
// Scan the AET into rectangles to fill (there's always at least one
// edge pair in the AET)
pCurrentEdge = 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 = pCurrentEdge->pNext;
iWindingCount += pCurrentEdge->iWindingDirection;
} while (iWindingCount != 0);
} else {
// Odd-even fill; the next edge is the matching right edge
pCurrentEdge = 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
if (pbri == (BRUSHINST *)NULL)
{
(*pfnFillSolid)(pdsurf, ulNumRects, prclRects, mix,
iSolidColor);
}
else
{
(*pfnFill)(pdsurf, ulNumRects, prclRects, mix, pbri,
pptlBrush);
}
// Reset the list to empty
ulNumRects = 0;
}
// Add the rectangle representing the current edge pair
// LATER coalesce rectangles
prclRects[ulNumRects].top = iCurrentY;
prclRects[ulNumRects].bottom = iCurrentY+1;
prclRects[ulNumRects].left = iLeftEdge;
prclRects[ulNumRects].right = pCurrentEdge->X;
ulNumRects++;
}
} while ((pCurrentEdge = pCurrentEdge->pNext) != pAETHead);
iCurrentY++; // next scan
}
/* draw the remaining rectangles, if there are any */
draw_remaining_rectangles:
if (ulNumRects > 0) {
if (pbri == (BRUSHINST *)NULL)
{
(*pfnFillSolid)(pdsurf, ulNumRects, prclRects, mix, iSolidColor);
}
else
{
(*pfnFill)(pdsurf, ulNumRects, prclRects, mix, pbri, pptlBrush);
}
}
retval = TRUE;
//
// if you get to here, we may have allocated memory...
//
if (pFreeEdges) {
//
// we did allocate memory, so release it
//
EngFreeMem(pFreeEdges);
}
return(retval); // done
}
// 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 AdvanceAETEdges(EDGE *pAETHead)
{
EDGE *pLastEdge, *pCurrentEdge;
pLastEdge = pAETHead;
pCurrentEdge = 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 = pLastEdge->pNext) != 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 XSortAETEdges(EDGE *pAETHead)
{
BOOL bEdgesSwapped;
EDGE *pLastEdge, *pCurrentEdge, *pNextEdge;
do {
bEdgesSwapped = FALSE;
pLastEdge = pAETHead;
pCurrentEdge = pLastEdge->pNext;
pNextEdge = 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;
pCurrentEdge = pNextEdge; // continue sorting before the edge
// we just swapped; it might move
// farther yet
}
pLastEdge = pCurrentEdge;
pCurrentEdge = pLastEdge->pNext;
} while ((pNextEdge = pCurrentEdge->pNext) != pAETHead);
} while (bEdgesSwapped);
}
// 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 MoveNewEdges(EDGE *pGETHead, EDGE *pAETHead, INT iCurrentY)
{
EDGE *pCurrentEdge = pAETHead;
EDGE *pGETNext = 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 = 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 = pGETHead->pNext;
} while (pGETNext->Y == 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.
VOID ConstructGET(
EDGE *pGETHead,
EDGE *pFreeEdges,
PATHOBJ *ppo,
PATHDATA *ppd,
BOOL bMore)
{
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
// PATHOBJ_vEnumStart is implicitly performed by engine
// already and first path is enumerated by the caller
next_subpath:
// Make sure the PATHDATA is not empty (is this necessary)
if (ppd->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 (ppd->flags & PD_BEGINSUBPATH) {
// the first point starts the subpath; remember it
pfxPathStart = *ppd->pptfx; // the subpath starts here
pfxPathPrevious = *ppd->pptfx; // this points starts next edge
ppd->pptfx++; // advance to the next point
ppd->count--; // count off this point
}
// add edges in PATHDATA to GET, in Y-X sorted order
while (ppd->count--) {
pFreeEdges =
AddEdgeToGET(pGETHead, pFreeEdges,&pfxPathPrevious,ppd->pptfx);
pfxPathPrevious = *ppd->pptfx; // current point becomes previous
ppd->pptfx++; // advance to the next point
}
// If last point ends the subpath, insert the edge that
// connects to first point (is this built in already)
if (ppd->flags & PD_ENDSUBPATH) {
pFreeEdges = AddEdgeToGET
(pGETHead, pFreeEdges, &pfxPathPrevious, &pfxPathStart);
}
}
// the initial loop conditions preclude a do, while or for
if (bMore) {
bMore = PATHOBJ_bEnum(ppo, ppd);
goto next_subpath;
}
}
// Adds the edge described by the two passed-in points to the Global Edge
// Table, if the edge spans at least one pixel vertically.
EDGE * AddEdgeToGET(EDGE *pGETHead, EDGE *pFreeEdge,
POINTFIX *ppfxEdgeStart, POINTFIX *ppfxEdgeEnd)
{
INT iYStart, iYEnd, iXStart, iXEnd, iYHeight, iXWidth;
// 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) {
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;
}
// First pixel scan line (non-fractional GIQ Y coordinate) edge intersects.
// Dividing by 16 with a shift is okay because Y is always positive
pFreeEdge->Y = (iYStart + 15) >> 4;
// Calculate the number of pixels spanned by this edge
pFreeEdge->iScansLeft = ((iYEnd + 15) >> 4) - pFreeEdge->Y;
if (pFreeEdge->iScansLeft <= 0) {
return(pFreeEdge); // no pixels at all are spanned, so we can ignore
// this edge
}
// 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 >= 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;
// 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
// LATER might be faster to use multiplication and division to jump ahead,
// rather than looping
while ((iYStart & 0x0F) != 0) {
// Starts at a fractional GIQ coordinate, not exactly on a pixel scan
// Advance the edge's GIQ X coordinate for a 1-GIQ-pixel Y advance
// Advance by the minimum amount
iXStart += pFreeEdge->iXWhole;
// Advance the error term and see if we got one extra pixel this time
pFreeEdge->iErrorTerm += pFreeEdge->iErrorAdjustUp;
if (pFreeEdge->iErrorTerm >= 0) {
// The error term turned over, so adjust the error term and
// advance the extra pixel
pFreeEdge->iErrorTerm -= pFreeEdge->iErrorAdjustDown;
iXStart += pFreeEdge->iXDirection;
}
iYStart++; // advance to the next GIQ Y coordinate
}
// 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 adjusts up by 16 times, to move 16 GIQ pixels at a time.
// Shifts work to do the multiplying because these values are always
// non-negative
pFreeEdge->iErrorAdjustUp <<= 4;
pFreeEdge->iErrorAdjustDown <<= 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 = pGETHead->pNext;
}
pFreeEdge->pNext = pGETHead->pNext; // link the edge into the GET
pGETHead->pNext = pFreeEdge;
return(++pFreeEdge); // point to the next edge storage location for next
// time
}