diff --git a/components/fpvectorial/fpvectorial.pas b/components/fpvectorial/fpvectorial.pas index 2160921a19..f981bbaf14 100644 --- a/components/fpvectorial/fpvectorial.pas +++ b/components/fpvectorial/fpvectorial.pas @@ -561,8 +561,11 @@ type procedure CalcGradientVector(out AGradientStart, AGradientEnd: T2dPoint; const ARect: TRect; ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); + procedure DrawPolygon(ADest: TFPCustomCanvas; var RenderInfo: TvRenderInfo; + const APoints: TPointsArray; const APolyStarts: TIntegerDynArray; ARect: TRect); procedure DrawPolygonBrushGradient(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; - const APoints: TPointsArray; ARect: TRect; AGradientStart, AGradientEnd: T2DPoint); + const APoints: TPointsArray; const APolyStarts: TIntegerDynArray; + ARect: TRect; AGradientStart, AGradientEnd: T2DPoint); procedure DrawPolygonBrushRadialGradient(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; const APoints: TPointsArray; ARect: TRect); procedure DrawNativePolygonBrushRadialGradient(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; @@ -3944,13 +3947,69 @@ begin end; end; +{ Fills a polygon with the color of the current brush. The routine can handle + non-contiguous polygons (holes!) correctly using the ScanLine algorithm and + the even-odd rule + http://www.tutorialspoint.com/computer_graphics/polygon_filling_algorithm.htm + + NOTE: The method only performs a solid fill, i.e. Brush.Style is ignored } +procedure TvEntityWithPenAndBrush.DrawPolygon(ADest: TFPCustomCanvas; + var RenderInfo: TvRenderInfo; const APoints: TPointsArray; + const APolyStarts: TIntegerDynArray; ARect: TRect); +var + scanlineY, scanLineY1, scanLineY2: Integer; + lPoints, pts: T2DPointsArray; + j: Integer; +begin + if ARect.Top < ARect.Bottom then + begin + scanLineY1 := ARect.Top; + scanLineY2 := ARect.Bottom; + end else + begin + scanLineY1 := ARect.Bottom; + scanLineY2 := ARect.Top; + end; + + // Prepare points as needed by the GetLinePolygonIntersectionPoints procedure + SetLength(pts, Length(APoints)); + for j := 0 to High(APoints) do + pts[j] := Point2D(APoints[j].X, APoints[j].Y); + + // Prepare parameters and polygon points + ADest.Pen.Style := psSolid; + ADest.Pen.Width := 1; + ADest.Pen.FPColor := Brush.Color; + + // Fill polygon by drawing horizontal line segments + scanlineY := scanlineY1; + while (scanlineY <= scanlineY2) do begin + // Find intersection points of horizontal scan line with polygon + // with polygon + lPoints := GetLinePolygonIntersectionPoints(scanlineY, pts, APolyStarts, false); + if Length(lPoints) < 2 then begin + inc(scanlineY); + Continue; + end; + // Draw lines between intersection points, skip every second pair + j := 0; + while j < High(lPoints) do + begin + ADest.Line(round(lPoints[j].X), round(lPoints[j].Y), round(lPoints[j+1].X), round(lPoints[j+1].Y)); + inc(j, 2); + end; + // Proceed to next scan line + inc(scanlineY); + end; +end; + { Fills the entity with a gradient. Assumes that the boundary is already in canvas units and is specified by polygon APoints. } procedure TvEntityWithPenAndBrush.DrawPolygonBrushGradient( ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; - const APoints: TPointsArray; ARect: TRect; AGradientStart, - AGradientEnd: T2DPoint); + const APoints: TPointsArray;const APolyStarts: TIntegerDynArray; + ARect: TRect; AGradientStart, AGradientEnd: T2DPoint); var lPoints, pts: T2DPointsArray; i, j: Integer; @@ -4072,7 +4131,7 @@ begin begin // Find intersection points of gradient line (normal to gradient vector) // with polygon - lPoints := GetLinePolygonIntersectionPoints(coord, pts, coordIsX); + lPoints := GetLinePolygonIntersectionPoints(coord, pts, APolyStarts, coordIsX); if Length(lPoints) < 2 then begin coord := coord + dcoord; Continue; @@ -4285,8 +4344,7 @@ begin end else {$endif} - DrawPolygonBrushGradient(ADest, ARenderInfo, polypoints, lRect, gv1, gv2); - // to do: multiple polygons! + DrawPolygonBrushGradient(ADest, ARenderInfo, polypoints, polystarts, lRect, gv1, gv2); // Paint outline if ADest.Pen.Style <> psClear then @@ -4969,19 +5027,26 @@ begin bkSimpleBrush: if Brush.Style <> bsClear then begin - {$IFDEF USE_LCL_CANVAS} - for i := 0 to High(FPolyStarts) do - begin - j := FPolyStarts[i]; - if i = High(FPolyStarts) then - n := Length(FPolyPoints) - j - else - n := FPolyStarts[i+1] - FPolyStarts[i]; // + 1; - ACanvas.Polygon(@FPolyPoints[j], n, WindingRule = vcmNonZeroWindingRule); - end; - {$ELSE} - ADest.Polygon(FPolyPoints); - {$ENDIF} + if (Brush.Style = bsSolid) and (Length(FPolyStarts) > 1) then begin + // Non-contiguous polygon (polygon with "holes") --> use special procedure + // Disadvantage: it can oly do solid fills! + lRect := Rect(x1, y1, x2, y2); + DrawPolygon(ADest, ARenderInfo, FPolyPoints, FPolyStarts, lRect) + end + else + {$IFDEF USE_LCL_CANVAS} + for i := 0 to High(FPolyStarts) do + begin + j := FPolyStarts[i]; + if i = High(FPolyStarts) then + n := Length(FPolyPoints) - j + else + n := FPolyStarts[i+1] - FPolyStarts[i]; // + 1; + ACanvas.Polygon(@FPolyPoints[j], n, WindingRule = vcmNonZeroWindingRule); + end; + {$ELSE} + ADest.Polygon(FPolyPoints); + {$ENDIF} end; else // gradients // Boundary rect of shape filled with a gradient @@ -4989,7 +5054,7 @@ begin // calculate gradient vector CalcGradientVector(gv1, gv2, lRect, ADestX, ADestY, AMulX, AMulY); // Draw the gradient - DrawPolygonBrushGradient(ADest, ARenderInfo, FPolyPoints, lRect, gv1, gv2); + DrawPolygonBrushGradient(ADest, ARenderInfo, FPolyPoints, FPolyStarts, lRect, gv1, gv2); // to do: multiple polygons! end; diff --git a/components/fpvectorial/fpvutils.pas b/components/fpvectorial/fpvutils.pas index 3c8221a6a1..436b5e040c 100644 --- a/components/fpvectorial/fpvutils.pas +++ b/components/fpvectorial/fpvutils.pas @@ -73,7 +73,10 @@ procedure ConvertPathToPolygons(APath: TPath; ADestX, ADestY: Integer; AMulX, AM var PolygonPoints: TPointsArray; var PolygonStartIndexes: TIntegerDynArray); procedure ConvertPathToPoints(APath: TPath; ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray); function GetLinePolygonIntersectionPoints(ACoord: Double; - const APoints: T2DPointsArray; ACoordIsX: Boolean): T2DPointsArray; + const APoints: T2DPointsArray; const APolyStarts: TIntegerDynArray; + ACoordIsX: Boolean): T2DPointsArray; overload; +function GetLinePolygonIntersectionPoints(ACoord: Double; + const APoints: T2DPointsArray; ACoordIsX: Boolean): T2DPointsArray; overload; function Rotate2DPoint(P, RotCenter: TPoint; alpha:double): TPoint; function Rotate3DPointInXY(P, RotCenter: T3DPoint; alpha:double): T3DPoint; procedure NormalizeRect(var ARect: TRect); @@ -711,15 +714,27 @@ begin Result := CompareValue(val1^, val2^); end; +function GetLinePolygonIntersectionPoints(ACoord: Double; + const APoints: T2DPointsArray; ACoordIsX: Boolean): T2DPointsArray; +var + polystarts: TIntegerDynArray; +begin + SetLength(polystarts, 1); + polystarts[0] := 0; + Result := GetLinePolygonIntersectionPoints(ACoord, APoints, ACoordIsX); +end; + {@@ Calculates the intersection points of a vertical (ACoordIsX = true) or horizontal (ACoordIsX = false) line with border of the polygon specified by APoints. Returns the coordinates of the intersection points } function GetLinePolygonIntersectionPoints(ACoord: Double; - const APoints: T2DPointsArray; ACoordIsX: Boolean): T2DPointsArray; + const APoints: T2DPointsArray; const APolyStarts: TIntegerDynArray; + ACoordIsX: Boolean): T2DPointsArray; const EPS = 1e-9; var - j: Integer; + j, p: Integer; + firstj,lastj: Integer; dx, dy: Double; xval, yval: Double; val: ^Double; @@ -728,41 +743,72 @@ begin list := TFPList.Create; if ACoordIsX then begin - for j:=0 to High(APoints) - 1 do - begin - if ((APoints[j].X <= ACoord) and (ACoord < APoints[j+1].X)) or - ((APoints[j+1].X <= ACoord) and (ACoord < APoints[j].X)) then - begin - dx := APoints[j+1].X - APoints[j].X; // can't be zero here - dy := APoints[j+1].Y - APoints[j].Y; - New(val); - val^ := APoints[j].Y + (ACoord - APoints[j].X) * dy / dx; - list.Add(val); + for p := 0 to High(APolyStarts) do begin + firstj := APolyStarts[p]; + lastj := IfThen(p = High(APolyStarts), High(APoints), APolyStarts[p+1]-1); + // Skip non-closed polygons + if (APoints[firstj].X <> APoints[lastj].x) or (APoints[lastj].Y <> APoints[lastj].Y) then + continue; + for j := firstj to lastj-1 do + if ((APoints[j].X <= ACoord) and (ACoord < APoints[j+1].X)) or + ((APoints[j+1].X <= ACoord) and (ACoord < APoints[j].X)) then + begin + dx := APoints[j+1].X - APoints[j].X; // can't be zero here + dy := APoints[j+1].Y - APoints[j].Y; + New(val); + val^ := APoints[j].Y + (ACoord - APoints[j].X) * dy / dx; + list.Add(val); + end; end; - end; end else begin - for j:=0 to High(APoints) - 1 do - if ((APoints[j].Y <= ACoord) and (ACoord < APoints[j+1].Y)) or - ((APoints[j+1].Y <= ACoord) and (ACoord < APoints[j].Y)) then - begin - dy := APoints[j+1].Y - APoints[j].Y; // can't be zero here - dx := APoints[j+1].X - APoints[j].X; - New(val); - val^ := APoints[j].X + (ACoord - APoints[j].Y) * dx / dy; - list.Add(val); - end; + for p := 0 to High(APolyStarts) do begin + firstj := APolyStarts[p]; + lastj := IfThen(p = High(APolyStarts), High(APoints), APolyStarts[p+1]-1); + // Skip non-closed polygons + if (APoints[firstj].X <> APoints[lastj].x) or (APoints[lastj].Y <> APoints[lastj].Y) then + continue; + for j := firstj to lastj-1 do + if ((APoints[j].Y <= ACoord) and (ACoord < APoints[j+1].Y)) or + ((APoints[j+1].Y <= ACoord) and (ACoord < APoints[j].Y)) then + begin + dy := APoints[j+1].Y - APoints[j].Y; // can't be zero here + dx := APoints[j+1].X - APoints[j].X; + New(val); + val^ := APoints[j].X + (ACoord - APoints[j].Y) * dx / dy; + list.Add(val); + end; + end; end; // Sort intersection coordinates in ascending order list.Sort(@CompareDbl); - SetLength(Result, list.Count); - if ACoordIsX then - for j:=0 to list.Count-1 do - Result[j] := Point2D(ACoord, Double(list[j]^)) - else - for j:=0 to list.Count-1 do - Result[j] := Point2D(Double(list[j]^), ACoord); + + // When scanning across an non-contiguous polygon the scan may produce an + // odd number of points where the scan finds irregular points due to interaction + // with the other polygon curves. I don't have a general solution, only for + // the case of 3 points. + (* + if list.Count = 3 then begin // this can't be --> use ony outer points + SetLength(Result, 2); + if ACoordIsX then begin + Result[0] := Point2D(ACoord, Double(list[0]^)); + Result[1] := Point2D(ACoord, Double(list[2]^)); + end else begin + Result[0] := Point2D(Double(list[0]^), ACoord); + Result[1] := Point2D(Double(list[2]^), ACoord); + end; + end else + *) + begin // regular case + SetLength(Result, list.Count); + if ACoordIsX then + for j:=0 to list.Count-1 do + Result[j] := Point2D(ACoord, Double(list[j]^)) + else + for j:=0 to list.Count-1 do + Result[j] := Point2D(Double(list[j]^), ACoord); + end; // Clean-up for j:=list.Count-1 downto 0 do