/* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS Win32k subsystem * PURPOSE: Various Polygon Filling routines for Polygon() * FILE: win32ss/gdi/ntgdi/polyfill.c * PROGRAMER: Mark Tempel */ #include #define NDEBUG #include #define FILL_EDGE_ALLOC_TAG 0x45465044 /* ** This struct is used for book keeping during polygon filling routines. */ typedef struct _tagFILL_EDGE { /* Basic line information */ int FromX; int FromY; int ToX; int ToY; int dx; int dy; int absdx, absdy; int x, y; int xmajor; /* Active Edge List information */ int XIntercept[2]; int Error; int ErrorMax; int XDirection, YDirection; /* The next edge in the active Edge List */ struct _tagFILL_EDGE * pNext; } FILL_EDGE; typedef struct _FILL_EDGE_LIST { int Count; FILL_EDGE** Edges; } FILL_EDGE_LIST; #if 0 static void DEBUG_PRINT_ACTIVE_EDGELIST ( FILL_EDGE* list ) { FILL_EDGE* pThis = list; if (0 == list) { DPRINT1("List is NULL\n"); return; } while(0 != pThis) { //DPRINT1("EDGE: (%d, %d) to (%d, %d)\n", pThis->FromX, pThis->FromY, pThis->ToX, pThis->ToY); DPRINT1("EDGE: [%d,%d]\n", pThis->XIntercept[0], pThis->XIntercept[1] ); pThis = pThis->pNext; } } #else #define DEBUG_PRINT_ACTIVE_EDGELIST(x) #endif /* ** Hide memory clean up. */ static void FASTCALL POLYGONFILL_DestroyEdgeList(FILL_EDGE_LIST* list) { int i; if (list) { if (list->Edges) { for (i = 0; i < list->Count; i++) { _PRAGMA_WARNING_SUPPRESS(__WARNING_USING_UNINIT_VAR) if (list->Edges[i]) EngFreeMem(list->Edges[i]); } EngFreeMem(list->Edges); } EngFreeMem(list); } } /* ** This makes and initializes an Edge struct for a line between two points. */ static FILL_EDGE* FASTCALL POLYGONFILL_MakeEdge(POINT From, POINT To) { FILL_EDGE* rc = (FILL_EDGE*)EngAllocMem(FL_ZERO_MEMORY, sizeof(FILL_EDGE), FILL_EDGE_ALLOC_TAG); if (0 == rc) return NULL; //DPRINT1("Making Edge: (%d, %d) to (%d, %d)\n", From.x, From.y, To.x, To.y); // Now fill the struct. if ( To.y < From.y ) { rc->FromX = To.x; rc->FromY = To.y; rc->ToX = From.x; rc->ToY = From.y; rc->YDirection = -1; // Lines that go up get walked backwards, so need to be offset // by -1 in order to make the walk identically on a pixel-level rc->Error = -1; } else { rc->FromX = From.x; rc->FromY = From.y; rc->ToX = To.x; rc->ToY = To.y; rc->YDirection = 1; rc->Error = 0; } rc->x = rc->FromX; rc->y = rc->FromY; rc->dx = rc->ToX - rc->FromX; rc->dy = rc->ToY - rc->FromY; rc->absdx = abs(rc->dx); rc->absdy = abs(rc->dy); rc->xmajor = rc->absdx > rc->absdy; rc->ErrorMax = max(rc->absdx,rc->absdy); rc->Error += rc->ErrorMax / 2; rc->XDirection = (rc->dx < 0)?(-1):(1); rc->pNext = 0; //DPRINT("MakeEdge (%i,%i)->(%i,%i) d=(%i,%i) dir=(%i,%i) err=%i max=%i\n", // From.x, From.y, To.x, To.y, rc->dx, rc->dy, rc->XDirection, rc->YDirection, rc->Error, rc->ErrorMax ); return rc; } /* ** My Edge comparison routine. ** This is for scan converting polygon fill. ** First sort by MinY, then Minx, then slope. ** ** This comparison will help us determine which ** lines will become active first when scanning from ** top (min y) to bottom (max y). ** ** Return Value Meaning ** Negative integer element1 < element2 ** Zero element1 = element2 ** Positive integer element1 > element2 */ static INT FASTCALL FILL_EDGE_Compare(FILL_EDGE* Edge1, FILL_EDGE* Edge2) { int e1 = Edge1->XIntercept[0] + Edge1->XIntercept[1]; int e2 = Edge2->XIntercept[0] + Edge2->XIntercept[1]; return e1 - e2; } /* ** Insert an edge into a list keeping the list in order. */ static void FASTCALL POLYGONFILL_ActiveListInsert(FILL_EDGE** activehead, FILL_EDGE* NewEdge ) { FILL_EDGE *pPrev, *pThis; //DPRINT1("In POLYGONFILL_ActiveListInsert()\n"); ASSERT(activehead && NewEdge); if (!*activehead) { NewEdge->pNext = NULL; *activehead = NewEdge; return; } /* ** First lets check to see if we have a new smallest value. */ if (FILL_EDGE_Compare(NewEdge, *activehead) <= 0) { NewEdge->pNext = *activehead; *activehead = NewEdge; return; } /* ** Ok, now scan to the next spot to put this item. */ pThis = *activehead; pPrev = NULL; while ( pThis && FILL_EDGE_Compare(pThis, NewEdge) < 0 ) { pPrev = pThis; pThis = pThis->pNext; } ASSERT(pPrev); NewEdge->pNext = pPrev->pNext; pPrev->pNext = NewEdge; //DEBUG_PRINT_ACTIVE_EDGELIST(*activehead); } /* ** Create a list of edges for a list of points. */ static FILL_EDGE_LIST* FASTCALL POLYGONFILL_MakeEdgeList(PPOINT Points, int Count) { int CurPt = 0; FILL_EDGE_LIST* list = 0; FILL_EDGE* e = 0; if (0 == Points || 2 > Count) return 0; list = (FILL_EDGE_LIST*)EngAllocMem(FL_ZERO_MEMORY, sizeof(FILL_EDGE_LIST), FILL_EDGE_ALLOC_TAG); if ( 0 == list ) goto fail; list->Count = 0; list->Edges = (FILL_EDGE**)EngAllocMem(FL_ZERO_MEMORY, Count*sizeof(FILL_EDGE*), FILL_EDGE_ALLOC_TAG); if ( !list->Edges ) goto fail; memset(list->Edges, 0, Count * sizeof(FILL_EDGE*)); for (CurPt = 1; CurPt < Count; ++CurPt) { e = POLYGONFILL_MakeEdge ( Points[CurPt-1], Points[CurPt] ); if (!e) goto fail; // If a straight horizontal line - who cares? if (!e->absdy) EngFreeMem(e); else list->Edges[list->Count++] = e; } e = POLYGONFILL_MakeEdge ( Points[CurPt-1], Points[0] ); if ( !e ) goto fail; if (!e->absdy) EngFreeMem(e); else list->Edges[list->Count++] = e; return list; fail: DPRINT1("Out Of MEMORY!!\n"); POLYGONFILL_DestroyEdgeList ( list ); return 0; } /* ** This slow routine uses the data stored in the edge list to ** calculate the x intercepts for each line in the edge list ** for scanline Scanline. **TODO: Get rid of this floating point arithmetic */ static void FASTCALL POLYGONFILL_UpdateScanline(FILL_EDGE* pEdge, int Scanline) { if (0 == pEdge->dy) return; ASSERT(pEdge->FromY <= Scanline && pEdge->ToY > Scanline); if (pEdge->xmajor) { int steps; ASSERT(pEdge->y == Scanline); // Now shoot to end of scanline collision steps = (pEdge->ErrorMax-pEdge->Error-1)/pEdge->absdy; if (steps) { // Record first collision with scanline int x1 = pEdge->x; pEdge->x += steps * pEdge->XDirection; pEdge->Error += steps * pEdge->absdy; ASSERT ( pEdge->Error < pEdge->ErrorMax ); pEdge->XIntercept[0] = min(x1,pEdge->x); pEdge->XIntercept[1] = max(x1,pEdge->x); } else { pEdge->XIntercept[0] = pEdge->x; pEdge->XIntercept[1] = pEdge->x; } // We should require exactly 1 step to step onto next scanline... ASSERT((pEdge->ErrorMax-pEdge->Error-1) / pEdge->absdy == 0); pEdge->x += pEdge->XDirection; pEdge->Error += pEdge->absdy; ASSERT(pEdge->Error >= pEdge->ErrorMax); // Now step onto next scanline... pEdge->Error -= pEdge->absdx; pEdge->y++; } else // Then this is a y-major line { pEdge->XIntercept[0] = pEdge->x; pEdge->XIntercept[1] = pEdge->x; pEdge->Error += pEdge->absdx; pEdge->y++; if (pEdge->Error >= pEdge->ErrorMax) { pEdge->Error -= pEdge->ErrorMax; pEdge->x += pEdge->XDirection; ASSERT ( pEdge->Error < pEdge->ErrorMax ); } } //DPRINT("Line (%d, %d) to (%d, %d) intersects scanline %d at (%d,%d)\n", // pEdge->FromX, pEdge->FromY, pEdge->ToX, pEdge->ToY, Scanline, pEdge->XIntercept[0], pEdge->XIntercept[1] ); } /* * This method updates the Active edge collection for the scanline Scanline. */ static void APIENTRY POLYGONFILL_BuildActiveList( int Scanline, FILL_EDGE_LIST* list, FILL_EDGE** ActiveHead) { int i; ASSERT(list && ActiveHead); *ActiveHead = 0; for (i = 0; i < list->Count; i++) { FILL_EDGE* pEdge = list->Edges[i]; ASSERT(pEdge); if (pEdge->FromY <= Scanline && pEdge->ToY > Scanline) { POLYGONFILL_UpdateScanline(pEdge, Scanline); POLYGONFILL_ActiveListInsert(ActiveHead, pEdge); } } } /* ** This method fills the portion of the polygon that intersects with the scanline ** Scanline. */ static void APIENTRY POLYGONFILL_FillScanLineAlternate( PDC dc, int ScanLine, FILL_EDGE* ActiveHead, SURFACE *psurf, BRUSHOBJ *BrushObj, MIX RopMode ) { FILL_EDGE *pLeft, *pRight; if (!ActiveHead) return; pLeft = ActiveHead; pRight = pLeft->pNext; ASSERT(pRight); while (NULL != pRight) { int x1 = pLeft->XIntercept[0]; int x2 = pRight->XIntercept[1]; if (x2 > x1) { RECTL BoundRect; BoundRect.top = ScanLine; BoundRect.bottom = ScanLine + 1; BoundRect.left = x1; BoundRect.right = x2; //DPRINT("Fill Line (%d, %d) to (%d, %d)\n",x1, ScanLine, x2, ScanLine); IntEngLineTo(&psurf->SurfObj, (CLIPOBJ *)&dc->co, BrushObj, x1, ScanLine, x2, ScanLine, &BoundRect, // Bounding rectangle RopMode); // MIX } pLeft = pRight->pNext; pRight = pLeft ? pLeft->pNext : NULL; } } static void APIENTRY POLYGONFILL_FillScanLineWinding( PDC dc, int ScanLine, FILL_EDGE* ActiveHead, SURFACE *psurf, BRUSHOBJ *BrushObj, MIX RopMode ) { FILL_EDGE *pLeft, *pRight; int x1, x2, winding = 0; RECTL BoundRect; if (!ActiveHead) return; BoundRect.top = ScanLine; BoundRect.bottom = ScanLine + 1; pLeft = ActiveHead; winding = pLeft->YDirection; pRight = pLeft->pNext; ASSERT(pRight); // Setup first line... x1 = pLeft->XIntercept[0]; x2 = pRight->XIntercept[1]; pLeft = pRight; pRight = pLeft->pNext; winding += pLeft->YDirection; while ( NULL != pRight ) { int newx1 = pLeft->XIntercept[0]; int newx2 = pRight->XIntercept[1]; if ( winding ) { // Check and see if this new line touches the previous... if ((newx1 >= x1 && newx1 <= x2) || (newx2 >= x1 && newx2 <= x2) || (x1 >= newx1 && x1 <= newx2) || (x2 >= newx2 && x2 <= newx2)) { // Yup, just tack it on to our existing line x1 = min(x1,newx1); x2 = max(x2,newx2); } else { // Nope - render the old line.. BoundRect.left = x1; BoundRect.right = x2; //DPRINT("Fill Line (%d, %d) to (%d, %d)\n",x1, ScanLine, x2, ScanLine); IntEngLineTo(&psurf->SurfObj, (CLIPOBJ *)&dc->co, BrushObj, x1, ScanLine, x2, ScanLine, &BoundRect, // Bounding rectangle RopMode); // MIX x1 = newx1; x2 = newx2; } } pLeft = pRight; pRight = pLeft->pNext; winding += pLeft->YDirection; } // There will always be a line left-over, render it now... BoundRect.left = x1; BoundRect.right = x2; //DPRINT("Fill Line (%d, %d) to (%d, %d)\n",x1, ScanLine, x2, ScanLine); IntEngLineTo(&psurf->SurfObj, (CLIPOBJ *)&dc->co, BrushObj, x1, ScanLine, x2, ScanLine, &BoundRect, // Bounding rectangle RopMode); // MIX } // When the fill mode is ALTERNATE, GDI fills the area between odd-numbered and // even-numbered polygon sides on each scan line. That is, GDI fills the area between the // first and second side, between the third and fourth side, and so on. // WINDING Selects winding mode (fills any region with a nonzero winding value). // When the fill mode is WINDING, GDI fills any region that has a nonzero winding value. // This value is defined as the number of times a pen used to draw the polygon would go around the region. // The direction of each edge of the polygon is important. BOOL APIENTRY FillPolygon( PDC dc, SURFACE *psurf, BRUSHOBJ *BrushObj, MIX RopMode, CONST PPOINT Points, int Count, RECTL BoundRect ) { FILL_EDGE_LIST *list = 0; FILL_EDGE *ActiveHead = 0; int ScanLine; PDC_ATTR pdcattr = dc->pdcattr; void (APIENTRY *FillScanLine)( PDC dc, int ScanLine, FILL_EDGE* ActiveHead, SURFACE *psurf, BRUSHOBJ *BrushObj, MIX RopMode); //DPRINT("FillPolygon\n"); /* Create Edge List. */ list = POLYGONFILL_MakeEdgeList(Points, Count); /* DEBUG_PRINT_EDGELIST(list); */ if (NULL == list) return FALSE; if (WINDING == pdcattr->jFillMode) FillScanLine = POLYGONFILL_FillScanLineWinding; else /* Default */ FillScanLine = POLYGONFILL_FillScanLineAlternate; /* For each Scanline from BoundRect.bottom to BoundRect.top, * determine line segments to draw */ for (ScanLine = BoundRect.top; ScanLine < BoundRect.bottom; ++ScanLine) { POLYGONFILL_BuildActiveList(ScanLine, list, &ActiveHead); //DEBUG_PRINT_ACTIVE_EDGELIST(ActiveHead); FillScanLine(dc, ScanLine, ActiveHead, psurf, BrushObj, RopMode); } /* Free Edge List. If any are left. */ POLYGONFILL_DestroyEdgeList(list); return TRUE; } BOOL FASTCALL IntFillPolygon( PDC dc, SURFACE *psurf, BRUSHOBJ *BrushObj, CONST PPOINT Points, int Count, RECTL DestRect, POINTL *BrushOrigin) { FILL_EDGE_LIST *list = 0; FILL_EDGE *ActiveHead = 0; FILL_EDGE *pLeft, *pRight; int ScanLine; //DPRINT("IntFillPolygon\n"); /* Create Edge List. */ list = POLYGONFILL_MakeEdgeList(Points, Count); /* DEBUG_PRINT_EDGELIST(list); */ if (NULL == list) return FALSE; /* For each Scanline from DestRect.top to DestRect.bottom, determine line segments to draw */ for (ScanLine = DestRect.top; ScanLine < DestRect.bottom; ++ScanLine) { POLYGONFILL_BuildActiveList(ScanLine, list, &ActiveHead); //DEBUG_PRINT_ACTIVE_EDGELIST(ActiveHead); if (!ActiveHead) { POLYGONFILL_DestroyEdgeList(list); return FALSE; } pLeft = ActiveHead; pRight = pLeft->pNext; ASSERT(pRight); while (NULL != pRight) { int x1 = pLeft->XIntercept[0]; int x2 = pRight->XIntercept[1]; if (x2 > x1) { RECTL LineRect; LineRect.top = ScanLine; LineRect.bottom = ScanLine + 1; LineRect.left = x1; LineRect.right = x2; IntEngBitBlt(&psurf->SurfObj, NULL, NULL, (CLIPOBJ *)&dc->co, NULL, &LineRect, NULL, NULL, BrushObj, BrushOrigin, ROP4_FROM_INDEX(R3_OPINDEX_PATCOPY)); } pLeft = pRight->pNext; pRight = pLeft ? pLeft->pNext : NULL; } } /* Free Edge List. If any are left. */ POLYGONFILL_DestroyEdgeList(list); return TRUE; } /* EOF */