fpvectorial: Supported rotated linear gradients. Support entity- and page-relative coordinates for gradient vectors.

git-svn-id: trunk@51232 -
This commit is contained in:
wp 2016-01-09 17:09:17 +00:00
parent 811799c840
commit d593e000f8
3 changed files with 397 additions and 165 deletions

View File

@ -113,6 +113,22 @@ type
TvVectorialDocument = class; TvVectorialDocument = class;
TvEmbeddedVectorialDoc = class; TvEmbeddedVectorialDoc = class;
{ Coordinates }
T2DPoint = record
X, Y: Double;
end;
P2DPoint = ^T2DPoint;
T3DPoint = record
X, Y, Z: Double;
end;
P3DPoint = ^T3DPoint;
T2DPointsArray = array of T2DPoint;
T3DPointsArray = array of T3DPoint;
TPointsArray = array of TPoint;
{ Pen, Brush and Font } { Pen, Brush and Font }
TvPen = record TvPen = record
@ -123,12 +139,16 @@ type
end; end;
PvPen = ^TvPen; PvPen = ^TvPen;
TvBrushKind = (bkSimpleBrush, bkHorizontalGradient, bkVerticalGradient, bkOtherLinearGradient, bkRadialGradient); TvBrushKind = (bkSimpleBrush, bkHorizontalGradient, bkVerticalGradient,
bkOtherLinearGradient, bkRadialGradient);
TvCoordinateUnit = (vcuDocumentUnit, vcuPercentage); TvCoordinateUnit = (vcuDocumentUnit, vcuPercentage);
TvGradientFlag = (gfRelStartX, gfRelStartY, gfRelEndX, gfRelEndY, gfRelToUserSpace);
TvGradientFlags = set of TvGradientFlag;
TvGradientColor = record TvGradientColor = record
Color: TFPColor; Color: TFPColor;
Position: Double; Position: Double; // 0 ... 1
end; end;
TvGradientColors = array of TvGradientColor; TvGradientColors = array of TvGradientColor;
@ -138,6 +158,9 @@ type
Style: TFPBrushStyle; Style: TFPBrushStyle;
Kind: TvBrushKind; Kind: TvBrushKind;
// Gradient filling support // Gradient filling support
Gradient_start: T2DPoint; // Start/end point of gradient, in pixels by default,
Gradient_end: T2DPoint; // but if gfRel* in flags relative to entity boundary or user space
Gradient_flags: TvGradientFlags;
Gradient_cx, Gradient_cy, Gradient_r, Gradient_fx, Gradient_fy: Double; Gradient_cx, Gradient_cy, Gradient_r, Gradient_fx, Gradient_fy: Double;
Gradient_cx_Unit, Gradient_cy_Unit, Gradient_r_Unit, Gradient_fx_Unit, Gradient_fy_Unit: TvCoordinateUnit; Gradient_cx_Unit, Gradient_cy_Unit, Gradient_r_Unit, Gradient_fx_Unit, Gradient_fy_Unit: TvCoordinateUnit;
Gradient_colors: TvGradientColors; Gradient_colors: TvGradientColors;
@ -273,16 +296,7 @@ type
function GetListLevelStyle(AIndex: Integer): TvListLevelStyle; function GetListLevelStyle(AIndex: Integer): TvListLevelStyle;
end; end;
{ Coordinates and polyline segments } { Polyline segments }
T3DPoint = record
X, Y, Z: Double;
end;
P3DPoint = ^T3DPoint;
T3DPointsArray = array of T3DPoint;
TPointsArray = array of TPoint;
TSegmentType = ( TSegmentType = (
st2DLine, st2DLineWithPen, st2DBezier, st2DLine, st2DLineWithPen, st2DBezier,
@ -426,7 +440,7 @@ type
Selected: Boolean; Selected: Boolean;
ForceRenderBlock: Boolean; // Blocks are usually invisible, but when rendering an insert, their drawing can be forced ForceRenderBlock: Boolean; // Blocks are usually invisible, but when rendering an insert, their drawing can be forced
// Fields which are output from the rendering process // Fields which are output from the rendering process
EntityCanvasMinXY, EntityCanvasMaxXY: TPoint; // The size utilized in the canvas to draw this entity EntityCanvasMinXY, EntityCanvasMaxXY: TPoint; // The size utilized in the canvas to draw this entity, in pixels
end; end;
TvEntityFeatures = record TvEntityFeatures = record
@ -466,8 +480,8 @@ type
{@@ ASubpart is only valid if this routine returns vfrSubpartFound } {@@ ASubpart is only valid if this routine returns vfrSubpartFound }
function GetLineIntersectionPoints(ACoord: Double; function GetLineIntersectionPoints(ACoord: Double;
ACoordIsX: Boolean): TDoubleDynArray; virtual; // get all points where the entity inner area crosses a line ACoordIsX: Boolean): TDoubleDynArray; virtual; // get all points where the entity inner area crosses a line
function GetLinePolygonIntersectionPoints(ACoord: Integer; function GetLinePolygonIntersectionPoints(ACoord: Double;
const APoints: TPointsArray; ACoordIsX: Boolean): TPointsArray; virtual; const APoints: T2DPointsArray; ACoordIsX: BOolean): T2DPointsArray; virtual;
function TryToSelect(APos: TPoint; var ASubpart: Cardinal; ASnapFlexibility: Integer = 5): TvFindEntityResult; virtual; function TryToSelect(APos: TPoint; var ASubpart: Cardinal; ASnapFlexibility: Integer = 5): TvFindEntityResult; virtual;
procedure Move(ADeltaX, ADeltaY: Double); virtual; procedure Move(ADeltaX, ADeltaY: Double); virtual;
procedure MoveSubpart(ADeltaX, ADeltaY: Double; ASubpart: Cardinal); virtual; procedure MoveSubpart(ADeltaX, ADeltaY: Double; ASubpart: Cardinal); virtual;
@ -520,6 +534,9 @@ type
TvClipMode = (vcmNonzeroWindingRule, vcmEvenOddRule); TvClipMode = (vcmNonzeroWindingRule, vcmEvenOddRule);
TvEntityWithPenAndBrush = class(TvEntityWithPen) TvEntityWithPenAndBrush = class(TvEntityWithPen)
protected
procedure DrawPolygonBrushGradient(ADest: TFPCustomCanvas; const APoints:
TPointsArray; ARect: TRect; AGradientStart, AGradientEnd: T2DPoint);
public public
{@@ The global Brush for the entire entity. In the case of paths, individual {@@ The global Brush for the entire entity. In the case of paths, individual
elements might be able to override this setting. } elements might be able to override this setting. }
@ -532,8 +549,6 @@ type
procedure DrawBrushGradient(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; procedure DrawBrushGradient(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo;
x1, y1, x2, y2: Integer; x1, y1, x2, y2: Integer;
ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); virtual; ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); virtual;
procedure DrawPolygonBrushGradient(ADest: TFPCustomCanvas;
const APolyPoints: TPointsArray; x1, y1, x2, y2: Integer);
procedure Render(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; ADestX: Integer = 0; procedure Render(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0; ADoDraw: Boolean = True); override; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0; ADoDraw: Boolean = True); override;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override; function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
@ -599,8 +614,8 @@ type
procedure AppendEllipticalArc(ARadX, ARadY, AXAxisRotation, ADestX, ADestY: Double; ALeftmostEllipse, AClockwiseArcFlag: Boolean); // See http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands procedure AppendEllipticalArc(ARadX, ARadY, AXAxisRotation, ADestX, ADestY: Double; ALeftmostEllipse, AClockwiseArcFlag: Boolean); // See http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
procedure AppendEllipticalArcWithCenter(ARadX, ARadY, AXAxisRotation, ADestX, ADestY, ACenterX, ACenterY: Double; AClockwiseArcFlag: Boolean); // See http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands procedure AppendEllipticalArcWithCenter(ARadX, ARadY, AXAxisRotation, ADestX, ADestY, ACenterX, ACenterY: Double; AClockwiseArcFlag: Boolean); // See http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
function GetLineIntersectionPoints(ACoord: Double; ACoordIsX: Boolean): TDoubleDynArray; override; function GetLineIntersectionPoints(ACoord: Double; ACoordIsX: Boolean): TDoubleDynArray; override;
function GetLinePolygonIntersectionPoints(ACoord: Integer; function GetLinePolygonIntersectionPoints(ACoord: Double;
const APoints: TPointsArray; ACoordIsX: Boolean): TPointsArray; override; const APoints: T2DPointsArray; ACoordIsX: Boolean): T2DPointsArray; override;
procedure Move(ADeltaX, ADeltaY: Double); override; procedure Move(ADeltaX, ADeltaY: Double); override;
procedure MoveSubpart(ADeltaX, ADeltaY: Double; ASubpart: Cardinal); override; procedure MoveSubpart(ADeltaX, ADeltaY: Double; ASubpart: Cardinal); override;
function MoveToSubpart(ASubpart: Cardinal): TPathSegment; function MoveToSubpart(ASubpart: Cardinal): TPathSegment;
@ -1629,7 +1644,7 @@ procedure RegisterVectorialReader(
procedure RegisterVectorialWriter( procedure RegisterVectorialWriter(
AWriterClass: TvVectorialWriterClass; AWriterClass: TvVectorialWriterClass;
AFormat: TvVectorialFormat); AFormat: TvVectorialFormat);
function Make2DPoint(AX, AY: Double): T3DPoint; //function Make2DPoint(AX, AY: Double): T3DPoint;
function Dimension(AValue : Double; AUnits : TvUnits) : TvDimension; function Dimension(AValue : Double; AUnits : TvUnits) : TvDimension;
function ConvertDimensionToMM(ADimension: TvDimension; ATotalSize: Double): Double; function ConvertDimensionToMM(ADimension: TvDimension; ATotalSize: Double): Double;
@ -1726,13 +1741,14 @@ begin
end; end;
end; end;
{
function Make2DPoint(AX, AY: Double): T3DPoint; function Make2DPoint(AX, AY: Double): T3DPoint;
begin begin
Result.X := AX; Result.X := AX;
Result.Y := AY; Result.Y := AY;
Result.Z := 0; Result.Z := 0;
end; end;
}
function Dimension(AValue: Double; AUnits: TvUnits): TvDimension; function Dimension(AValue: Double; AUnits: TvUnits): TvDimension;
begin begin
Result.Value := AValue; Result.Value := AValue;
@ -3087,6 +3103,7 @@ begin
Result := True; Result := True;
APoint.X := T2DSegment(Previous).X; APoint.X := T2DSegment(Previous).X;
APoint.Y := T2DSegment(Previous).Y; APoint.Y := T2DSegment(Previous).Y;
APoint.Z := 0;
Exit; Exit;
end; end;
end; end;
@ -3194,8 +3211,8 @@ var
begin begin
Result := 0; Result := 0;
if not GetStartPoint(lStartPoint) then Exit; if not GetStartPoint(lStartPoint) then Exit;
Result := BezierEquation_GetLength(lStartPoint, Make2DPoint(X2, Y2), Result := BezierEquation_GetLength(lStartPoint, Make3DPoint(X2, Y2, 0),
Make2DPoint(X3, Y3), Make2DPoint(X, Y)); Make3DPoint(X3, Y3, 0), Make3DPoint(X, Y, 0), 0);
end; end;
function T2DBezierSegment.GetPointAndTangentForDistance(ADistance: Double; out function T2DBezierSegment.GetPointAndTangentForDistance(ADistance: Double; out
@ -3206,8 +3223,8 @@ begin
Result:=inherited GetPointAndTangentForDistance(ADistance, AX, AY, Result:=inherited GetPointAndTangentForDistance(ADistance, AX, AY,
ATangentAngle); ATangentAngle);
if not GetStartPoint(lStartPoint) then Exit; if not GetStartPoint(lStartPoint) then Exit;
Result := BezierEquation_GetPointAndTangentForLength(lStartPoint, Make2DPoint(X2, Y2), Result := BezierEquation_GetPointAndTangentForLength(lStartPoint, Make3DPoint(X2, Y2, 0),
Make2DPoint(X3, Y3), Make2DPoint(X, Y), ADistance, AX, AY, ATangentAngle); Make3DPoint(X3, Y3, 0), Make3DPoint(X, Y, 0), ADistance, AX, AY, ATangentAngle);
end; end;
procedure T2DBezierSegment.Move(ADeltaX, ADeltaY: Double); procedure T2DBezierSegment.Move(ADeltaX, ADeltaY: Double);
@ -3250,10 +3267,10 @@ begin
SetLength(pts, 0); SetLength(pts, 0);
AddBezierToPoints( AddBezierToPoints(
Make2DPoint(coordX, coordY), Make3DPoint(coordX, coordY, 0),
Make2DPoint(coord2X, coord2Y), Make3DPoint(coord2X, coord2Y, 0),
Make2DPoint(coord3X, coord3Y), Make3DPoint(coord3X, coord3Y, 0),
Make2DPoint(coord4X, coord4Y), Make3DPoint(coord4X, coord4Y, 0),
pts); pts);
if Length(pts) = 0 then if Length(pts) = 0 then
@ -3436,8 +3453,8 @@ end;
{ calculates the intersection points of a line at the specified coordinate with { calculates the intersection points of a line at the specified coordinate with
the entity's boundary. This overload uses the boundary in canvas units, the entity's boundary. This overload uses the boundary in canvas units,
specified by the points array APoints. } specified by the points array APoints. }
function TvEntity.GetLinePolygonIntersectionPoints(ACoord: Integer; function TvEntity.GetLinePolygonIntersectionPoints(ACoord: Double;
const APoints: TPointsArray; ACoordIsX: Boolean): TPointsArray; const APoints: T2DPointsArray; ACoordIsX: Boolean): T2DPointsArray;
begin begin
SetLength(Result, 0); SetLength(Result, 0);
end; end;
@ -3634,107 +3651,161 @@ end;
{ Fills the entity with a gradient. { Fills the entity with a gradient.
Assumes that the boundary is already in canvas units and is specified by Assumes that the boundary is already in canvas units and is specified by
polygon APolyPoints. } polygon APoints. }
procedure TvEntityWithPenAndBrush.DrawPolygonBrushGradient(ADest: TFPCustomCanvas; procedure TvEntityWithPenAndBrush.DrawPolygonBrushGradient(ADest: TFPCustomCanvas;
const APolyPoints: TPointsArray; x1, y1, x2, y2: Integer); const APoints: TPointsArray; ARect: TRect; AGradientStart, AGradientEnd: T2dPoint);
var var
lPoints: TPointsArray; lPoints, pts: T2DPointsArray;
lColor, lColor1, lColor2: TFPColor; i, j: Integer;
i, j, c: Integer; pf: Double; // fraction of path travelled along gradient vector
i1, i2: Integer; px, py: Double;
p, p1, p2: Double; phi, sinphi, cosphi: Double;
coord, coord1, coord2, dcoord: Double;
coordIsX: Boolean;
p1, p2: T2dPoint;
gv: T2dPoint; // gradient vector
gvlen: Double; // length of gradient vector
gstart: Double; // Gradient start point (1-dim)
dir: Integer;
begin begin
if not (Brush.Kind in [bkVerticalGradient, bkHorizontalGradient]) then if not (Brush.Kind in [bkVerticalGradient, bkHorizontalGradient, bkOtherLinearGradient]) then
Exit; Exit;
if Length(Brush.Gradient_colors) = 1 then // Direction of gradient vector. The gradient vector begins at color position 0%
begin // and ends at 100%.
lColor1 := Brush.Gradient_colors[0].Color; gv := Make2dPoint(AGradientEnd.X-AGradientStart.X, AGradientEnd.Y-AGradientStart.Y);
lColor2 := Brush.Gradient_colors[0].Color; gvlen := sqrt(sqr(gv.x) + sqr(gv.y));
c := 0; if gvlen = 0 then
end else exit;
begin
c := 0;
lColor1 := Brush.Gradient_colors[0].Color;
lColor2 := Brush.Gradient_colors[1].Color;
p1 := Brush.Gradient_colors[0].Position;
p2 := Brush.Gradient_colors[1].Position;
end;
// Find boundary points where the gradient starts and ends. The gradient is
// always travered from 0% to 100% color fractions.
p1 := Make2dPoint(
IfThen(AGradientEnd.x > AGradientStart.x, ARect.Left, ARect.Right),
IfThen(AGradientEnd.Y > AGradientStart.y, ARect.Top, ARect.Bottom)
);
p2 := Make2dPoint(
IfThen(AGradientEnd.x > AGradientStart.x, ARect.Right, ARect.Left),
IfThen(AGradientEnd.Y > AGradientStart.y, ARect.Bottom, ARect.Top)
);
// Prepare parameters and polygon points
ADest.Pen.Style := psSolid;
ADest.Pen.Width := 1;
SetLength(pts, Length(APoints));
case Brush.Kind of case Brush.Kind of
bkVerticalGradient: bkVerticalGradient:
begin // horizontal (!) lines have same color begin // Run vertically, horizontal lines have same color
i1 := y1; coord1 := p1.y;
i2 := y2; coord2 := p2.y;
dcoord := IfThen(AGradientEnd.Y > AGradientStart.Y, 1.0, -1.0);
gstart := coord1;
dir := round(dcoord);
for i := 0 to High(APoints) do
pts[i] := Make2DPoint(APoints[i].X, APoints[i].Y);
coordIsX := false;
gstart := coord1;
end; end;
bkHorizontalGradient: bkHorizontalGradient:
begin // vertical lines have same color begin // Run horizontally, vertical lines have same color
i1 := x1; coord1 := p1.x;
i2 := x2; coord2 := p2.x;
dcoord := IfThen(AGradientEnd.X > AGradientStart.X, 1.0, -1.0);
gstart := coord1;
dir := round(dcoord);
for i := 0 to High(APoints) do
pts[i] := Make2DPoint(APoints[i].X, APoints[i].Y);
coordIsX := true;
end;
bkOtherLinearGradient:
begin // Run along gradient vector, lines perpendicular to gradient vector
phi := arctan2(gv.y, gv.x);
Sincos(phi, sinphi, cosphi);
coordIsX := (abs(sinphi) <= sin(pi/4));
if not coordIsX then begin
phi := -(pi/2 - phi);
Sincos(phi, sinphi, cosphi);
end;
// p1 is the boundary point around which the shape is rotated in order to
// to get the gradient vector in horizontal or vertical direction for
// easier finding of intersection points.
// Projection of vector from GradientStart to p1 onto gradient vector
coord1 := (((p1.x - AGradientStart.X)*gv.x) + (p1.y - AGradientStart.Y)*gv.y) / gvlen;
// dto for p2.
coord2 := (((p2.x - AGradientStart.X)*gv.x) + (p2.y - AGradientStart.Y)*gv.Y) / gvlen;
// dcoord := 1.0 / abs(cosphi);
// dcoord := abs(cosphi);
dcoord := 1.0;
gstart := -coord1;
dir := +1;
// Rotate polygon point such that gradient axis is parallel to x axis
// (if angle < 45°) or y axis (if angle > 45°)
// Rotation center is the projection of the corner of the bounding box
// onto the gradient vector
p1 := Make2dPoint(
AGradientStart.X + coord1 * gv.x / gvlen,
AGradientStart.Y + coord1 * gv.y / gvlen
);
for j := 0 to High(APoints) do
begin
px := APoints[j].X - p1.x;
py := APoints[j].Y - p1.y;
pts[j] := Make2DPoint(px*cosPhi + py*sinPhi, -px*sinPhi + py*cosPhi);
end;
// Begin painting at corner
coord2 := coord2 - coord1;
coord1 := 0;
ADest.Pen.Width := 2; // make sure that there are no gaps due to rounding errors
end; end;
end; end;
ADest.Pen.Style := psSolid; // Draw gradient
for i := i1 to i2 do coord := coord1;
while ((dcoord > 0) and (coord <= coord2)) or (dcoord < 0) and (coord >= coord2) do
begin begin
lPoints := GetLinePolygonIntersectionPoints(i, APolyPoints, // Find intersection points of gradient line (normal to gradient vector)
Brush.Kind = bkHorizontalGradient); // with polygon
if Length(lPoints) < 2 then Continue; lPoints := GetLinePolygonIntersectionPoints(coord, pts, coordIsX);
p := (i-i1) / (i2-i1) * 100.0; // p = "percent" if Length(lPoints) < 2 then begin
// Use first color below first position coord := coord + dcoord;
if p < Brush.Gradient_colors[0].Position then Continue;
lColor := Brush.Gradient_colors[0].Color
else
// Use last color above last position
if p > Brush.Gradient_colors[High(Brush.Gradient_colors)].Position then
lColor := Brush.Gradient_colors[High(Brush.Gradient_colors)].Color
else
if (c < High(Brush.Gradient_colors)) then
begin
// Use current color pair if percentage is below next position
if (p < Brush.Gradient_colors[c+1].Position) then
lColor := MixColors(lColor2, lColor1, p-p1, p2-p1)
else
// Next next color pair if percentage is above next position
begin
inc(c);
p1 := p2;
p2 := Brush.Gradient_colors[c+1].Position;
lColor1 := lColor2;
lColor2 := Brush.Gradient_colors[c+1].Color;
lColor := MixColors(lColor2, lColor1, p-p1, p2-p1);
end;
end; end;
ADest.Pen.FPColor := lColor;
// Prepare intersection points for painting
case Brush.Kind of
bkVerticalGradient:
// Add loop variable as mssing y coordinate of intersection points
for j := 0 to High(lPoints) do lPoints[j].Y := coord;
bkHorizontalGradient:
// Add loop variable as mssing x coordinate of intersection points
for j := 0 to High(lPoints) do lPoints[j].X := coord;
bkOtherLinearGradient:
// Rotate back
for j := 0 to High(lPoints) do
lPoints[j] := Make2DPoint(
lPoints[j].X * cosPhi - lPoints[j].Y * sinPhi + p1.x,
lPoints[j].X * sinPhi + lPoints[j].Y * cosPhi + p1.y
);
end;
// WriteLn(Format('%.3f'#9'%.3f'#9'%.3f'#9'%.3f'#9'%.3f', [coord, lPoints[0].x, lPoints[0].Y, lPoints[1].x, lPoints[1].y]));
// Determine color from fraction (pf) of path travelled along gradient vector
pf := (coord - gstart) * dir / gvlen;
ADest.Pen.FPColor := GradientColor(Brush.Gradient_colors, pf);
// Draw gradient lines between intersection points
j := 0; j := 0;
while j < High(lPoints) do while j < High(lPoints) do
begin begin
case Brush.Kind of ADest.Line(round(lPoints[j].X), round(lPoints[j].Y), round(lPoints[j+1].X), round(lPoints[j+1].Y));
bkVerticalGradient : ADest.Line(lPoints[j].X, i, lPoints[j+1].X, i);
bkHorizontalGradient: ADest.Line(i, lPoints[j].Y, i, lPoints[j+1].Y);
end;
inc(j, 2); inc(j, 2);
end; end;
// Proceed to next line
coord := coord + dcoord;
end; end;
{
end
else if Brush.Kind = bkHorizontalGradient then // vertical (!) lines have same color
begin
for i := x1 to x2 do
begin
lPoints := GetLinePolygonIntersectionPoints(i, APolyPoints, True);
if Length(lPoints) < 2 then Continue;
lColor := MixColors(lColor2, lColor1, i-x1, x2-x1);
ADest.Pen.FPColor := lColor;
j := 0;
while (j < High(lPoints)) do
begin
ADest.Line(i, lPoints[j].Y, i, lPoints[j+1].Y);
inc(j , 2);
end;
end;
end;
}
end; end;
{ Fills the entity's shape with a gradient. { Fills the entity's shape with a gradient.
@ -4265,24 +4336,29 @@ begin
end; end;
end; end;
function CompareInt(P1, P2: Pointer): Integer; function CompareDbl(P1, P2: Pointer): Integer;
var var
i1, i2: PtrInt; val1, val2: ^Double;
begin begin
i1 := PtrInt(P1); val1 := P1;
i2 := PtrInt(P2); val2 := P2;
Result := CompareValue(i1, i2); Result := CompareValue(val1^, val2^);
end; end;
function TPath.GetLinePolygonIntersectionPoints(ACoord: Integer; function TPath.GetLinePolygonIntersectionPoints(ACoord: Double;
const APoints: TPointsArray; ACoordIsX: Boolean): TPointsArray; const APoints: T2DPointsArray; ACoordIsX: Boolean): T2DPointsArray;
const
EPS = 1e-9;
var var
j, dx, dy: Integer; j: Integer;
dx, dy: Double;
xval, yval: Double; xval, yval: Double;
val: ^Double;
list: TFPList; list: TFPList;
begin begin
list := TFPList.Create; list := TFPList.Create;
if ACoordIsX then if ACoordIsX then
begin
for j:=0 to High(APoints) - 1 do for j:=0 to High(APoints) - 1 do
begin begin
if ((APoints[j].X <= ACoord) and (ACoord < APoints[j+1].X)) or if ((APoints[j].X <= ACoord) and (ACoord < APoints[j+1].X)) or
@ -4290,33 +4366,41 @@ begin
begin begin
dx := APoints[j+1].X - APoints[j].X; // can't be zero here dx := APoints[j+1].X - APoints[j].X; // can't be zero here
dy := APoints[j+1].Y - APoints[j].Y; dy := APoints[j+1].Y - APoints[j].Y;
yval := APoints[j].Y + (ACoord - APoints[j].X) * dy / dx; New(val);
list.Add(pointer(PtrInt(round(yval)))); val^ := APoints[j].Y + (ACoord - APoints[j].X) * dy / dx;
end {else list.Add(val);
if ((APoints[j].X = ACoord) and (ACoord = APoints[j+1].X)) then end;
list.Add(pointer(PtrInt(APoints[j].Y)));} end;
end end else
else begin
for j:=0 to High(APoints) - 1 do for j:=0 to High(APoints) - 1 do
if ((APoints[j].Y <= ACoord) and (ACoord < APoints[j+1].Y)) or if ((APoints[j].Y <= ACoord) and (ACoord < APoints[j+1].Y)) or
((APoints[j+1].Y <= ACoord) and (ACoord < APoints[j].Y)) then ((APoints[j+1].Y <= ACoord) and (ACoord < APoints[j].Y)) then
begin begin
dy := APoints[j+1].Y - APoints[j].Y; // can't be zero here dy := APoints[j+1].Y - APoints[j].Y; // can't be zero here
dx := APoints[j+1].X - APoints[j].X; dx := APoints[j+1].X - APoints[j].X;
xval := APoints[j].X + (ACoord - APoints[j].Y) * dx / dy; New(val);
list.Add(pointer(PtrInt(round(xval)))); val^ := APoints[j].X + (ACoord - APoints[j].Y) * dx / dy;
end {else list.Add(val);
if ((APoints[j].Y = ACoord) and (ACoord = APoints[j+1].Y)) then end;
list.Add(pointer(PtrInt(APoints[j].X)))}; end;
// Sort intersection coordinates in ascending order // Sort intersection coordinates in ascending order
list.Sort(@CompareInt); list.Sort(@CompareDbl);
SetLength(Result, list.Count); SetLength(Result, list.Count);
if ACoordIsX then if ACoordIsX then
for j:=0 to list.Count-1 do for j:=0 to list.Count-1 do
Result[j] := Point(ACoord, PtrInt(list[j])) Result[j] := Make2DPoint(ACoord, Double(list[j]^))
else else
for j:=0 to list.Count-1 do for j:=0 to list.Count-1 do
Result[j] := Point(PtrInt(list[j]), ACoord); Result[j] := Make2DPoint(Double(list[j]^), ACoord);
// Clean-up
for j:=list.Count-1 downto 0 do
begin
val := List[j];
Dispose(val);
end;
list.Free; list.Free;
end; end;
@ -4343,12 +4427,12 @@ begin
begin begin
seg2D := T2DSegment(seg); seg2D := T2DSegment(seg);
if p.X < seg2D.X then begin if p.X < seg2D.X then begin
p1 := Make2DPoint(p.X, p.Y); p1 := Make3DPoint(p.X, p.Y, 0);
p2 := Make2DPoint(seg2D.X, seg2D.Y); p2 := Make3DPoint(seg2D.X, seg2D.Y, 0);
end else end else
begin begin
p1 := Make2DPoint(seg2D.X, seg2D.Y); p1 := Make3DPoint(seg2D.X, seg2D.Y, 0);
p2 := Make2DPoint(p.X, p.Y); p2 := Make3DPoint(p.X, p.Y, 0);
end; end;
if (p1.X < ACoord) and (ACoord <= p2.X) then if (p1.X < ACoord) and (ACoord <= p2.X) then
begin begin
@ -4370,12 +4454,12 @@ begin
seg2D := T2DSegment(seg); seg2D := T2DSegment(seg);
if p.Y < seg2D.Y then if p.Y < seg2D.Y then
begin begin
p1 := Make2DPoint(p.X, p.Y); p1 := Make3DPoint(p.X, p.Y, 0);
p2 := Make2DPoint(seg2D.X, seg2D.Y); p2 := Make3DPoint(seg2D.X, seg2D.Y, 0);
end else end else
begin begin
p1 := Make2DPoint(seg2D.X, seg2D.Y); p1 := Make3DPoint(seg2D.X, seg2D.Y, 0);
p2 := Make2DPoint(p.X, p.Y); p2 := Make3DPoint(p.X, p.Y, 0);
end; end;
if (p1.Y < ACoord) and (ACoord <= p2.Y) then if (p1.Y < ACoord) and (ACoord <= p2.Y) then
begin begin
@ -4402,6 +4486,8 @@ var
coordX, coordY: Integer; coordX, coordY: Integer;
curSegment: TPathSegment; curSegment: TPathSegment;
cur2DSegment: T2DSegment absolute curSegment; cur2DSegment: T2DSegment absolute curSegment;
lRect: TRect;
gv1, gv2: T2DPoint;
begin begin
inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY, ADoDraw); inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY, ADoDraw);
@ -4443,7 +4529,42 @@ begin
{$ENDIF} {$ENDIF}
end; end;
else // gradients else // gradients
DrawPolygonBrushGradient(ADest, FPolyPoints, x1, y1, x2, y2); // Boundary rect of shape filled with a gradient
lRect := Rect(x1, y1, x2, y2);
// calculate gradient vector
gv1 := Make2DPoint(Brush.Gradient_start.X, Brush.Gradient_start.Y);
gv2 := Make2DPoint(Brush.Gradient_end.X, Brush.Gradient_end.Y);
if (gfRelToUserSpace in Brush.Gradient_flags) then
begin
if (gfRelStartX in Brush.Gradient_flags) then
gv1.X := gv1.X * FPage.Width;
if (gfRelStartY in Brush.Gradient_flags) then
gv1.Y := gv1.Y * FPage.Height;
if (gfRelEndX in Brush.Gradient_flags) then
gv2.X := gv2.X * FPage.Width;
if (gfRelEndY in Brush.Gradient_flags) then
gv2.Y := gv2.Y * FPage.Height;
gv1.X := CoordToCanvasX(gv1.X, ADestX, AMulX);
gv1.Y := CoordToCanvasY(gv1.Y, ADestY, AMulY);
gv2.X := CoordToCanvasX(gv2.X, ADestX, AMulX);
gv2.Y := CoordToCanvasY(gv2.Y, ADestY, AMulY);
end else
begin
if (gfRelStartX in Brush.Gradient_flags) then
gv1.X := x1 + gv1.X * (x2 - x1) else
gv1.X := CoordToCanvasX(gv1.X, ADestX, AMulX);
if (gfRelStartY in Brush.Gradient_flags) then
gv1.Y := y1 + gv1.Y * (y2 - y1) else
gv1.Y := CoordToCanvasY(gv1.Y, ADestY, AMulY);
if (gfRelEndX in Brush.Gradient_flags) then
gv2.X := x1 + gv2.X * (x2 - x1) else
gv2.X := CoordToCanvasX(gv2.X, ADestX, AMulX);
if (gfRelEndY in Brush.Gradient_flags) then
gv2.Y := y1 + gv2.Y * (y2 - y1) else
gv2.Y := CoordToCanvasY(gv2.Y, ADestY, AMulY);
end;
// Draw the gradient
DrawPolygonBrushGradient(ADest, FPolyPoints, lRect, gv1, gv2);
// to do: multiple polygons! // to do: multiple polygons!
end; end;

View File

@ -42,6 +42,7 @@ type
function FPColorToRGBHexString(AColor: TFPColor): string; function FPColorToRGBHexString(AColor: TFPColor): string;
function RGBToFPColor(AR, AG, AB: byte): TFPColor; inline; function RGBToFPColor(AR, AG, AB: byte): TFPColor; inline;
function MixColors(AColor1, AColor2: TFPColor; APos, AMax: Double): TFPColor; function MixColors(AColor1, AColor2: TFPColor; APos, AMax: Double): TFPColor;
function GradientColor(AColors: TvGradientColors; AValue: Double): TFPColor;
// Coordinate Conversion routines // Coordinate Conversion routines
function CanvasCoordsToFPVectorial(AY: Integer; AHeight: Integer): Integer; inline; function CanvasCoordsToFPVectorial(AY: Integer; AHeight: Integer): Integer; inline;
function CanvasTextPosToFPVectorial(AY: Integer; ACanvasHeight, ATextHeight: Integer): Integer; function CanvasTextPosToFPVectorial(AY: Integer; ACanvasHeight, ATextHeight: Integer): Integer;
@ -49,6 +50,7 @@ function CoordToCanvasX(ACoord: Double; ADestX: Integer; AMulX: Double): Integer
function CoordToCanvasY(ACoord: Double; ADestY: Integer; AMulY: Double): Integer; inline; function CoordToCanvasY(ACoord: Double; ADestY: Integer; AMulY: Double): Integer; inline;
// Other routines // Other routines
function SeparateString(AString: string; ASeparator: char): T10Strings; function SeparateString(AString: string; ASeparator: char): T10Strings;
function Make2DPoint(AX, AY: Double): T2DPoint;
function Make3DPoint(AX, AY, AZ: Double): T3DPoint; function Make3DPoint(AX, AY, AZ: Double): T3DPoint;
// Mathematical routines // Mathematical routines
function LineEquation_GetPointAndTangentForLength(AStart, AEnd: T3DPoint; ADistance: Double; out AX, AY, ATangentAngle: Double): Boolean; function LineEquation_GetPointAndTangentForLength(AStart, AEnd: T3DPoint; ADistance: Double; out AX, AY, ATangentAngle: Double): Boolean;
@ -115,12 +117,48 @@ begin
Result.Alpha := $FFFF; Result.Alpha := $FFFF;
end; end;
{@@ Returns AColor1 if APos = 0, AColor2 if APos = AMax, or interpolates between }
function MixColors(AColor1, AColor2: TFPColor; APos, AMax: Double): TFPColor; function MixColors(AColor1, AColor2: TFPColor; APos, AMax: Double): TFPColor;
var
f1, f2: Double;
begin begin
Result.Alpha := Round(AColor1.Alpha * APos / AMax + AColor2.Alpha * (AMax - APos) / AMax); f1 := (AMax - APos) / AMax;
Result.Red := Round(AColor1.Red * APos / AMax + AColor2.Red * (AMax - APos) / AMax); f2 := APos / AMax;
Result.Green := Round(AColor1.Green * APos / AMax + AColor2.Green * (AMax - APos) / AMax); Result.Alpha := Round(AColor1.Alpha * f1 + AColor2.Alpha * f2);
Result.Blue := Round(AColor1.Blue * APos / AMax + AColor2.Blue * (AMax - APos) / AMax); Result.Red := Round(AColor1.Red * f1 + AColor2.Red * f2);
Result.Green := Round(AColor1.Green * f1 + AColor2.Green * f2);
Result.Blue := Round(AColor1.Blue * f1 + AColor2.Blue * f2);
end;
{@@ Assigns a color to the specified value. The color is interpolated between
the colors defined in AColors.
}
function GradientColor(AColors: TvGradientColors; AValue: Double): TFPColor;
var
i: Integer;
c1, c2: TFPColor;
p1, p2: Double;
begin
// Return first color if AValue is below the first color position
if AValue <= AColors[0].Position then
Result := AColors[0].Color
else
// Return last color if AValue is above the last color position
if AValue >= AColors[High(AColors)].Position then
Result := AColors[High(AColors)].Color
else
// Find pair of colors positions which bracket the specified value and
// interpolate color
for i:= High(AColors)-1 downto 0 do
if AValue >= AColors[i].Position then
begin
c1 := AColors[i].Color;
c2 := AColors[i+1].Color;
p1 := AColors[i].Position;
p2 := AColors[i+1].Position;
Result := MixColors(c1, c2, AValue - p1, p2 - p1);
exit;
end;
end; end;
{@@ Converts the coordinate system from a TCanvas to FPVectorial {@@ Converts the coordinate system from a TCanvas to FPVectorial
@ -193,6 +231,12 @@ begin
end; end;
end; end;
function Make2DPoint(AX, AY: Double): T2DPoint;
begin
Result.X := AX;
Result.Y := AY;
end;
function Make3DPoint(AX, AY, AZ: Double): T3DPoint; function Make3DPoint(AX, AY, AZ: Double): T3DPoint;
begin begin
Result.X := AX; Result.X := AX;
@ -616,10 +660,10 @@ begin
CoordX3 := CoordToCanvasX(Cur2DBSegment.X3, ADestX, AMulX); CoordX3 := CoordToCanvasX(Cur2DBSegment.X3, ADestX, AMulX);
CoordY3 := CoordToCanvasY(Cur2DBSegment.Y3, ADestY, AMulY); CoordY3 := CoordToCanvasY(Cur2DBSegment.Y3, ADestY, AMulY);
AddBezierToPoints( AddBezierToPoints(
Make2DPoint(CoordX, CoordY), Make3DPoint(CoordX, CoordY, 0),
Make2DPoint(CoordX2, CoordY2), Make3DPoint(CoordX2, CoordY2, 0),
Make2DPoint(CoordX3, CoordY3), Make3DPoint(CoordX3, CoordY3, 0),
Make2DPoint(CoordX4, CoordY4), Make3DPoint(CoordX4, CoordY4, 0),
Points); Points);
end; end;
else else

View File

@ -137,6 +137,7 @@ type
function StringWithUnitToFloat(AStr: string; ACoordKind: TSVGCoordinateKind = sckUnknown; function StringWithUnitToFloat(AStr: string; ACoordKind: TSVGCoordinateKind = sckUnknown;
ADefaultUnit: TSVGUnit = suPX; ATargetUnit: TSVGUnit = suPX): Double; ADefaultUnit: TSVGUnit = suPX; ATargetUnit: TSVGUnit = suPX): Double;
function StringFloatZeroToOneToWord(AStr: string): Word; function StringFloatZeroToOneToWord(AStr: string): Word;
function StringWithPercentToFloat(AStr: String): Double;
procedure ConvertSVGCoordinatesToFPVCoordinates( procedure ConvertSVGCoordinatesToFPVCoordinates(
const AData: TvVectorialPage; const AData: TvVectorialPage;
@ -1235,7 +1236,7 @@ var
lBrushEntity: TvEntityWithPenAndBrush; lBrushEntity: TvEntityWithPenAndBrush;
lCurEntity: TvEntity; lCurEntity: TvEntity;
lGradientColor: TvGradientColor; lGradientColor: TvGradientColor;
x1, x2, y1, y2: string; x1, y1, x2, y2: Double;
begin begin
lCurNode := ANode.FirstChild; lCurNode := ANode.FirstChild;
while Assigned(lCurNode) do while Assigned(lCurNode) do
@ -1307,24 +1308,76 @@ begin
lBrushEntity := TvEntityWithPenAndBrush.Create(nil); lBrushEntity := TvEntityWithPenAndBrush.Create(nil);
// <linearGradient id="grad1" x1="0%" y1="0%" x2="0%" y2="100%"> // <linearGradient id="grad1" x1="0%" y1="0%" x2="0%" y2="100%">
x1 := 0;
x2 := 0;
y1 := 0;
y2 := 0;
for i := 0 to lCurNode.Attributes.Length - 1 do for i := 0 to lCurNode.Attributes.Length - 1 do
begin begin
lAttrName := lCurNode.Attributes.Item[i].NodeName; lAttrName := lowercase(lCurNode.Attributes.Item[i].NodeName);
lAttrValue := lCurNode.Attributes.Item[i].NodeValue; lAttrValue := lowercase(lCurNode.Attributes.Item[i].NodeValue);
if lAttrName = 'id' then if lAttrName = 'id' then
lBrushEntity.Name := lAttrValue lBrushEntity.Name := lAttrValue
else if lAttrName = 'x1' then else
x1 := lAttrValue if lAttrName = 'x1' then
else if lAttrName = 'x2' then begin
x2 := lAttrValue if lAttrValue[Length(lAttrValue)] = '%' then
else if lAttrName = 'y1' then Include(lBrushEntity.Brush.Gradient_flags, gfRelStartX);
y1 := lAttrValue x1 := StringWithPercentToFloat(lAttrValue);
else if lAttrName = 'y2' then end else
y2 := lAttrValue; if lAttrName = 'x2' then
begin
if lAttrValue[Length(lAttrValue)] = '%' then
Include(lBrushEntity.Brush.Gradient_flags, gfRelEndX);
x2 := StringWithPercentToFloat(lAttrValue);
end else
if lAttrName = 'y1' then
begin
if lAttrValue[Length(lAttrValue)] = '%' then
Include(lBrushEntity.Brush.Gradient_flags, gfRelStartY);
y1 := StringWithPercentToFloat(lAttrValue);
end else
if lAttrName = 'y2' then
begin
if lAttrValue[Length(lAttrValue)] = '%' then
Include(lBrushEntity.Brush.Gradient_flags, gfRelEndY);
y2 := StringWithPercentToFloat(lAttrValue);
end else
if lAttrName = 'gradientunits' then
begin
if lAttrValue = 'userspaceonuse' then
Include(lBrushEntity.Brush.Gradient_flags, gfRelToUserSpace)
else if lAttrValue = 'objectboundingbox' then
Exclude(lBrushEntity.Brush.Gradient_flags, gfRelToUserSpace);
end;
end; end;
if x2 = x1 then lBrushEntity.Brush.Gradient_start.X := x1;
lBrushEntity.Brush.Gradient_end.X := x2;
lBrushEntity.Brush.Gradient_start.Y := y1;
lBrushEntity.Brush.Gradient_end.Y := y2;
ConvertSVGCoordinatesToFPVCoordinates(AData, x1, y1, x1, y1);
ConvertSVGCoordinatesToFPVCoordinates(AData, x2, y2, x2, y2);
if not (gfRelStartX in lBrushEntity.Brush.Gradient_flags) then
lBrushEntity.Brush.Gradient_start.X := x1;
if not (gfRelEndX in lBrushEntity.Brush.Gradient_flags) then
lBrushEntity.Brush.Gradient_end.X := x2;
if not (gfRelStartY in lBrushEntity.Brush.Gradient_flags) then
lBrushEntity.Brush.Gradient_start.Y := y1;
if not (gfRelEndY in lBrushEntity.Brush.Gradient_flags) then
lBrushEntity.Brush.Gradient_end.Y := y2;
if (lBrushEntity.Brush.Gradient_start.X = 0) and
(lBrushEntity.Brush.Gradient_start.Y = 0) and
(lBrushEntity.Brush.Gradient_end.X = 0) and
(lBrushEntity.Brush.Gradient_end.Y = 0) then
begin
lBrushEntity.Brush.Gradient_start.X := 0.0;
lBrushEntity.Brush.Gradient_start.Y := 0.0;
lBrushEntity.Brush.Gradient_end.X := 1.0;
lBrushEntity.Brush.Gradient_end.Y := 1.0;
end;
if lBrushEntity.Brush.Gradient_start.X = lBrushEntity.Brush.Gradient_end.X then
lBrushEntity.Brush.Kind := bkVerticalGradient lBrushEntity.Brush.Kind := bkVerticalGradient
else if y2=y1 then else if lBrushEntity.Brush.Gradient_start.Y = lBrushEntity.Brush.Gradient_end.Y then
lBrushEntity.Brush.Kind := bkHorizontalGradient lBrushEntity.Brush.Kind := bkHorizontalGradient
else else
lBrushEntity.Brush.Kind := bkOtherLinearGradient; lBrushEntity.Brush.Kind := bkOtherLinearGradient;
@ -1341,7 +1394,8 @@ begin
lAttrName := lCurSubNode.Attributes.Item[i].NodeName; lAttrName := lCurSubNode.Attributes.Item[i].NodeName;
lAttrValue := lCurSubNode.Attributes.Item[i].NodeValue; lAttrValue := lCurSubNode.Attributes.Item[i].NodeValue;
if lAttrName = 'offset' then if lAttrName = 'offset' then
lGradientColor.Position := StringWithUnitToFloat(lAttrValue) lGradientColor.Position := StringWithPercentToFloat(lAttrValue)
// use as fraction 0..1
else if lAttrName = 'style' then else if lAttrName = 'style' then
lGradientColor.Color := ReadSVGGradientColorStyle(lAttrValue) lGradientColor.Color := ReadSVGGradientColorStyle(lAttrValue)
else if lAttrName = 'stop-color' then else if lAttrName = 'stop-color' then
@ -3027,6 +3081,19 @@ begin
Result := Round(StrToFloat(AStr, FPointSeparator) * $FFFF); Result := Round(StrToFloat(AStr, FPointSeparator) * $FFFF);
end; end;
{@@ Converts a number string to a floating-point number. If the string has a
% character at its end then it is removed, and the numerical value is
divided by 100. }
function TvSVGVectorialReader.StringWithPercentToFloat(AStr: String): Double;
begin
if AStr[Length(AStr)] = '%' then
begin
Delete(AStr, Length(AStr), 1);
Result := 0.01 * StrToFloat(trim(AStr), FPointSeparator);
end else
Result := StrToFloat(AStr, FPointSeparator);
end;
procedure TvSVGVectorialReader.ConvertSVGCoordinatesToFPVCoordinates( procedure TvSVGVectorialReader.ConvertSVGCoordinatesToFPVCoordinates(
const AData: TvVectorialPage; const ASrcX, ASrcY: Double; const AData: TvVectorialPage; const ASrcX, ASrcY: Double;
var ADestX,ADestY: Double; ADoViewBoxAdjust: Boolean = True); var ADestX,ADestY: Double; ADoViewBoxAdjust: Boolean = True);