/**************************************************************************\ * * Copyright (c) 2000 Microsoft Corporation * * Module Name: * * AARasterizer.cpp * * Abstract: * * Contains all the code for rasterizing the fill of a path. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ #include "precomp.hpp" #include #if DBG #define ASSERTACTIVELIST(list, y) AssertActiveList(list, y) #define ASSERTACTIVELISTORDER(list) AssertActiveListOrder(list) #define ASSERTINACTIVEARRAY(list, count) AssertInactiveArray(list, count) #define ASSERTPATH(path) AssertPath(path) #else #define ASSERTACTIVELIST(list, y) #define ASSERTACTIVELISTORDER(list) #define ASSERTINACTIVEARRAY(list, count) #define ASSERTPATH(path) #endif // Define our on-stack storage use. The 'free' versions are nicely tuned // to avoid allocations in most common scenarios, while at the same time // not chewing up toooo much stack space. // // We make the debug versions small so that we hit the 'grow' cases more // frequently, for better testing: #if DBG #define EDGE_STORE_STACK_NUMBER 10 #define EDGE_STORE_ALLOCATION_NUMBER 11 #define INACTIVE_LIST_NUMBER 12 #define ENUMERATE_BUFFER_NUMBER 15 #define INTERVAL_BUFFER_NUMBER 8 // Must be at least 4 #define NOMINAL_FILL_POINT_NUMBER 4 // Must be at least 4 #else #define EDGE_STORE_STACK_NUMBER (1600 / sizeof(EpEdge)) #define EDGE_STORE_ALLOCATION_NUMBER (4032 / sizeof(EpEdge)) #define INACTIVE_LIST_NUMBER EDGE_STORE_STACK_NUMBER #define ENUMERATE_BUFFER_NUMBER 32 #define INTERVAL_BUFFER_NUMBER 32 #define NOMINAL_FILL_POINT_NUMBER 32 #endif class EpEdgeStore; // 'EpEdge' is our classic data structure for tracking an edge: struct EpEdge { EpEdge *Next; // Next active edge (don't check for NULL, // look for tail sentinel instead) INT X; // Current X location INT Dx; // X increment INT Error; // Current DDA error INT ErrorUp; // Error increment INT ErrorDown; // Error decrement when the error rolls over INT StartY; // Y-row start INT EndY; // Y-row end INT WindingDirection; // -1 or 1 }; // We the inactive-array separate from the edge allocations so that // we can more easily do in-place sorts on it: struct EpInactiveEdge { EpEdge *Edge; // Associated edge LONGLONG Yx; // Sorting key, StartY and X packed into an lword }; // We allocate room for our edge datastructures in batches: struct EpEdgeAllocation { EpEdgeAllocation *Next; // Next allocation batch (may be NULL) INT Count; EpEdge EdgeArray[EDGE_STORE_STACK_NUMBER]; }; // The following is effectively the paramter list for 'InitializeEdges', // which takes a run of points and sets up the initial edge list: struct EpInitializeEdgesContext { INT MaxY; // Maximum 'y' found, should be INT_MIN on // first call to 'InitializeEdges' RECT* ClipRect; // Bounding clip rectangle in 28.4 format EpEdgeStore *Store; // Where to stick the edges BOOL IsAntialias; // The edges are going to be rendered // using antialiasing super-sampling }; // Interval coverage descriptor for our antialiased filler: struct EpInterval { INT X; // Interval's left edge (Next->X is the // right edge) INT Depth; // Number of layers that this interval has // been covered EpInterval *Next; // Next interval (look for sentinel, not NULL) }; // Allocator structure for the antialiased fill interval data: struct EpIntervalBuffer { EpIntervalBuffer *Next; EpInterval Interval[INTERVAL_BUFFER_NUMBER]; }; /**************************************************************************\ * * Class Description: * * 'EpEdgeStore' is used by 'InitializeEdges' as its repository for * all the edge data: * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ class EpEdgeStore { private: INT TotalCount; // Total edge count in store INT CurrentRemaining; // How much room remains in current buffer EpEdgeAllocation *CurrentBuffer;// Current buffer EpEdge *CurrentEdge; // Current edge in current buffer EpEdgeAllocation *Enumerator; // For enumerating all the edges EpEdgeAllocation EdgeHead; // Our built-in allocation public: EpEdgeStore() { TotalCount = 0; CurrentBuffer = &EdgeHead; CurrentEdge = &EdgeHead.EdgeArray[0]; CurrentRemaining = EDGE_STORE_STACK_NUMBER; EdgeHead.Count = EDGE_STORE_STACK_NUMBER; EdgeHead.Next = NULL; } ~EpEdgeStore() { // Free our allocation list, skipping the head, which is not // dynamically allocated: EpEdgeAllocation *allocation = EdgeHead.Next; while (allocation != NULL) { EpEdgeAllocation *next = allocation->Next; GpFree(allocation); allocation = next; } } INT StartEnumeration() { Enumerator = &EdgeHead; // Update the count and make sure nothing more gets added (in // part because this Count would have to be re-computed): CurrentBuffer->Count -= CurrentRemaining; TotalCount += CurrentBuffer->Count; // Prevent this from being called again, because bad things would // happen: CurrentBuffer = NULL; return(TotalCount); } BOOL Enumerate(EpEdge** startEdge, EpEdge** endEdge) { EpEdgeAllocation *enumerator = Enumerator; // Might return startEdge == endEdge: *startEdge = &enumerator->EdgeArray[0]; *endEdge = &enumerator->EdgeArray[Enumerator->Count]; return((Enumerator = enumerator->Next) != NULL); } VOID StartAddBuffer(EpEdge **currentEdge, INT *remaining) { *currentEdge = CurrentEdge; *remaining = CurrentRemaining; } VOID EndAddBuffer(EpEdge *currentEdge, INT remaining) { CurrentEdge = currentEdge; CurrentRemaining = remaining; } BOOL NextAddBuffer(EpEdge **currentEdge, INT *remaining); }; /**************************************************************************\ * * Function Description: * * The edge initializer is out of room in its current 'store' buffer; * get it a new one. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ BOOL EpEdgeStore::NextAddBuffer( EpEdge **currentEdge, INT *remaining ) { // The caller has completely filled up this chunk: ASSERT(*remaining == 0); // We have to grow our data structure by adding a new buffer // and adding it to the list: EpEdgeAllocation *newBuffer = static_cast (GpMalloc(sizeof(EpEdgeAllocation) + sizeof(EpEdge) * (EDGE_STORE_ALLOCATION_NUMBER - EDGE_STORE_STACK_NUMBER))); if (newBuffer == NULL) return(FALSE); newBuffer->Next = NULL; newBuffer->Count = EDGE_STORE_ALLOCATION_NUMBER; TotalCount += CurrentBuffer->Count; CurrentBuffer->Next = newBuffer; CurrentBuffer = newBuffer; *currentEdge = CurrentEdge = &newBuffer->EdgeArray[0]; *remaining = CurrentRemaining = EDGE_STORE_ALLOCATION_NUMBER; return(TRUE); } /**************************************************************************\ * * Function Description: * * Some debug code for verifying the state of the active edge list. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ BOOL AssertActiveList( const EpEdge *list, INT yCurrent ) { BOOL b = TRUE; INT activeCount = 0; ASSERT(list->X == INT_MIN); b &= (list->X == INT_MIN); // Skip the head sentinel: list = list->Next; while (list->X != INT_MAX) { ASSERT(list->X != INT_MIN); b &= (list->X != INT_MIN); ASSERT(list->X <= list->Next->X); b &= (list->X <= list->Next->X); ASSERT((list->StartY <= yCurrent) && (yCurrent < list->EndY)); b &= ((list->StartY <= yCurrent) && (yCurrent < list->EndY)); activeCount++; list = list->Next; } ASSERT(list->X == INT_MAX); b &= (list->X == INT_MAX); // There should always be a multiple of 2 edges in the active list. // // NOTE: If you hit this assert, do NOT simply comment it out! // It usually means that all the edges didn't get initialized // properly. For every scan-line, there has to be a left edge // and a right edge (or a mulitple thereof). So if you give // even a single bad edge to the edge initializer (or you miss // one), you'll probably hit this assert. ASSERT((activeCount & 1) == 0); b &= ((activeCount & 1) == 0); return(b); } /**************************************************************************\ * * Function Description: * * Some debug code for verifying the state of the active edge list. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ VOID AssertActiveListOrder( const EpEdge *list ) { INT activeCount = 0; ASSERT(list->X == INT_MIN); // Skip the head sentinel: list = list->Next; while (list->X != INT_MAX) { ASSERT(list->X != INT_MIN); ASSERT(list->X <= list->Next->X); activeCount++; list = list->Next; } ASSERT(list->X == INT_MAX); } /**************************************************************************\ * * Class Description: * * Base class for all our fill routines. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ class EpFiller : public DpOutputSpan { public: virtual BOOL IsValid() const { return(TRUE); } }; typedef VOID (FASTCALL EpFiller::*EpFillerFunction)(EpEdge *, INT); /**************************************************************************\ * * Class Description: * * Antialised filler state. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ class EpAntialiasedFiller : public EpFiller { private: INT Y; // Current scan DpOutputSpan *Output; DpOutputSpan *Clipper; EpInterval *StartInterval; // Points to list head entry EpInterval *NewInterval; EpInterval *EndIntervalMinus2; EpIntervalBuffer BuiltinBuffer; EpIntervalBuffer *CurrentBuffer; public: EpAntialiasedFiller(DpOutputSpan *output) { Output = output; Clipper = this; BuiltinBuffer.Interval[0].X = INT_MIN; BuiltinBuffer.Interval[0].Depth = 0; BuiltinBuffer.Interval[0].Next = &BuiltinBuffer.Interval[1]; BuiltinBuffer.Interval[1].X = INT_MAX; BuiltinBuffer.Interval[1].Depth = 0xdeadbeef; BuiltinBuffer.Interval[1].Next = NULL; BuiltinBuffer.Next = NULL; CurrentBuffer = &BuiltinBuffer; StartInterval = &BuiltinBuffer.Interval[0]; NewInterval = &BuiltinBuffer.Interval[2]; EndIntervalMinus2 = &BuiltinBuffer.Interval[INTERVAL_BUFFER_NUMBER - 2]; } ~EpAntialiasedFiller() { GenerateOutputAndClearCoverage(Y); // Free the linked-list of allocations (skipping 'BuiltinBuffer', // which is built into the class): EpIntervalBuffer *buffer = BuiltinBuffer.Next; while (buffer != NULL) { EpIntervalBuffer *nextBuffer = buffer->Next; GpFree(buffer); buffer = nextBuffer; } } VOID SetClipper(DpOutputSpan *clipper) { Clipper = clipper; } VOID FASTCALL FillEdgesAlternate(const EpEdge *active, INT yCurrent); VOID FASTCALL FillEdgesWinding(const EpEdge *active, INT yCurrent); BOOL Grow(EpInterval **newInterval, EpInterval**endIntervalMinus2); VOID GenerateOutputAndClearCoverage(INT yCurrent); virtual GpStatus OutputSpan(INT y, INT left, INT right); }; /**************************************************************************\ * * Function Description: * * Grow our interval buffer. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ BOOL EpAntialiasedFiller::Grow( EpInterval **newInterval, EpInterval **endIntervalMinus2 ) { EpIntervalBuffer *newBuffer = CurrentBuffer->Next; if (!newBuffer) { newBuffer = static_cast (GpMalloc(sizeof(EpIntervalBuffer))); if (!newBuffer) return(FALSE); newBuffer->Next = NULL; CurrentBuffer->Next = newBuffer; } CurrentBuffer = newBuffer; NewInterval = &newBuffer->Interval[2]; EndIntervalMinus2 = &newBuffer->Interval[INTERVAL_BUFFER_NUMBER - 2]; *newInterval = NewInterval; *endIntervalMinus2 = EndIntervalMinus2; return(TRUE); } /**************************************************************************\ * * Function Description: * * Given the active edge list for the current scan, do an alternate-mode * antialiased fill. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ VOID FASTCALL EpAntialiasedFiller::FillEdgesAlternate( const EpEdge *activeList, INT yCurrent ) { EpInterval *interval = StartInterval; EpInterval *newInterval = NewInterval; EpInterval *endIntervalMinus2 = EndIntervalMinus2; const EpEdge *startEdge = activeList->Next; const EpEdge *endEdge; INT left; INT right; INT nextX; ASSERTACTIVELIST(activeList, yCurrent); while (startEdge->X != INT_MAX) { endEdge = startEdge->Next; // We skip empty pairs: if ((left = startEdge->X) != endEdge->X) { // We now know we have a non-empty interval. Skip any // empty interior pairs: while ((right = endEdge->X) == endEdge->Next->X) endEdge = endEdge->Next->Next; ASSERT((left < right) && (right < INT_MAX)); // Make sure we have enough room to add two intervals if // necessary: if (newInterval >= endIntervalMinus2) { if (!Grow(&newInterval, &endIntervalMinus2)) break; // ==============> } // Skip any intervals less than 'left': while ((nextX = interval->Next->X) < left) interval = interval->Next; // Insert a new interval if necessary: if (nextX != left) { newInterval->X = left; newInterval->Depth = interval->Depth + 1; newInterval->Next = interval->Next; interval->Next = newInterval; interval = newInterval; newInterval++; } // Increase the coverage for any intervals between 'left' // and 'right': while ((nextX = interval->Next->X) < right) { interval = interval->Next; interval->Depth++; } // Insert another new interval if necessary: if (nextX != right) { newInterval->X = right; newInterval->Depth = interval->Depth - 1; newInterval->Next = interval->Next; interval->Next = newInterval; interval = newInterval; newInterval++; } } // Prepare for the next iteration: startEdge = endEdge->Next; } NewInterval = newInterval; Y = yCurrent; // If the next scan is done, output what's there: if (((yCurrent + 1) & AA_Y_MASK) == 0) { GenerateOutputAndClearCoverage(yCurrent); } } /**************************************************************************\ * * Function Description: * * Given the active edge list for the current scan, do a winding-mode * antialiased fill. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ VOID FASTCALL EpAntialiasedFiller::FillEdgesWinding( const EpEdge *activeList, INT yCurrent ) { EpInterval *interval = StartInterval; EpInterval *newInterval = NewInterval; EpInterval *endIntervalMinus2 = EndIntervalMinus2; const EpEdge *startEdge = activeList->Next; const EpEdge *endEdge; INT left; INT right; INT nextX; INT windingValue; ASSERTACTIVELIST(activeList, yCurrent); while (startEdge->X != INT_MAX) { endEdge = startEdge->Next; windingValue = startEdge->WindingDirection; while ((windingValue += endEdge->WindingDirection) != 0) endEdge = endEdge->Next; ASSERT(endEdge->X != INT_MAX); // We skip empty pairs: if ((left = startEdge->X) != endEdge->X) { // We now know we have a non-empty interval. Skip any // empty interior pairs: while ((right = endEdge->X) == endEdge->Next->X) { startEdge = endEdge->Next; endEdge = startEdge->Next; windingValue = startEdge->WindingDirection; while ((windingValue += endEdge->WindingDirection) != 0) endEdge = endEdge->Next; } ASSERT((left < right) && (right < INT_MAX)); // Make sure we have enough room to add two intervals if // necessary: if (newInterval >= endIntervalMinus2) { if (!Grow(&newInterval, &endIntervalMinus2)) break; // ==============> } // Skip any intervals less than 'left': while ((nextX = interval->Next->X) < left) interval = interval->Next; // Insert a new interval if necessary: if (nextX != left) { newInterval->X = left; newInterval->Depth = interval->Depth + 1; newInterval->Next = interval->Next; interval->Next = newInterval; interval = newInterval; newInterval++; } // Increase the coverage for any intervals between 'left' // and 'right': while ((nextX = interval->Next->X) < right) { interval = interval->Next; interval->Depth++; } // Insert another new interval if necessary: if (nextX != right) { newInterval->X = right; newInterval->Depth = interval->Depth - 1; newInterval->Next = interval->Next; interval->Next = newInterval; interval = newInterval; newInterval++; } } // Prepare for the next iteration: startEdge = endEdge->Next; } NewInterval = newInterval; Y = yCurrent; // If the next scan is done, output what's there: if (((yCurrent + 1) & AA_Y_MASK) == 0) { GenerateOutputAndClearCoverage(yCurrent); } } /**************************************************************************\ * * Function Description: * * Now that it's been clipped, produce the pixels and modify their * alpha values according to the antialiased coverage. * * Created: * * 03/17/2000 andrewgo * \**************************************************************************/ GpStatus EpAntialiasedFiller::OutputSpan( INT y, // Non-scaled coordinates INT left, INT right ) { ASSERT(right > left); // First ask the 'producer' to actually generate the pixels for us. // Then we need to simply whack the pixel buffer with the coverage // values we've generated. Output->OutputSpan(y, left, right); // Retrieve a pointer to the buffer that the 'producer' just wrote // the pixels to: UCHAR *buffer = reinterpret_cast (Output->GetScanBuffer()->GetCurrentBuffer()); EpInterval *coverage = StartInterval; // Figure out the end of the last pixel, remembering that 'right' // is exclusive: INT scaledRight = right << AA_X_SHIFT; // Skip any intervals that might have been completely clipped out: INT pixelLeftEdge = left << AA_X_SHIFT; while (coverage->Next->X < pixelLeftEdge) coverage = coverage->Next; INT pixelRightEdge = pixelLeftEdge + AA_X_WIDTH; while (pixelLeftEdge < scaledRight) { UINT coverageValue; // Compute the coverage coming into the first pixel: if (coverage->Next->X > pixelRightEdge) { // The interval extends out the end of the pixel: coverageValue = (pixelRightEdge - max(pixelLeftEdge, coverage->X)) * coverage->Depth; } else { // The interval ends in our pixel: coverageValue = (coverage->Next->X - max(pixelLeftEdge, coverage->X)) * coverage->Depth; coverage = coverage->Next; // Add in any coverages for intervals contained entirely within the // pixel: while (coverage->Next->X < pixelRightEdge) { coverageValue += (coverage->Next->X - coverage->X) * coverage->Depth; coverage = coverage->Next; } // Add in the coverage for the interval going out of the pixel: coverageValue += (pixelRightEdge - max(coverage->X, pixelLeftEdge)) * coverage->Depth; } // We've goofed if we get a coverage value more than is theoretically // possible, or if it's zero (in the latter case, it should have // been filtered already by our caller). ASSERT(coverageValue <= (1 << (AA_X_SHIFT + AA_Y_SHIFT))); ASSERT(coverageValue != 0); // Modify the pixel's alpha channel according to the coverage values: #if !defined(NO_PREMULTIPLIED_ALPHA) *(buffer+0) = MULTIPLY_COVERAGE(*(buffer+0), coverageValue, AA_X_SHIFT + AA_Y_SHIFT); *(buffer+1) = MULTIPLY_COVERAGE(*(buffer+1), coverageValue, AA_X_SHIFT + AA_Y_SHIFT); *(buffer+2) = MULTIPLY_COVERAGE(*(buffer+2), coverageValue, AA_X_SHIFT + AA_Y_SHIFT); #endif *(buffer+3) = MULTIPLY_COVERAGE(*(buffer+3), coverageValue, AA_X_SHIFT + AA_Y_SHIFT); buffer += 4; // Now handle the part of the current interval that completely covers // more than one pixel (if it does): UINT consecutivePixels = (min(coverage->Next->X, scaledRight) - pixelRightEdge) >> AA_X_SHIFT; UINT depth = coverage->Depth; // By definition, we shouldn't have an interval with zero coverage // (it should have been filtered out by our caller). We won't fall // over, but it would be the wrong thing to do for SrcCopy mode. ASSERT((consecutivePixels == 0) || (depth != 0)); if (depth == AA_Y_HEIGHT) { // All these pixels are completely covered. Woo hoo, no work to // do! buffer += (4 * consecutivePixels); } else { // Go through the run and multiply the alpha values by the run's // coverage: UINT i = consecutivePixels; while (i-- != 0) { #if !defined(NO_PREMULTIPLIED_ALPHA) *(buffer+0) = MULTIPLY_COVERAGE(*(buffer+0), depth, AA_Y_SHIFT); *(buffer+1) = MULTIPLY_COVERAGE(*(buffer+1), depth, AA_Y_SHIFT); *(buffer+2) = MULTIPLY_COVERAGE(*(buffer+2), depth, AA_Y_SHIFT); #endif *(buffer+3) = MULTIPLY_COVERAGE(*(buffer+3), depth, AA_Y_SHIFT); buffer += 4; } } // Prepare for the next iteration through the loop: pixelLeftEdge += ((consecutivePixels + 1) << AA_X_SHIFT); pixelRightEdge += ((consecutivePixels + 1) << AA_X_SHIFT); } return(Ok); } /**************************************************************************\ * * Function Description: * * Given complete interval data for a scan, find runs of touched pixels * and then call the clipper (or directly to the rendering routine if * there's no clipping). * * Created: * * 03/17/2000 andrewgo * \**************************************************************************/ VOID EpAntialiasedFiller::GenerateOutputAndClearCoverage( INT yScaled ) { EpInterval *spanStart = StartInterval->Next; EpInterval *spanEnd; while (spanStart->X != INT_MAX) { ASSERT(spanStart->Depth != 0); // Here we determine the length of a continuous run of covered // pixels. For the case where the user has set the mode to // SRCCOPY, it's very important that we don't accidentally pass // off as 'covered' a pixel that we later realize wasn't covered. spanEnd = spanStart->Next; while ((spanEnd->Depth != 0) || ((spanEnd->Next->X & ~AA_X_MASK) == (spanEnd->X & ~AA_X_MASK))) { spanEnd = spanEnd->Next; } // Figure out the actual integer pixel values. INT left = spanStart->X >> AA_X_SHIFT; // inclusive INT right = (spanEnd->X + AA_X_WIDTH - 1) >> AA_X_SHIFT; // exclusive INT y = yScaled >> AA_Y_SHIFT; // If there's no clip region, this jumps to EpAntialiasedFiller:: // OutputSpan: Clipper->OutputSpan(y, left, right); // Advance to after the gap: spanStart = spanEnd->Next; } // Reset our coverage structure. Point the head back to the tail, // and reset where the next new entry will be placed: BuiltinBuffer.Interval[0].Next = &BuiltinBuffer.Interval[1]; CurrentBuffer = &BuiltinBuffer; NewInterval = &BuiltinBuffer.Interval[2]; EndIntervalMinus2 = &BuiltinBuffer.Interval[INTERVAL_BUFFER_NUMBER - 2]; } /**************************************************************************\ * * Class Description: * * Aliased filler state. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ class EpAliasedFiller : public EpFiller { private: DpOutputSpan *Output; public: EpAliasedFiller(DpOutputSpan *output) { Output = output; } VOID SetOutputSpan(DpOutputSpan *output) { Output = output; } VOID FASTCALL FillEdgesAlternate(const EpEdge *active, INT yCurrent); VOID FASTCALL FillEdgesWinding(const EpEdge *active, INT yCurrent); virtual GpStatus OutputSpan(INT y, INT left, INT right) { return Ok; } }; /**************************************************************************\ * * Function Description: * * Given the active edge list for the current scan, do an alternate-mode * aliased fill. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ VOID FASTCALL EpAliasedFiller::FillEdgesAlternate( const EpEdge *activeList, INT yCurrent ) { const EpEdge *startEdge = activeList->Next; const EpEdge *endEdge; INT left; INT right; INT nextX; ASSERTACTIVELIST(activeList, yCurrent); while (startEdge->X != INT_MAX) { endEdge = startEdge->Next; ASSERT(endEdge->X != INT_MAX); // We skip empty pairs: if ((left = startEdge->X) != endEdge->X) { // We now know we have a non-empty interval. Skip any // empty interior pairs: while ((right = endEdge->X) == endEdge->Next->X) endEdge = endEdge->Next->Next; ASSERT((left < right) && (right < INT_MAX)); Output->OutputSpan(yCurrent, left, right); } // Prepare for the next iteration: startEdge = endEdge->Next; } } /**************************************************************************\ * * Function Description: * * Given the active edge list for the current scan, do a winding-mode * aliased fill. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ VOID FASTCALL EpAliasedFiller::FillEdgesWinding( const EpEdge *activeList, INT yCurrent ) { const EpEdge *startEdge = activeList->Next; const EpEdge *endEdge; INT left; INT right; INT nextX; INT windingValue; ASSERTACTIVELIST(activeList, yCurrent); while (startEdge->X != INT_MAX) { endEdge = startEdge->Next; windingValue = startEdge->WindingDirection; while ((windingValue += endEdge->WindingDirection) != 0) endEdge = endEdge->Next; ASSERT(endEdge->X != INT_MAX); // We skip empty pairs: if ((left = startEdge->X) != endEdge->X) { // We now know we have a non-empty interval. Skip any // empty interior pairs: while ((right = endEdge->X) == endEdge->Next->X) { startEdge = endEdge->Next; endEdge = startEdge->Next; windingValue = startEdge->WindingDirection; while ((windingValue += endEdge->WindingDirection) != 0) endEdge = endEdge->Next; } ASSERT((left < right) && (right < INT_MAX)); Output->OutputSpan(yCurrent, left, right); } // Prepare for the next iteration: startEdge = endEdge->Next; } } #ifdef BEZIER_FLATTEN_GDI_COMPATIBLE // GDI flattens using an error of 2/3 // Flatten to an error of 2/3. During initial phase, use 18.14 format. #define TEST_MAGNITUDE_INITIAL (6 * 0x00002aa0L) // Error of 2/3. During normal phase, use 15.17 format. #define TEST_MAGNITUDE_NORMAL (TEST_MAGNITUDE_INITIAL << 3) #else // Use a higher flattening tolerance. Turns out that 2/3 produces very // noticable artifacts on antialiased lines. // Flatten to an error of 1/4. During initial phase, use 18.14 format. #define TEST_MAGNITUDE_INITIAL (6 * 0x00001000L) // Error of 1/4. During normal phase, use 15.17 format. #define TEST_MAGNITUDE_NORMAL (TEST_MAGNITUDE_INITIAL << 3) #endif /**********************************Class***********************************\ * class HfdBasis32 * * Class for HFD vector objects. * * Public Interface: * * vInit(p1, p2, p3, p4) - Re-parameterizes the given control points * to our initial HFD error basis. * vLazyHalveStepSize(cShift) - Does a lazy shift. Caller has to remember * it changes 'cShift' by 2. * vSteadyState(cShift) - Re-parameterizes to our working normal * error basis. * * vTakeStep() - Forward steps to next sub-curve * vHalveStepSize() - Adjusts down (subdivides) the sub-curve * vDoubleStepSize() - Adjusts up the sub-curve * lError() - Returns error if current sub-curve were * to be approximated using a straight line * (value is actually multiplied by 6) * fxValue() - Returns rounded coordinate of first point in * current sub-curve. Must be in steady * state. * * History: * 10-Nov-1990 -by- J. Andrew Goossen [andrewgo] * Wrote it. \**************************************************************************/ // The code is actually smaller when these methods are forced inline; // this is one of the rare cases where 'forceinline' is warranted: #define INLINE __forceinline class HfdBasis32 { private: LONG e0; LONG e1; LONG e2; LONG e3; public: INLINE LONG lParentErrorDividedBy4() { return(max(abs(e3), abs(e2 + e2 - e3))); } INLINE LONG lError() { return(max(abs(e2), abs(e3))); } INLINE INT fxValue() { return((e0 + (1L << 12)) >> 13); } INLINE VOID vInit(INT p1, INT p2, INT p3, INT p4) { // Change basis and convert from 28.4 to 18.14 format: e0 = (p1 ) << 10; e1 = (p4 - p1 ) << 10; e2 = (3 * (p2 - p3 - p3 + p4)) << 11; e3 = (3 * (p1 - p2 - p2 + p3)) << 11; } INLINE VOID vLazyHalveStepSize(LONG cShift) { e2 = (e2 + e3) >> 1; e1 = (e1 - (e2 >> cShift)) >> 1; } INLINE VOID vSteadyState(LONG cShift) { // We now convert from 18.14 fixed format to 15.17: e0 <<= 3; e1 <<= 3; register LONG lShift = cShift - 3; if (lShift < 0) { lShift = -lShift; e2 <<= lShift; e3 <<= lShift; } else { e2 >>= lShift; e3 >>= lShift; } } INLINE VOID vHalveStepSize() { e2 = (e2 + e3) >> 3; e1 = (e1 - e2) >> 1; e3 >>= 2; } INLINE VOID vDoubleStepSize() { e1 += e1 + e2; e3 <<= 2; e2 = (e2 << 3) - e3; } INLINE VOID vTakeStep() { e0 += e1; register LONG lTemp = e2; e1 += lTemp; e2 += lTemp - e3; e3 = lTemp; } }; /**********************************Class***********************************\ * class Bezier32 * * Bezier cracker. * * A hybrid cubic Bezier curve flattener based on KirkO's error factor. * Generates line segments fast without using the stack. Used to flatten * a path. * * For an understanding of the methods used, see: * * Kirk Olynyk, "..." * Goossen and Olynyk, "System and Method of Hybrid Forward * Differencing to Render Bezier Splines" * Lien, Shantz and Vaughan Pratt, "Adaptive Forward Differencing for * Rendering Curves and Surfaces", Computer Graphics, July 1987 * Chang and Shantz, "Rendering Trimmed NURBS with Adaptive Forward * Differencing", Computer Graphics, August 1988 * Foley and Van Dam, "Fundamentals of Interactive Computer Graphics" * * Public Interface: * * vInit(pptfx) - pptfx points to 4 control points of * Bezier. Current point is set to the first * point after the start-point. * Bezier32(pptfx) - Constructor with initialization. * vGetCurrent(pptfx) - Returns current polyline point. * bCurrentIsEndPoint() - TRUE if current point is end-point. * vNext() - Moves to next polyline point. * * History: * 1-Oct-1991 -by- J. Andrew Goossen [andrewgo] * Wrote it. \**************************************************************************/ class Bezier32 { private: LONG cSteps; HfdBasis32 x; HfdBasis32 y; RECT rcfxBound; public: BOOL bInit(const POINT* aptfx, const RECT*); INT cFlatten(POINT* pptfx, INT cptfx, BOOL *pbMore); }; #define FRACTION64 28 class HfdBasis64 { private: LONGLONG e0; LONGLONG e1; LONGLONG e2; LONGLONG e3; public: VOID vInit(INT p1, INT p2, INT p3, INT p4); VOID vHalveStepSize(); VOID vDoubleStepSize(); VOID vTakeStep(); VOID vUntransform(LONG* afx); VOID vParentError(LONGLONG* peq) const; VOID vError(LONGLONG* peq) const; INT fxValue() const; }; class Bezier64 { private: HfdBasis64 xLow; HfdBasis64 yLow; HfdBasis64 xHigh; HfdBasis64 yHigh; LONGLONG eqErrorLow; RECT* prcfxClip; RECT rcfxClip; LONG cStepsHigh; LONG cStepsLow; public: INT cFlatten(POINT* pptfx, INT cptfx, BOOL *pbMore); VOID vInit(const POINT* aptfx, const RECT* prcfx, const LONGLONG eq); }; typedef struct _BEZIERCONTROLS { POINT ptfx[4]; } BEZIERCONTROLS; inline VOID vBoundBox(const POINT* aptfx, RECT* prcfx) { INT i; INT left = aptfx[0].x; INT right = aptfx[0].x; INT top = aptfx[0].y; INT bottom = aptfx[0].y; for (i = 1; i < 4; i++) { left = min(left, aptfx[i].x); top = min(top, aptfx[i].y); right = max(right, aptfx[i].x); bottom = max(bottom, aptfx[i].y); } // We make the bounds one pixel loose for the nominal width // stroke case, which increases the bounds by half a pixel // in every dimension: prcfx->left = left - 16; prcfx->top = top - 16; prcfx->right = right + 16; prcfx->bottom = bottom + 16; } BOOL bIntersect( const RECT *a, const RECT *b) { return((a->left < b->right) && (a->top < b->bottom) && (a->right > b->left) && (a->bottom > b->top)); } BOOL Bezier32::bInit( const POINT* aptfxBez, // Pointer to 4 control points const RECT* prcfxClip) // Bound box of visible region (optional) { POINT aptfx[4]; LONG cShift = 0; // Keeps track of 'lazy' shifts cSteps = 1; // Number of steps to do before reach end of curve vBoundBox(aptfxBez, &rcfxBound); *((BEZIERCONTROLS*) aptfx) = *((BEZIERCONTROLS*) aptfxBez); { register INT fxOr; register INT fxOffset; fxOffset = rcfxBound.left; fxOr = (aptfx[0].x -= fxOffset); fxOr |= (aptfx[1].x -= fxOffset); fxOr |= (aptfx[2].x -= fxOffset); fxOr |= (aptfx[3].x -= fxOffset); fxOffset = rcfxBound.top; fxOr |= (aptfx[0].y -= fxOffset); fxOr |= (aptfx[1].y -= fxOffset); fxOr |= (aptfx[2].y -= fxOffset); fxOr |= (aptfx[3].y -= fxOffset); // This 32 bit cracker can only handle points in a 10 bit space: if ((fxOr & 0xffffc000) != 0) return(FALSE); } x.vInit(aptfx[0].x, aptfx[1].x, aptfx[2].x, aptfx[3].x); y.vInit(aptfx[0].y, aptfx[1].y, aptfx[2].y, aptfx[3].y); if (prcfxClip == (RECT*) NULL || bIntersect(&rcfxBound, prcfxClip)) { while (TRUE) { register LONG lTestMagnitude = TEST_MAGNITUDE_INITIAL << cShift; if (x.lError() <= lTestMagnitude && y.lError() <= lTestMagnitude) break; cShift += 2; x.vLazyHalveStepSize(cShift); y.vLazyHalveStepSize(cShift); cSteps <<= 1; } } x.vSteadyState(cShift); y.vSteadyState(cShift); // Note that this handles the case where the initial error for // the Bezier is already less than TEST_MAGNITUDE_NORMAL: x.vTakeStep(); y.vTakeStep(); cSteps--; return(TRUE); } INT Bezier32::cFlatten(POINT* pptfx, INT cptfx, BOOL *pbMore) { ASSERT(cptfx > 0); INT cptfxOriginal = cptfx; do { // Return current point: pptfx->x = x.fxValue() + rcfxBound.left; pptfx->y = y.fxValue() + rcfxBound.top; pptfx++; // If cSteps == 0, that was the end point in the curve! if (cSteps == 0) { *pbMore = FALSE; // '+1' because we haven't decremented 'cptfx' yet: return(cptfxOriginal - cptfx + 1); } // Okay, we have to step: if (max(x.lError(), y.lError()) > TEST_MAGNITUDE_NORMAL) { x.vHalveStepSize(); y.vHalveStepSize(); cSteps <<= 1; } ASSERTMSG(max(x.lError(), y.lError()) <= TEST_MAGNITUDE_NORMAL, ("Please tell AndrewGo he was wrong")); while (!(cSteps & 1) && x.lParentErrorDividedBy4() <= (TEST_MAGNITUDE_NORMAL >> 2) && y.lParentErrorDividedBy4() <= (TEST_MAGNITUDE_NORMAL >> 2)) { x.vDoubleStepSize(); y.vDoubleStepSize(); cSteps >>= 1; } cSteps--; x.vTakeStep(); y.vTakeStep(); } while (--cptfx != 0); *pbMore = TRUE; return(cptfxOriginal); } /////////////////////////////////////////////////////////////////////////// // Bezier64 // // All math is done using 64 bit fixed numbers in a 36.28 format. // // All drawing is done in a 31 bit space, then a 31 bit window offset // is applied. In the initial transform where we change to the HFD // basis, e2 and e3 require the most bits precision: e2 = 6(p2 - 2p3 + p4). // This requires an additional 4 bits precision -- hence we require 36 bits // for the integer part, and the remaining 28 bits is given to the fraction. // // In rendering a Bezier, every 'subdivide' requires an extra 3 bits of // fractional precision. In order to be reversible, we can allow no // error to creep in. Since a INT coordinate is 32 bits, and we // require an additional 4 bits as mentioned above, that leaves us // 28 bits fractional precision -- meaning we can do a maximum of // 9 subdivides. Now, the maximum absolute error of a Bezier curve in 27 // bit integer space is 2^29 - 1. But 9 subdivides reduces the error by a // guaranteed factor of 2^18, meaning we can crack down only to an error // of 2^11 before we overflow, when in fact we want to crack error to less // than 1. // // So what we do is HFD until we hit an error less than 2^11, reverse our // basis transform to get the four control points of this smaller curve // (rounding in the process to 32 bits), then invoke another copy of HFD // on the reduced Bezier curve. We again have enough precision, but since // its starting error is less than 2^11, we can reduce error to 2^-7 before // overflowing! We'll start a low HFD after every step of the high HFD. //////////////////////////////////////////////////////////////////////////// // The following is our 2^11 target error encoded as a 36.28 number // (don't forget the additional 4 bits of fractional precision!) and // the 6 times error multiplier: const LONGLONG geqErrorHigh = (LONGLONG)(6 * (1L << 15) >> (32 - FRACTION64)) << 32; // The following is the default 2/3 error encoded as a 36.28 number, // multiplied by 6, and leaving 4 bits for fraction: const LONGLONG geqErrorLow = (LONGLONG)(4) << 32; inline INT HfdBasis64::fxValue() const { // Convert from 36.28 and round: LONGLONG eq = e0; eq += (1L << (FRACTION64 - 1)); eq >>= FRACTION64; return((INT) (LONG) eq); } #define MAX(a, b) ((a) >= (b) ? (a) : (b)) #define ABS(a) ((a) >= 0 ? (a) : -(a)) inline VOID HfdBasis64::vParentError(LONGLONG* peq) const { *peq = MAX(ABS(e3 << 2), ABS((e2 << 3) - (e3 << 2))); } inline VOID HfdBasis64::vError(LONGLONG* peq) const { *peq = MAX(ABS(e2), ABS(e3)); } VOID HfdBasis64::vInit(INT p1, INT p2, INT p3, INT p4) { LONGLONG eqTmp; LONGLONG eqP2 = (LONGLONG) p2; LONGLONG eqP3 = (LONGLONG) p3; // e0 = p1 // e1 = p4 - p1 // e2 = 6(p2 - 2p3 + p4) // e3 = 6(p1 - 2p2 + p3) // Change basis: e0 = p1; // e0 = p1 e1 = p4; e2 = eqP2; e2 -= eqP3; e2 -= eqP3; e2 += e1; // e2 = p2 - 2*p3 + p4 e3 = e0; e3 -= eqP2; e3 -= eqP2; e3 += eqP3; // e3 = p1 - 2*p2 + p3 e1 -= e0; // e1 = p4 - p1 // Convert to 36.28 format and multiply e2 and e3 by six: e0 <<= FRACTION64; e1 <<= FRACTION64; eqTmp = e2; e2 += eqTmp; e2 += eqTmp; e2 <<= (FRACTION64 + 1); eqTmp = e3; e3 += eqTmp; e3 += eqTmp; e3 <<= (FRACTION64 + 1); } VOID HfdBasis64::vUntransform(LONG* afx) { // Declare some temps to hold our operations, since we can't modify e0..e3. LONGLONG eqP0; LONGLONG eqP1; LONGLONG eqP2; LONGLONG eqP3; // p0 = e0 // p1 = e0 + (6e1 - e2 - 2e3)/18 // p2 = e0 + (12e1 - 2e2 - e3)/18 // p3 = e0 + e1 eqP0 = e0; // NOTE PERF: Convert this to a multiply by 6: [andrewgo] eqP2 = e1; eqP2 += e1; eqP2 += e1; eqP1 = eqP2; eqP1 += eqP2; // 6e1 eqP1 -= e2; // 6e1 - e2 eqP2 = eqP1; eqP2 += eqP1; // 12e1 - 2e2 eqP2 -= e3; // 12e1 - 2e2 - e3 eqP1 -= e3; eqP1 -= e3; // 6e1 - e2 - 2e3 // NOTE: May just want to approximate these divides! [andrewgo] // Or can do a 64 bit divide by 32 bit to get 32 bits right here. eqP1 /= 18; eqP2 /= 18; eqP1 += e0; eqP2 += e0; eqP3 = e0; eqP3 += e1; // Convert from 36.28 format with rounding: eqP0 += (1L << (FRACTION64 - 1)); eqP0 >>= FRACTION64; afx[0] = (LONG) eqP0; eqP1 += (1L << (FRACTION64 - 1)); eqP1 >>= FRACTION64; afx[2] = (LONG) eqP1; eqP2 += (1L << (FRACTION64 - 1)); eqP2 >>= FRACTION64; afx[4] = (LONG) eqP2; eqP3 += (1L << (FRACTION64 - 1)); eqP3 >>= FRACTION64; afx[6] = (LONG) eqP3; } VOID HfdBasis64::vHalveStepSize() { // e2 = (e2 + e3) >> 3 // e1 = (e1 - e2) >> 1 // e3 >>= 2 e2 += e3; e2 >>= 3; e1 -= e2; e1 >>= 1; e3 >>= 2; } VOID HfdBasis64::vDoubleStepSize() { // e1 = 2e1 + e2 // e3 = 4e3; // e2 = 8e2 - e3 e1 <<= 1; e1 += e2; e3 <<= 2; e2 <<= 3; e2 -= e3; } VOID HfdBasis64::vTakeStep() { e0 += e1; LONGLONG eqTmp = e2; e1 += e2; e2 += eqTmp; e2 -= e3; e3 = eqTmp; } VOID Bezier64::vInit( const POINT* aptfx, // Pointer to 4 control points const RECT* prcfxVis, // Pointer to bound box of visible area (may be NULL) LONGLONG eqError) // Fractional maximum error (32.32 format) { LONGLONG eqTmp; cStepsHigh = 1; cStepsLow = 0; xHigh.vInit(aptfx[0].x, aptfx[1].x, aptfx[2].x, aptfx[3].x); yHigh.vInit(aptfx[0].y, aptfx[1].y, aptfx[2].y, aptfx[3].y); // Initialize error: eqErrorLow = eqError; if (prcfxVis == (RECT*) NULL) prcfxClip = (RECT*) NULL; else { rcfxClip = *prcfxVis; prcfxClip = &rcfxClip; } while (((xHigh.vError(&eqTmp), eqTmp) > geqErrorHigh) || ((yHigh.vError(&eqTmp), eqTmp) > geqErrorHigh)) { cStepsHigh <<= 1; xHigh.vHalveStepSize(); yHigh.vHalveStepSize(); } } INT Bezier64::cFlatten(POINT* pptfx, INT cptfx, BOOL *pbMore) { POINT aptfx[4]; RECT rcfxBound; LONGLONG eqTmp; INT cptfxOriginal = cptfx; ASSERT(cptfx > 0); do { if (cStepsLow == 0) { // Optimization that if the bound box of the control points doesn't // intersect with the bound box of the visible area, render entire // curve as a single line: xHigh.vUntransform(&aptfx[0].x); yHigh.vUntransform(&aptfx[0].y); xLow.vInit(aptfx[0].x, aptfx[1].x, aptfx[2].x, aptfx[3].x); yLow.vInit(aptfx[0].y, aptfx[1].y, aptfx[2].y, aptfx[3].y); cStepsLow = 1; if (prcfxClip != (RECT*) NULL) vBoundBox(aptfx, &rcfxBound); if (prcfxClip == (RECT*) NULL || bIntersect(&rcfxBound, prcfxClip)) { while (((xLow.vError(&eqTmp), eqTmp) > eqErrorLow) || ((yLow.vError(&eqTmp), eqTmp) > eqErrorLow)) { cStepsLow <<= 1; xLow.vHalveStepSize(); yLow.vHalveStepSize(); } } // This 'if' handles the case where the initial error for the Bezier // is already less than the target error: if (--cStepsHigh != 0) { xHigh.vTakeStep(); yHigh.vTakeStep(); if (((xHigh.vError(&eqTmp), eqTmp) > geqErrorHigh) || ((yHigh.vError(&eqTmp), eqTmp) > geqErrorHigh)) { cStepsHigh <<= 1; xHigh.vHalveStepSize(); yHigh.vHalveStepSize(); } while (!(cStepsHigh & 1) && ((xHigh.vParentError(&eqTmp), eqTmp) <= geqErrorHigh) && ((yHigh.vParentError(&eqTmp), eqTmp) <= geqErrorHigh)) { xHigh.vDoubleStepSize(); yHigh.vDoubleStepSize(); cStepsHigh >>= 1; } } } xLow.vTakeStep(); yLow.vTakeStep(); pptfx->x = xLow.fxValue(); pptfx->y = yLow.fxValue(); pptfx++; cStepsLow--; if (cStepsLow == 0 && cStepsHigh == 0) { *pbMore = FALSE; // '+1' because we haven't decremented 'cptfx' yet: return(cptfxOriginal - cptfx + 1); } if (((xLow.vError(&eqTmp), eqTmp) > eqErrorLow) || ((yLow.vError(&eqTmp), eqTmp) > eqErrorLow)) { cStepsLow <<= 1; xLow.vHalveStepSize(); yLow.vHalveStepSize(); } while (!(cStepsLow & 1) && ((xLow.vParentError(&eqTmp), eqTmp) <= eqErrorLow) && ((yLow.vParentError(&eqTmp), eqTmp) <= eqErrorLow)) { xLow.vDoubleStepSize(); yLow.vDoubleStepSize(); cStepsLow >>= 1; } } while (--cptfx != 0); *pbMore = TRUE; return(cptfxOriginal); } /**********************************Class***********************************\ * class BEZIER * * Bezier cracker. Flattens any Bezier in our 28.4 device space down * to a smallest 'error' of 2^-7 = 0.0078. Will use fast 32 bit cracker * for small curves and slower 64 bit cracker for big curves. * * Public Interface: * * vInit(aptfx, prcfxClip, peqError) * - pptfx points to 4 control points of Bezier. The first point * retrieved by bNext() is the the first point in the approximation * after the start-point. * * - prcfxClip is an optional pointer to the bound box of the visible * region. This is used to optimize clipping of Bezier curves that * won't be seen. Note that this value should account for the pen's * width! * * - optional maximum error in 32.32 format, corresponding to Kirko's * error factor. * * bNext(pptfx) * - pptfx points to where next point in approximation will be * returned. Returns FALSE if the point is the end-point of the * curve. * * History: * 1-Oct-1991 -by- J. Andrew Goossen [andrewgo] * Wrote it. \**************************************************************************/ class BEZIER { private: union { Bezier64 bez64; Bezier32 bez32; } bez; BOOL bBez32; public: // All coordinates must be in 28.4 format: BEZIER(const POINT* aptfx, const RECT* prcfxClip) { bBez32 = bez.bez32.bInit(aptfx, prcfxClip); if (!bBez32) bez.bez64.vInit(aptfx, prcfxClip, geqErrorLow); } INT Flatten(POINT* pptfx, INT cptfx, BOOL *pbMore) { if (bBez32) { return(bez.bez32.cFlatten(pptfx, cptfx, pbMore)); } else { return(bez.bez64.cFlatten(pptfx, cptfx, pbMore)); } } }; /**************************************************************************\ * * Function Description: * * Clip the edge vertically. * * We've pulled this routine out-of-line from InitializeEdges mainly * because it needs to call inline Asm, and when there is in-line * Asm in a routine the compiler generally does a much less efficient * job optimizing the whole routine. InitializeEdges is rather * performance critical, so we avoid polluting the whole routine * by having this functionality out-of-line. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ VOID ClipEdge( EpEdge *edgeBuffer, INT yClipTopInteger, INT dMOriginal ) { INT xDelta; INT error; // Cases where bigNumerator will exceed 32-bits in precision // will be rare, but could happen, and we can't fall over // in those cases. INT dN = edgeBuffer->ErrorDown; LONGLONG bigNumerator = Int32x32To64(dMOriginal, yClipTopInteger - edgeBuffer->StartY) + (edgeBuffer->Error + dN); if (bigNumerator >= 0) { QUOTIENT_REMAINDER_64_32(bigNumerator, dN, xDelta, error); } else { bigNumerator = -bigNumerator; QUOTIENT_REMAINDER_64_32(bigNumerator, dN, xDelta, error); xDelta = -xDelta; if (error != 0) { xDelta--; error = dN - error; } } // Update the edge data structure with the results: edgeBuffer->StartY = yClipTopInteger; edgeBuffer->X += xDelta; edgeBuffer->Error = error - dN; // Renormalize error } /**************************************************************************\ * * Function Description: * * Add edges to the edge list. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ BOOL InitializeEdges( VOID *context, POINT *pointArray, // Points to a 28.4 array of size 'vertexCount' // Note that we may modify the contents! INT vertexCount, PathEnumerateTermination lastSubpath // not used ) { INT xStart; INT yStart; INT yStartInteger; INT yEndInteger; INT dMOriginal; INT dM; INT dN; INT dX; INT errorUp; INT quotient; INT remainder; INT error; LONGLONG bigNumerator; INT littleNumerator; INT windingDirection; EpEdge *edgeBuffer; EpEdge *endEdge; INT bufferCount; INT yClipTopInteger; INT yClipTop; INT yClipBottom; INT xClipLeft; INT xClipRight; EpInitializeEdgesContext *edgeContext = static_cast(context); INT yMax = edgeContext->MaxY; EpEdgeStore *store = edgeContext->Store; RECT *clipRect = edgeContext->ClipRect; INT edgeCount = vertexCount - 1; ASSERT(edgeCount >= 1); if (clipRect == NULL) { yClipBottom = 0; yClipTopInteger = INT_MIN >> AA_Y_SHIFT; } else { yClipTopInteger = clipRect->top >> 4; yClipTop = clipRect->top; yClipBottom = clipRect->bottom; xClipLeft = clipRect->left; xClipRight = clipRect->right; ASSERT(yClipBottom > 0); ASSERT(yClipTop <= yClipBottom); } if (edgeContext->IsAntialias) { // If antialiasing, apply the supersampling scaling here before we // calculate the DDAs. We do this here and not in the Matrix // transform we give to FixedPointPathEnumerate mainly so that the // Bezier flattener can continue to operate in its optimal 28.4 // format. // // We also apply a half-pixel offset here so that the antialiasing // code can assume that the pixel centers are at half-pixel // coordinates, not on the integer coordinates. POINT *point = pointArray; INT i = vertexCount; do { point->x = (point->x + 8) << AA_X_SHIFT; point->y = (point->y + 8) << AA_Y_SHIFT; } while (point++, --i != 0); yClipTopInteger <<= AA_Y_SHIFT; yClipTop <<= AA_Y_SHIFT; yClipBottom <<= AA_Y_SHIFT; xClipLeft <<= AA_X_SHIFT; xClipRight <<= AA_X_SHIFT; } // Make 'yClipBottom' inclusive by subtracting off one pixel // (keeping in mind that we're in 28.4 device space): yClipBottom -= 16; // Warm up the store where we keep the edge data: store->StartAddBuffer(&edgeBuffer, &bufferCount); do { // Handle trivial rejection: if (yClipBottom >= 0) { // Throw out any edges that are above or below the clipping. // This has to be a precise check, because we assume later // on that every edge intersects in the vertical dimension // with the clip rectangle. That asssumption is made in two // places: // // 1. When we sort the edges, we assume either zero edges, // or two or more. // 2. When we start the DDAs, we assume either zero edges, // or that there's at least one scan of DDAs to output. // // Plus, of course, it's less efficient if we let things // through. // // Note that 'yClipBottom' is inclusive: BOOL clipHigh = ((pointArray)->y <= yClipTop) && ((pointArray + 1)->y <= yClipTop); BOOL clipLow = ((pointArray)->y > yClipBottom) && ((pointArray + 1)->y > yClipBottom); #if DBG { INT yRectTop, yRectBottom, y0, y1, yTop, yBottom; // Getting the trivial rejection code right is tricky. // So on checked builds let's verify that we're doing it // correctly, using a different approach: BOOL clipped = FALSE; if (clipRect != NULL) { yRectTop = clipRect->top >> 4; yRectBottom = clipRect->bottom >> 4; if (edgeContext->IsAntialias) { yRectTop <<= AA_Y_SHIFT; yRectBottom <<= AA_Y_SHIFT; } y0 = ((pointArray)->y + 15) >> 4; y1 = ((pointArray + 1)->y + 15) >> 4; yTop = min(y0, y1); yBottom = max(y0, y1); clipped = ((yTop >= yRectBottom) || (yBottom <= yRectTop)); } ASSERT(clipped == (clipHigh || clipLow)); } #endif if (clipHigh || clipLow) continue; // ======================> if (edgeCount > 1) { // Here we'll collapse two edges down to one if both are // to the left or to the right of the clipping rectangle. if (((pointArray)->x < xClipLeft) && ((pointArray + 1)->x < xClipLeft) && ((pointArray + 2)->x < xClipLeft)) { // Note this is one reason why 'pointArray' can't be 'const': *(pointArray + 1) = *(pointArray); continue; // ======================> } if (((pointArray)->x > xClipRight) && ((pointArray + 1)->x > xClipRight) && ((pointArray + 2)->x > xClipRight)) { // Note this is one reason why 'pointArray' can't be 'const': *(pointArray + 1) = *(pointArray); continue; // ======================> } } } dM = (pointArray + 1)->x - (pointArray)->x; dN = (pointArray + 1)->y - (pointArray)->y; if (dN >= 0) { // The vector points downward: xStart = (pointArray)->x; yStart = (pointArray)->y; yStartInteger = (yStart + 15) >> 4; yEndInteger = ((pointArray + 1)->y + 15) >> 4; windingDirection = 1; } else { // The vector points upward, so we have to essentially // 'swap' the end points: dN = -dN; dM = -dM; xStart = (pointArray + 1)->x; yStart = (pointArray + 1)->y; yStartInteger = (yStart + 15) >> 4; yEndInteger = ((pointArray)->y + 15) >> 4; windingDirection = -1; } // The edgeBuffer must span an integer y-value in order to be // added to the edgeBuffer list. This serves to get rid of // horizontal edges, which cause trouble for our divides. if (yEndInteger > yStartInteger) { yMax = max(yMax, yEndInteger); dMOriginal = dM; if (dM < 0) { dM = -dM; if (dM < dN) // Can't be '<=' { dX = -1; errorUp = dN - dM; } else { QUOTIENT_REMAINDER(dM, dN, quotient, remainder); dX = -quotient; errorUp = remainder; if (remainder > 0) { dX = -quotient - 1; errorUp = dN - remainder; } } } else { if (dM < dN) { dX = 0; errorUp = dM; } else { QUOTIENT_REMAINDER(dM, dN, quotient, remainder); dX = quotient; errorUp = remainder; } } error = -1; // Error is initially zero (add dN - 1 for // the ceiling, but subtract off dN so that // we can check the sign instead of comparing // to dN) if ((yStart & 15) != 0) { // Advance to the next integer y coordinate for (INT i = 16 - (yStart & 15); i != 0; i--) { xStart += dX; error += errorUp; if (error >= 0) { error -= dN; xStart++; } } } if ((xStart & 15) != 0) { error -= dN * (16 - (xStart & 15)); xStart += 15; // We'll want the ceiling in just a bit... } xStart >>= 4; error >>= 4; if (bufferCount == 0) { if (!store->NextAddBuffer(&edgeBuffer, &bufferCount)) return(FALSE); } edgeBuffer->X = xStart; edgeBuffer->Dx = dX; edgeBuffer->Error = error; edgeBuffer->ErrorUp = errorUp; edgeBuffer->ErrorDown = dN; edgeBuffer->WindingDirection = windingDirection; edgeBuffer->StartY = yStartInteger; edgeBuffer->EndY = yEndInteger; // Exclusive of end // Here we handle the case where the edge starts above the // clipping rectangle, and we need to jump down in the 'y' // direction to the first unclipped scan-line. // // Consequently, we advance the DDA here: if (yClipTopInteger > yStartInteger) { ASSERT(edgeBuffer->EndY > yClipTopInteger); ClipEdge(edgeBuffer, yClipTopInteger, dMOriginal); } // Advance to handle the next edge: edgeBuffer++; bufferCount--; } } while (pointArray++, --edgeCount != 0); // We're done with this batch. Let the store know how many edges // we ended up with: store->EndAddBuffer(edgeBuffer, bufferCount); edgeContext->MaxY = yMax; return(TRUE); } /**************************************************************************\ * * Function Description: * * Returns TRUE if the line from point[1] to point[2] turns 'left' * from the line from point[0] to point[1]. Uses the sign of the * cross product. * * Remember that we're in device space, where positive 'y' is down! * * Created: * * 04/09/2000 andrewgo * \**************************************************************************/ inline BOOL TurnLeft( const POINT *points ) { LONGLONG ad = Int32x32To64(points[1].x - points[0].x, points[2].y - points[1].y); LONGLONG bc = Int32x32To64(points[1].y - points[0].y, points[2].x - points[1].x); return(ad < bc); } /**************************************************************************\ * * Function Description: * * Computes the index of the NominalDrawVertex table to be use as the * drawing vertex. The result is numbered such that a traversal using * an increasing pointer will go counter-clockwise around the pen. * * Created: * * 04/09/2000 andrewgo * \**************************************************************************/ POINT NominalDrawVertex[] = { // Don't forget that in device space, positive 'y' is down: {0, -8}, {-8, 0}, {0, 8}, {8, 0} }; INT OctantToDrawVertexTranslate[] = { 0, 2, 0, 2, 3, 3, 1, 1 }; inline INT ComputeDrawVertex( const POINT* points ) { INT dx = points[1].x - points[0].x; INT dy = points[1].y - points[0].y; INT octant = 0; if (dx < 0) { octant |= 1; dx = -dx; } if (dy < 0) { octant |= 2; dy = -dy; } if (dy > dx) { octant |= 4; } return(OctantToDrawVertexTranslate[octant]); } /**************************************************************************\ * * Function Description: * * Routine for nominal-width lines that generates a fast outline to * be filled by the fill code. * * The resulting fill must always be done in winding mode. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ BOOL InitializeNominal( VOID *context, POINT *pointArray, // Points to a 28.4 array of size 'vertexCount' // Note that we may modify the contents! INT vertexCount, PathEnumerateTermination lastSubpath ) { POINT leftBuffer[NOMINAL_FILL_POINT_NUMBER]; POINT rightBuffer[NOMINAL_FILL_POINT_NUMBER]; POINT lastPoint; INT iDraw; INT iNewDraw; INT xStart; INT yStart; INT xEnd; INT yEnd; INT xPerp; INT yPerp; BOOL isLeftTurn; INT iNext; POINT *rightStartPlus3 = rightBuffer + 3; POINT *rightEnd = rightBuffer + NOMINAL_FILL_POINT_NUMBER; POINT *leftStart = leftBuffer; POINT *leftEndMinus3 = leftBuffer + NOMINAL_FILL_POINT_NUMBER - 3; POINT *left = leftStart; POINT *right = rightEnd; INT lineCount = vertexCount - 1; iDraw = ComputeDrawVertex(pointArray); // Add start cap: xStart = (pointArray)->x; yStart = (pointArray)->y; (left)->x = xStart - NominalDrawVertex[iDraw].x; (left)->y = yStart - NominalDrawVertex[iDraw].y; (left + 1)->x = xStart + NominalDrawVertex[(iDraw + 1) & 3].x; (left + 1)->y = yStart + NominalDrawVertex[(iDraw + 1) & 3].y; left += 2; while (TRUE) { if (left >= leftEndMinus3) { lastPoint = *(left - 1); if (!InitializeEdges(context, leftBuffer, static_cast(left - leftBuffer), lastSubpath)) { return(FALSE); } *(leftStart) = lastPoint; left = leftStart + 1; } if (right < rightStartPlus3) { lastPoint = *right; if (!InitializeEdges(context, right, static_cast(rightEnd - right), lastSubpath)) { return(FALSE); } *(rightEnd - 1) = lastPoint; right = rightEnd - 1; } xStart = (pointArray)->x; yStart = (pointArray)->y; xEnd = (pointArray + 1)->x; yEnd = (pointArray + 1)->y; xPerp = NominalDrawVertex[iDraw].x; yPerp = NominalDrawVertex[iDraw].y; (left)->x = xStart + xPerp; (left)->y = yStart + yPerp; (right - 1)->x = xStart - xPerp; (right - 1)->y = yStart - yPerp; (left + 1)->x = xEnd + xPerp; (left + 1)->y = yEnd + yPerp; (right - 2)->x = xEnd - xPerp; (right - 2)->y = yEnd - yPerp; left += 2; right -= 2; pointArray++; if (--lineCount == 0) break; // Darn, we have to handle a join: iNewDraw = ComputeDrawVertex(pointArray); if (iNewDraw != iDraw) { isLeftTurn = TurnLeft(pointArray - 1); if (isLeftTurn) { iNext = (iDraw + 1) & 3; if (iNewDraw != iNext) { (right - 1)->x = xEnd - NominalDrawVertex[iNext].x; (right - 1)->y = yEnd - NominalDrawVertex[iNext].y; right--; } (left)->x = xEnd; (left)->y = yEnd; left++; } else // We're turning 'right': { iNext = (iDraw - 1) & 3; if (iNewDraw != iNext) { (left)->x = xEnd + NominalDrawVertex[iNext].x; (left)->y = yEnd + NominalDrawVertex[iNext].y; left++; } (right - 1)->x = xEnd; (right - 1)->y = yEnd; right--; } } ASSERT(left <= &leftBuffer[NOMINAL_FILL_POINT_NUMBER]); ASSERT(right >= &rightBuffer[0]); iDraw = iNewDraw; } // Add end cap: if (left >= leftEndMinus3) { lastPoint = *(left - 1); if (!InitializeEdges(context, leftBuffer, static_cast(left - leftBuffer), lastSubpath)) { return(FALSE); } *(leftStart) = lastPoint; left = leftStart + 1; } xStart = (pointArray)->x; yStart = (pointArray)->y; (left)->x = xStart + NominalDrawVertex[(iDraw - 1) & 3].x; (left)->y = yStart + NominalDrawVertex[(iDraw - 1) & 3].y; (left + 1)->x = xStart - NominalDrawVertex[iDraw].x; (left + 1)->y = yStart - NominalDrawVertex[iDraw].y; left += 2; // Flush the left batch. Since we just added an end cap, we // know there's more than one vertex in there: if (!InitializeEdges(context, leftBuffer, static_cast(left - leftBuffer), lastSubpath)) { return(FALSE); } // Don't flush the right buffer if there's only one point in there, // because one point doesn't make an edge. INT count = static_cast(rightEnd - right); if (count > 1) { if (!InitializeEdges(context, right, count, lastSubpath)) { return(FALSE); } } return(TRUE); } /**************************************************************************\ * * Function Description: * * Does complete parameter checking on the 'types' array of a path. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ BOOL ValidatePathTypes( const BYTE *typesArray, INT count ) { BYTE type; const BYTE *types = typesArray; if (count == 0) return(TRUE); while (TRUE) { // The first point in every subpath has to be an unadorned // 'start' point: if ((*types & PathPointTypePathTypeMask) != PathPointTypeStart) { WARNING(("Bad subpath start")); return(FALSE); } // Advance to the first point after the 'start' point: types++; if (--count == 0) { WARNING(("Path ended after start-path")); return(FALSE); } if ((*types & PathPointTypePathTypeMask) == PathPointTypeStart) { WARNING(("Can't have a start followed by a start!")); return(FALSE); } // Swallow up runs of lines and Bezier curves: do { switch(*types & PathPointTypePathTypeMask) { case PathPointTypeLine: types++; if (--count == 0) return(TRUE); break; case PathPointTypeBezier: if(count < 3) { WARNING(("Path ended before multiple of 3 Bezier points")); return(FALSE); } if((*types & PathPointTypePathTypeMask) != PathPointTypeBezier) { WARNING(("Can't have a close on the first Bezier vertex")); return(FALSE); } if((*(types + 1) & PathPointTypePathTypeMask) != PathPointTypeBezier) { WARNING(("Expected plain Bezier control point for 3rd vertex")); return(FALSE); } if((*(types + 2) & PathPointTypePathTypeMask) != PathPointTypeBezier) { WARNING(("Expected Bezier control point for 4th vertex")); return(FALSE); } types += 3; if ((count -= 3) == 0) return(TRUE); break; default: WARNING(("Illegal type")); return(FALSE); } // A close-subpath marker or a start-subpath marker marks the // end of a subpath: } while (!(*(types - 1) & PathPointTypeCloseSubpath) && ((*types & PathPointTypePathTypeMask) != PathPointTypeStart)); } return(TRUE); } /**************************************************************************\ * * Function Description: * * Some debug code for verifying the path. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ VOID AssertPath( const DpPath *path ) { // Make sure that the 'types' array is well-formed, otherwise we // may fall over in the FixedPointPathEnumerate function. // // NOTE: If you hit this assert, DO NOT SIMPLY COMMENT THIS ASSERT OUT! // // Instead, fix the ValidatePathTypes code if it's letting through // valid paths, or (more likely) fix the code that's letting bogus // paths through. The FixedPointPathEnumerate routine has some // subtle assumptions that require the path to be perfectly valid! // // No internal code should be producing invalid paths, and all // paths created by the application must be parameter checked! ASSERT(ValidatePathTypes(path->GetPathTypes(), path->GetPointCount())); } /**************************************************************************\ * * Function Description: * * Enumerate the path. * * NOTE: The 'enumerateFunction' function is allowed to modify the * contents of our call-back buffer! (This is mainly done * to allow 'InitializeEdges' to be simpler for some clipping trivial * rejection cases.) * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ BOOL FixedPointPathEnumerate( const DpPath *path, const GpMatrix *matrix, const RECT *clipRect, // In scaled 28.4 format PathEnumerateType enumType, // Fill, stroke or for flattening. FIXEDPOINTPATHENUMERATEFUNCTION enumerateFunction, VOID *enumerateContext ) { POINT bufferStart[ENUMERATE_BUFFER_NUMBER]; POINT bezierBuffer[4]; POINT *buffer; INT bufferSize; POINT startFigure; INT iStart; INT iEnd; INT runSize; INT thisCount; BOOL isMore; RECT scaledClip; INT xLast; INT yLast; ASSERTPATH(path); INT pathCount = path->GetPointCount(); const PointF *pathPoint = path->GetPathPoints(); const BYTE *pathType = path->GetPathTypes(); // Every valid subpath has at least two vertices in it, hence the // check of 'pathCount - 1': iStart = 0; while (iStart < pathCount - 1) { ASSERT((pathType[iStart] & PathPointTypePathTypeMask) == PathPointTypeStart); ASSERT((pathType[iStart + 1] & PathPointTypePathTypeMask) != PathPointTypeStart); // Add the start point to the beginning of the batch, and // remember it for handling the close figure: matrix->Transform(&pathPoint[iStart], &startFigure, 1); bufferStart[0].x = startFigure.x; bufferStart[0].y = startFigure.y; buffer = bufferStart + 1; bufferSize = ENUMERATE_BUFFER_NUMBER - 1; // We need to enter our loop with 'iStart' pointing one past // the start figure: iStart++; do { // Try finding a run of lines: if ((pathType[iStart] & PathPointTypePathTypeMask) == PathPointTypeLine) { iEnd = iStart + 1; while ((iEnd < pathCount) && ((pathType[iEnd] & PathPointTypePathTypeMask) == PathPointTypeLine)) { iEnd++; } // Okay, we've found a run of lines. Break it up into our // buffer size: runSize = (iEnd - iStart); do { thisCount = min(bufferSize, runSize); matrix->Transform(&pathPoint[iStart], buffer, thisCount); iStart += thisCount; buffer += thisCount; runSize -= thisCount; bufferSize -= thisCount; if (bufferSize > 0) break; xLast = bufferStart[ENUMERATE_BUFFER_NUMBER - 1].x; yLast = bufferStart[ENUMERATE_BUFFER_NUMBER - 1].y; if (!(enumerateFunction)( enumerateContext, bufferStart, ENUMERATE_BUFFER_NUMBER, PathEnumerateContinue )) { return(FALSE); } // Continue the last vertex as the first in the new batch: bufferStart[0].x = xLast; bufferStart[0].y = yLast; buffer = bufferStart + 1; bufferSize = ENUMERATE_BUFFER_NUMBER - 1; } while (runSize != 0); } else { ASSERT(iStart + 3 <= pathCount); ASSERT((pathType[iStart] & PathPointTypePathTypeMask) == PathPointTypeBezier); ASSERT((pathType[iStart + 1] & PathPointTypePathTypeMask) == PathPointTypeBezier); ASSERT((pathType[iStart + 2] & PathPointTypePathTypeMask) == PathPointTypeBezier); matrix->Transform(&pathPoint[iStart - 1], bezierBuffer, 4); // Prepare for the next iteration: iStart += 3; // Crack that Bezier: BEZIER bezier(bezierBuffer, clipRect); do { thisCount = bezier.Flatten(buffer, bufferSize, &isMore); buffer += thisCount; bufferSize -= thisCount; if (bufferSize > 0) break; xLast = bufferStart[ENUMERATE_BUFFER_NUMBER - 1].x; yLast = bufferStart[ENUMERATE_BUFFER_NUMBER - 1].y; if (!(enumerateFunction)( enumerateContext, bufferStart, ENUMERATE_BUFFER_NUMBER, PathEnumerateContinue )) { return(FALSE); } // Continue the last vertex as the first in the new batch: bufferStart[0].x = xLast; bufferStart[0].y = yLast; buffer = bufferStart + 1; bufferSize = ENUMERATE_BUFFER_NUMBER - 1; } while (isMore); } } while ((iStart < pathCount) && ((pathType[iStart] & PathPointTypePathTypeMask) != PathPointTypeStart)); // Okay, the subpath is done. But we still have to handle the // 'close figure' (which is implicit for a fill): bool isClosed = ( (enumType == PathEnumerateTypeFill) || (pathType[iStart - 1] & PathPointTypeCloseSubpath)); if (isClosed) { // Add the close-figure point: buffer->x = startFigure.x; buffer->y = startFigure.y; bufferSize--; } // We have to flush anything we might have in the batch, unless // there's only one vertex in there! (The latter case may happen // for the stroke case with no close figure if we just flushed a // batch.) // If we're flattening, we must call the one additional time to // correctly handle closing the subpath, even if there is only // one entry in the batch. The flattening callback handles the // one point case and closes the subpath properly without adding // extraneous points. INT verticesInBatch = ENUMERATE_BUFFER_NUMBER - bufferSize; if ((verticesInBatch > 1) || (enumType == PathEnumerateTypeFlatten)) { // because we always exit the above loops if the buffer contains // some data, and if it contains nothing, we add a final element, // verticesInBatch should always be at least one. // If we're flattening, we will sometimes enter here with // verticesInBatch==1, but it should never be zero or less. ASSERT(verticesInBatch >= 1); if (!(enumerateFunction)( enumerateContext, bufferStart, verticesInBatch, isClosed ? PathEnumerateCloseSubpath : PathEnumerateEndSubpath )) { return(FALSE); } } } return(TRUE); } /**************************************************************************\ * * Function Description: * * We want to sort in the inactive list; the primary key is 'y', and * the secondary key is 'x'. This routine creates a single LONGLONG * key that represents both. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ inline VOID YX(INT x, INT y, LONGLONG *p) { // Bias 'x' by INT_MAX so that it's effectively unsigned: reinterpret_cast(p)->HighPart = y; reinterpret_cast(p)->LowPart = x + INT_MAX; } /**************************************************************************\ * * Function Description: * * Recursive function to quick-sort our inactive edge list. Note that * for performance, the results are not completely sorted; an insertion * sort has to be run after the quicksort in order to do a lighter-weight * sort of the subtables. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ #define QUICKSORT_THRESHOLD 8 VOID QuickSortEdges( EpInactiveEdge *f, EpInactiveEdge *l ) { EpEdge *e; LONGLONG y; LONGLONG first; LONGLONG second; LONGLONG last; // Find the median of the first, middle, and last elements: EpInactiveEdge *m = f + ((l - f) >> 1); SWAP(y, (f + 1)->Yx, m->Yx); SWAP(e, (f + 1)->Edge, m->Edge); if ((second = (f + 1)->Yx) > (last = l->Yx)) { (f + 1)->Yx = last; l->Yx = second; SWAP(e, (f + 1)->Edge, l->Edge); } if ((first = f->Yx) > (last = l->Yx)) { f->Yx = last; l->Yx = first; SWAP(e, f->Edge, l->Edge); } if ((second = (f + 1)->Yx) > (first = f->Yx)) { (f + 1)->Yx = first; f->Yx = second; SWAP(e, (f + 1)->Edge, f->Edge); } // f->Yx is now the desired median, and (f + 1)->Yx <= f->Yx <= l->Yx ASSERT(((f + 1)->Yx <= f->Yx) && (f->Yx <= l->Yx)); LONGLONG median = f->Yx; EpInactiveEdge *i = f + 2; while (i->Yx < median) i++; EpInactiveEdge *j = l - 1; while (j->Yx > median) j--; while (i < j) { SWAP(y, i->Yx, j->Yx); SWAP(e, i->Edge, j->Edge); do { i++; } while (i->Yx < median); do { j--; } while (j->Yx > median); } SWAP(y, f->Yx, j->Yx); SWAP(e, f->Edge, j->Edge); size_t a = j - f; size_t b = l - j; // Use less stack space by recursing on the shorter subtable. Also, // have the less-overhead insertion-sort handle small subtables. if (a <= b) { if (a > QUICKSORT_THRESHOLD) { // 'a' is the smallest, so do it first: QuickSortEdges(f, j - 1); QuickSortEdges(j + 1, l); } else if (b > QUICKSORT_THRESHOLD) { QuickSortEdges(j + 1, l); } } else { if (b > QUICKSORT_THRESHOLD) { // 'b' is the smallest, so do it first: QuickSortEdges(j + 1, l); QuickSortEdges(f, j - 1); } else if (a > QUICKSORT_THRESHOLD) { QuickSortEdges(f, j- 1); } } } /**************************************************************************\ * * Function Description: * * Do a sort of the inactive table using an insertion-sort. Expects * large tables to have already been sorted via quick-sort. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ VOID FASTCALL InsertionSortEdges( EpInactiveEdge *inactive, INT count ) { EpInactiveEdge *p; EpEdge *e; LONGLONG y; LONGLONG yPrevious; ASSERT((inactive - 1)->Yx == _I64_MIN); ASSERT(count >= 2); inactive++; // Skip first entry (by definition it's already in order!) count--; do { p = inactive; // Copy the current stuff to temporary variables to make a hole: e = inactive->Edge; y = inactive->Yx; // Shift everything one slot to the right (effectively moving // the hole one position to the left): while (y < (yPrevious = (p - 1)->Yx)) { p->Yx = yPrevious; p->Edge = (p - 1)->Edge; p--; } // Drop the temporary stuff into the final hole: p->Yx = y; p->Edge = e; // The quicksort should have ensured that we don't have to move // any entry terribly far: ASSERT(inactive - p <= QUICKSORT_THRESHOLD); } while (inactive++, --count != 0); } /**************************************************************************\ * * Function Description: * * Assert the state of the inactive array. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ VOID AssertInactiveArray( EpInactiveEdge *inactive, INT count ) { // Verify the head: ASSERT((inactive - 1)->Yx == _I64_MIN); ASSERT(inactive->Yx != _I64_MIN); do { LONGLONG yx; YX(inactive->Edge->X, inactive->Edge->StartY, &yx); ASSERT(inactive->Yx == yx); ASSERT(inactive->Yx >= (inactive - 1)->Yx); } while (inactive++, --count != 0); // Verify that the tail is setup appropriately: ASSERT(inactive->Edge->StartY == INT_MAX); } /**************************************************************************\ * * Function Description: * * Initialize and sort the inactive array. * * Returns: * * 'y' value of topmost edge. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ INT InitializeInactiveArray( EpEdgeStore *edgeStore, EpInactiveEdge *inactiveArray, // 'inactiveArray' must be at least INT count, // 'count + 2' elements in size! EpEdge *tailEdge // Tail sentinel for inactive list ) { BOOL isMore; EpEdge *activeEdge; EpEdge *activeEdgeEnd; INT i; INT j; EpEdge *e; INT y; // First initialize the inactive array. Skip the first entry, // which we reserve as a head sentinel for the insertion sort: EpInactiveEdge *inactiveEdge = inactiveArray + 1; do { isMore = edgeStore->Enumerate(&activeEdge, &activeEdgeEnd); while (activeEdge != activeEdgeEnd) { inactiveEdge->Edge = activeEdge; YX(activeEdge->X, activeEdge->StartY, &inactiveEdge->Yx); inactiveEdge++; activeEdge++; } } while (isMore); ASSERT(inactiveEdge - inactiveArray == count + 1); // Add the tail, which is used when reading back the array. This // is why we had to allocate the array as 'count + 1': inactiveEdge->Edge = tailEdge; // Add the head, which is used for the insertion sort. This is why // we had to allocate the array as 'count + 2': inactiveArray->Yx = _I64_MIN; // Only invoke the quicksort routine if it's worth the overhead: if (count > QUICKSORT_THRESHOLD) { // Quick-sort this, skipping the first and last elements, // which are sentinels. // // We do 'inactiveArray + count' to be inclusive of the last // element: QuickSortEdges(inactiveArray + 1, inactiveArray + count); } // Do a quick sort to handle the mostly sorted result: InsertionSortEdges(inactiveArray + 1, count); ASSERTINACTIVEARRAY(inactiveArray + 1, count); // Return the 'y' value of the topmost edge: return(inactiveArray[1].Edge->StartY); } /**************************************************************************\ * * Function Description: * * Insert edges into the active edge list. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ VOID InsertNewEdges( EpEdge *activeList, INT yCurrent, EpInactiveEdge **inactiveEdge, // In/Out INT *yNextInactive // Out, will be INT_MAX when no more ) { EpInactiveEdge *inactive = *inactiveEdge; ASSERT(inactive->Edge->StartY == yCurrent); do { EpEdge *newActive = inactive->Edge; // The activeList edge list sentinel is INT_MAX, so this always // terminates: while (activeList->Next->X < newActive->X) activeList = activeList->Next; newActive->Next = activeList->Next; activeList->Next = newActive; inactive++; } while (inactive->Edge->StartY == yCurrent); *yNextInactive = inactive->Edge->StartY; *inactiveEdge = inactive; } /**************************************************************************\ * * Function Description: * * Sort the edges so that they're in ascending 'x' order. * * We use a bubble-sort for this stage, because edges maintain good * locality and don't often switch ordering positions. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ VOID FASTCALL SortActiveEdges( EpEdge *list ) { BOOL swapOccurred; EpEdge *tmp; // We should never be called with an empty active edge list: ASSERT(list->Next->X != INT_MAX); do { swapOccurred = FALSE; EpEdge *previous = list; EpEdge *current = list->Next; EpEdge *next = current->Next; INT nextX = next->X; do { if (nextX < current->X) { swapOccurred = TRUE; previous->Next = next; current->Next = next->Next; next->Next = current; SWAP(tmp, next, current); } previous = current; current = next; next = next->Next; } while ((nextX = next->X) != INT_MAX); } while (swapOccurred); } /**************************************************************************\ * * Function Description: * * For each scan-line to be filled: * * 1. Remove any stale edges from the active edge list * 2. Insert into the active edge list any edges new to this scan-line * 3. Advance the DDAs of every active edge * 4. If any active edges are out of order, re-sort the active edge list * 5. Now that the active edges are ready for this scan, call the filler * to traverse the edges and output the spans appropriately * 6. Lather, rinse, and repeat * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ VOID RasterizeEdges( EpEdge *activeList, EpInactiveEdge *inactiveArray, INT yCurrent, INT yBottom, EpFiller *filler, EpFillerFunction fillerFunction ) { INT yNextInactive; InsertNewEdges(activeList, yCurrent, &inactiveArray, &yNextInactive); ASSERTACTIVELIST(activeList, yCurrent); (filler->*fillerFunction)(activeList, yCurrent); while (++yCurrent < yBottom) { EpEdge *previous = activeList; EpEdge *current = activeList->Next; INT outOfOrderCount = 0; while (TRUE) { if (current->EndY <= yCurrent) { // If we've hit the sentinel, our work here is done: if (current->EndY == INT_MIN) break; // ============> // This edge is stale, remove it from the list: current = current->Next; previous->Next = current; continue; // ============> } // Advance the DDA: current->X += current->Dx; current->Error += current->ErrorUp; if (current->Error >= 0) { current->Error -= current->ErrorDown; current->X++; } // Is this entry out-of-order with respect to the previous one? outOfOrderCount += (previous->X > current->X); // Advance: previous = current; current = current->Next; } // It turns out that having any out-of-order edges at this point // is extremely rare in practice, so only call the bubble-sort // if it's truly needed. // // NOTE: If you're looking at this code trying to fix a bug where // the edges are out of order when the filler is called, do // NOT simply change the code to always do the bubble-sort! // Instead, figure out what caused our 'outOfOrder' logic // above to get messed up. if (outOfOrderCount) { SortActiveEdges(activeList); } ASSERTACTIVELISTORDER(activeList); if (yCurrent == yNextInactive) { InsertNewEdges(activeList, yCurrent, &inactiveArray, &yNextInactive); } ASSERTACTIVELIST(activeList, yCurrent); // Do the appropriate alternate or winding, supersampled or // non-supersampled fill: (filler->*fillerFunction)(activeList, yCurrent); } } /**************************************************************************\ * * Function Description: * * Fill (or sometimes stroke) that path. * * Created: * * 03/25/2000 andrewgo * \**************************************************************************/ GpStatus RasterizePath( const DpPath *path, GpMatrix *worldTransform, GpFillMode fillMode, BOOL antiAlias, BOOL nominalWideLine, DpOutputSpan *output, DpClipRegion *clipper, const GpRect *drawBounds ) { EpInactiveEdge inactiveArrayStack[INACTIVE_LIST_NUMBER]; EpInactiveEdge *inactiveArray; EpInactiveEdge *inactiveArrayAllocation; EpEdge headEdge; EpEdge tailEdge; EpEdge *activeList; RECT clipBounds; GpRect clipRect; EpEdgeStore edgeStore; EpInitializeEdgesContext edgeContext; INT yClipBottom; inactiveArrayAllocation = NULL; edgeContext.ClipRect = NULL; tailEdge.X = INT_MAX; // Terminator to active list tailEdge.StartY = INT_MAX; // Terminator to inactive list tailEdge.EndY = INT_MIN; headEdge.X = INT_MIN; // Beginning of active list edgeContext.MaxY = INT_MIN; headEdge.Next = &tailEdge; activeList = &headEdge; edgeContext.Store = &edgeStore; edgeContext.IsAntialias = antiAlias; ////////////////////////////////////////////////////////////////////////// DpRegion::Visibility visibility = clipper->GetRectVisibility( drawBounds->X, drawBounds->Y, drawBounds->X + drawBounds->Width, drawBounds->Y + drawBounds->Height); if (visibility != DpRegion::TotallyVisible) { clipper->GetBounds(&clipRect); // !!![andrewgo] Don, why do we have to do an 'Invisible' test // here? Shouldn't GetRectVisibility have already // taken care of that case? (GetRectVisibility // was checked by our caller.) // Early-out if we're invisible, or if the bounds would overflow // our DDA calculations (which would cause us to crash). We // leave 4 bits for the 28.4 fixed point, plus enough bits for // the antialiasing supersampling. We also need a bit for doing // a signed difference without overflowing. if ((visibility == DpRegion::Invisible) || (clipRect.X < (INT_MIN >> (5 + AA_X_SHIFT))) || (clipRect.Y < (INT_MIN >> (5 + AA_Y_SHIFT))) || (clipRect.X > (INT_MAX >> (5 + AA_X_SHIFT))) || (clipRect.Y > (INT_MAX >> (5 + AA_Y_SHIFT))) || (clipRect.Width > (INT_MAX >> (5 + AA_X_SHIFT))) || (clipRect.Height > (INT_MAX >> (5 + AA_Y_SHIFT)))) { return(Ok); } // Scale the clip bounds rectangle by 16 to account for our // scaling to 28.4 coordinates: clipBounds.left = clipRect.GetLeft() * 16; clipBounds.top = clipRect.GetTop() * 16; clipBounds.right = clipRect.GetRight() * 16; clipBounds.bottom = clipRect.GetBottom() * 16; yClipBottom = clipRect.GetBottom(); edgeContext.ClipRect = &clipBounds; } ////////////////////////////////////////////////////////////////////////// // Convert all our points to 28.4 fixed point: GpMatrix matrix(*worldTransform); matrix.AppendScale(TOREAL(16), TOREAL(16)); // Enumerate the path and construct the edge table: FIXEDPOINTPATHENUMERATEFUNCTION pathEnumerationFunction = InitializeEdges; PathEnumerateType enumType = PathEnumerateTypeFill; if (nominalWideLine) { // The nominal-width wideline code always produces edges that // require a winding-mode fill: pathEnumerationFunction = InitializeNominal; fillMode = FillModeWinding; enumType = PathEnumerateTypeStroke; // nominal width is a stroke. } if (!FixedPointPathEnumerate( path, &matrix, edgeContext.ClipRect, enumType, pathEnumerationFunction, &edgeContext )) { return(OutOfMemory); } INT totalCount = edgeStore.StartEnumeration(); if (totalCount == 0) return(Ok); // We're outta here (empty path or entirely clipped) // At this point, there has to be at least two edges. If there's only // one, it means that we didn't do the trivially rejection properly. ASSERT(totalCount >= 2); inactiveArray = &inactiveArrayStack[0]; if (totalCount > (INACTIVE_LIST_NUMBER - 2)) { inactiveArrayAllocation = static_cast (GpMalloc(sizeof(EpInactiveEdge) * (totalCount + 2))); if (inactiveArrayAllocation == NULL) return(OutOfMemory); inactiveArray = inactiveArrayAllocation; } // Initialize and sort the inactive array: INT yCurrent = InitializeInactiveArray(&edgeStore, inactiveArray, totalCount, &tailEdge); INT yBottom = edgeContext.MaxY; ASSERT(yBottom > 0); // Skip the head sentinel on the inactive array: inactiveArray++; if (antiAlias) { EpAntialiasedFiller filler(output); EpFillerFunction fillerFunction = (fillMode == FillModeAlternate) ? (EpFillerFunction) (EpAntialiasedFiller::FillEdgesAlternate) : (EpFillerFunction) (EpAntialiasedFiller::FillEdgesWinding); if (edgeContext.ClipRect) { filler.SetClipper(clipper); // The clipper has to call back to EpFillerFunction::OutputSpan // to do the output, and then *we* call output->OutputSpan. clipper->InitClipping(&filler, drawBounds->Y); // 'yClipBottom' is in 28.4 format, and has to be converted // to the 30.2 format we use for antialiasing: yBottom = min(yBottom, yClipBottom << AA_Y_SHIFT); // 'totalCount' should have been zero if all the edges were // clipped out (RasterizeEdges assumes there's at least one edge // to be drawn): ASSERT(yBottom > yCurrent); } RasterizeEdges(activeList, inactiveArray, yCurrent, yBottom, &filler, fillerFunction); } else { EpAliasedFiller filler(output); EpFillerFunction fillerFunction = (fillMode == FillModeAlternate) ? (EpFillerFunction) (EpAliasedFiller::FillEdgesAlternate) : (EpFillerFunction) (EpAliasedFiller::FillEdgesWinding); if (edgeContext.ClipRect) { // The clipper calls output->OutputSpan directly. We just have // to remember to call clipper->OutputSpan instead of // output->OutputSpan: filler.SetOutputSpan(clipper); clipper->InitClipping(output, drawBounds->Y); yBottom = min(yBottom, yClipBottom); // 'totalCount' should have been zero if all the edges were // clipped out (RasterizeEdges assumes there's at least one edge // to be drawn): ASSERT(yBottom > yCurrent); } RasterizeEdges(activeList, inactiveArray, yCurrent, yBottom, &filler, fillerFunction); } // Free any objects and get outta here: if (inactiveArrayAllocation != NULL) GpFree(inactiveArrayAllocation); return(Ok); }