mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-11-02 19:47:02 +01:00
fpvectorial: Fix TPath to avoid rendering of internal lines for bezier segments. Fix elliptic path segment with rotated axis.
git-svn-id: trunk@51021 -
This commit is contained in:
parent
44acd99a0f
commit
3cab480c11
@ -101,6 +101,7 @@ const
|
||||
// Convenience constant to convert text size points to mm
|
||||
FPV_TEXT_POINT_TO_MM = 0.35278;
|
||||
|
||||
TWO_PI = 2.0 * pi;
|
||||
|
||||
type
|
||||
TvCustomVectorialWriter = class;
|
||||
@ -273,6 +274,8 @@ type
|
||||
|
||||
P3DPoint = ^T3DPoint;
|
||||
|
||||
T3DPointsArray = array of T3DPoint;
|
||||
|
||||
TSegmentType = (
|
||||
st2DLine, st2DLineWithPen, st2DBezier,
|
||||
st3DLine, st3DBezier, stMoveTo,
|
||||
@ -390,8 +393,10 @@ type
|
||||
LeftmostEllipse, ClockwiseArcFlag: Boolean;
|
||||
CX, CY: Double; // Ellipse center
|
||||
CenterSetByUser: Boolean; // defines if we should use LeftmostEllipse to calculate the center, or if CX, CY is set directly
|
||||
procedure BezierApproximate(var Points: T3dPointsArray);
|
||||
procedure PolyApproximate(var Points: T3dPointsArray);
|
||||
procedure CalculateCenter;
|
||||
procedure CalculateEllipseBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double);
|
||||
procedure CalculateEllipseBoundingBox(ADest: TFPCustomCanvas; out ALeft, ATop, ARight, ABottom: Double);
|
||||
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
|
||||
end;
|
||||
|
||||
@ -2642,6 +2647,93 @@ begin
|
||||
if Result < 0 then Result := -1* Result;
|
||||
end;
|
||||
|
||||
procedure T2DEllipticalArcSegment.BezierApproximate(var Points: T3dPointsArray);
|
||||
var
|
||||
P1, P2, P3, P4: T3dPoint;
|
||||
startangle, endangle: Double;
|
||||
startanglePi2, endanglePi2: Double;
|
||||
xstart, ystart: Double;
|
||||
nextx, nexty: Double;
|
||||
angle: Double;
|
||||
n: Integer;
|
||||
begin
|
||||
SetLength(Points, 30);
|
||||
n := 0;
|
||||
|
||||
xstart := T2DSegment(Previous).X;
|
||||
ystart := T2DSegment(Previous).Y;
|
||||
startangle := CalcEllipsePointAngle(xstart, ystart, RX, RY, CX, CY, XRotation);
|
||||
endangle := CalcEllipsePointAngle(X, Y, RX, RY, CX, CY, XRotation);
|
||||
if endangle < 0 then endangle := 2*pi + endangle;
|
||||
|
||||
angle := arctan2(-1,1);
|
||||
angle := radtodeg(angle);
|
||||
|
||||
angle := radtodeg(startangle);
|
||||
angle := radtodeg(endangle);
|
||||
|
||||
// Since the algorithm for bezier approximation requires that the angle
|
||||
// between start and end is at most pi/3 we have to progress in pi/3 steps.
|
||||
angle := startangle + pi/3;
|
||||
while true do
|
||||
begin
|
||||
if angle >= endangle then begin
|
||||
EllipticalArcToBezier(CX, CY, RX, RY, startAngle, endangle, Points[n], Points[n+1], Points[n+2], Points[n+3]);
|
||||
inc(n, 4);
|
||||
break;
|
||||
end else
|
||||
EllipticalArcToBezier(CX, CY, RX, RY, startangle, angle, Points[n], Points[n+1], Points[n+2], Points[n+3]);
|
||||
inc(n, 4);
|
||||
startangle := angle;
|
||||
angle := angle + pi/2;
|
||||
end;
|
||||
SetLength(Points, n);
|
||||
end;
|
||||
|
||||
procedure T2DEllipticalArcSegment.PolyApproximate(var Points: T3dPointsArray);
|
||||
const
|
||||
BUFSIZE = 100;
|
||||
var
|
||||
t, tstart, tend, dt: Double;
|
||||
xstart, ystart: Double;
|
||||
n: Integer;
|
||||
done: Boolean;
|
||||
begin
|
||||
n := 0;
|
||||
SetLength(Points, BUFSIZE);
|
||||
|
||||
dt := DegToRad(2.0); // 2-degree increments
|
||||
|
||||
xstart := T2DSegment(Previous).X;
|
||||
ystart := T2DSegment(Previous).Y;
|
||||
tstart := CalcEllipsePointAngle(xstart, ystart, RX, RY, CX, CY, XRotation);
|
||||
tend := CalcEllipsePointAngle(X, Y, RX, RY, CX, CY, XRotation);
|
||||
if ClockwiseArcFlag then
|
||||
begin // tend must be smaller than tstart
|
||||
if tend < tstart then tend := TWO_PI + tend;
|
||||
end else begin // tstart must be smaller than tend
|
||||
if tstart < tend then tstart := TWO_PI + tstart;
|
||||
dt := -dt;
|
||||
end;
|
||||
|
||||
done := false;
|
||||
t := tstart;
|
||||
while not done do begin
|
||||
if (ClockwiseArcFlag and (t > tend)) or
|
||||
(not ClockwiseArcFlag and (t < tend)) then
|
||||
begin
|
||||
t := tend;
|
||||
done := true;
|
||||
end;
|
||||
if n >= Length(Points) then
|
||||
SetLength(Points, Length(Points) + BUFSIZE);
|
||||
CalcEllipsePoint(t, RX, RY, CX, CY, XRotation, Points[n].x, Points[n].y);
|
||||
inc(n);
|
||||
t := t + dt; // Note: dt is <0 in counter-clockwise case
|
||||
end;
|
||||
SetLength(Points, n);
|
||||
end;
|
||||
|
||||
// wp: no longer needed...
|
||||
procedure T2DEllipticalArcSegment.CalculateCenter;
|
||||
var
|
||||
@ -2738,7 +2830,7 @@ begin
|
||||
end;
|
||||
|
||||
procedure T2DEllipticalArcSegment.CalculateEllipseBoundingBox(ADest: TFPCustomCanvas;
|
||||
var ALeft, ATop, ARight, ABottom: Double);
|
||||
out ALeft, ATop, ARight, ABottom: Double);
|
||||
var
|
||||
t1, t2, t3: Double;
|
||||
x1, x2, x3: Double;
|
||||
@ -2787,35 +2879,36 @@ begin
|
||||
else
|
||||
begin
|
||||
// Search for the minimum and maximum X
|
||||
// There are two solutions in each 2pi range
|
||||
t1 := arctan(-RY*tan(XRotation)/RX);
|
||||
t2 := arctan(-RY*tan(XRotation)/RX) + Pi/2;
|
||||
t3 := arctan(-RY*tan(XRotation)/RX) + Pi;
|
||||
t2 := arctan(-RY*tan(XRotation)/RX) + pi; //Pi/2; // why add pi/2 ??
|
||||
// t3 := arctan(-RY*tan(XRotation)/RX) + Pi;
|
||||
|
||||
x1 := Cx + RX*Cos(t1)*Cos(XRotation)-RY*Sin(t1)*Sin(XRotation);
|
||||
x2 := Cx + RX*Cos(t2)*Cos(XRotation)-RY*Sin(t2)*Sin(XRotation);
|
||||
x3 := Cx + RX*Cos(t3)*Cos(XRotation)-RY*Sin(t3)*Sin(XRotation);
|
||||
// x3 := Cx + RX*Cos(t3)*Cos(XRotation)-RY*Sin(t3)*Sin(XRotation);
|
||||
|
||||
ALeft := Min(x1, x2);
|
||||
ALeft := Min(ALeft, x3);
|
||||
// ALeft := Min(ALeft, x3);
|
||||
|
||||
ARight := Max(x1, x2);
|
||||
ARight := Max(ARight, x3);
|
||||
//ARight := Max(ARight, x3);
|
||||
|
||||
// Now the same for Y
|
||||
|
||||
t1 := arctan(RY*cotan(XRotation)/RX);
|
||||
t2 := arctan(RY*cotan(XRotation)/RX) + Pi/2;
|
||||
t3 := arctan(RY*cotan(XRotation)/RX) + 3*Pi/2;
|
||||
t2 := arctan(RY*cotan(XRotation)/RX) + pi; //Pi/2; // why add pi/2 ??
|
||||
// t3 := arctan(RY*cotan(XRotation)/RX) + 3*Pi/2;
|
||||
|
||||
y1 := CY + RY*Sin(t1)*Cos(XRotation)+RX*Cos(t1)*Sin(XRotation);
|
||||
y2 := CY + RY*Sin(t2)*Cos(XRotation)+RX*Cos(t2)*Sin(XRotation);
|
||||
y3 := CY + RY*Sin(t3)*Cos(XRotation)+RX*Cos(t3)*Sin(XRotation);
|
||||
// y3 := CY + RY*Sin(t3)*Cos(XRotation)+RX*Cos(t3)*Sin(XRotation);
|
||||
|
||||
ATop := Max(y1, y2);
|
||||
ATop := Max(ATop, y3);
|
||||
// ATop := Max(ATop, y3);
|
||||
|
||||
ABottom := Min(y1, y2);
|
||||
ABottom := Min(ABottom, y3);
|
||||
// ABottom := Min(ABottom, y3);
|
||||
{
|
||||
ATop := Min(y1, y2);
|
||||
ATop := Min(ATop, y3);
|
||||
@ -4033,8 +4126,10 @@ procedure TPath.Render(ADest: TFPCustomCanvas; var ARenderInfo: TvRenderInfo; AD
|
||||
end;
|
||||
end;
|
||||
|
||||
const
|
||||
POINT_BUFFER = 100;
|
||||
var
|
||||
j: Integer;
|
||||
i, j: Integer;
|
||||
PosX, PosY: Double; // Not modified by ADestX, etc
|
||||
CoordX, CoordY: Integer;
|
||||
CurSegment: TPathSegment;
|
||||
@ -4043,10 +4138,11 @@ var
|
||||
Cur2DArcSegment: T2DEllipticalArcSegment absolute CurSegment;
|
||||
x1, y1, x2, y2: Integer;
|
||||
// For bezier
|
||||
CoordX2, CoordY2, CoordX3, CoordY3, CoordX4, CoordY4: Integer;
|
||||
//t: Double;
|
||||
CoordX2, CoordY2, CoordX3, CoordY3, CoordX4, CoordY4, CoordX5, CoordY5: Integer;
|
||||
// For polygons
|
||||
lPoints: array of TPoint;
|
||||
lPoints, pts: array of TPoint;
|
||||
NumPoints: Integer;
|
||||
pts3d: T3dPointsArray = nil;
|
||||
// for elliptical arcs
|
||||
BoxLeft, BoxTop, BoxRight, BoxBottom: Double;
|
||||
EllipseRect: TRect;
|
||||
@ -4063,7 +4159,7 @@ begin
|
||||
// ADest.Brush.Style := bsClear;
|
||||
|
||||
ADest.MoveTo(ADestX, ADestY);
|
||||
{
|
||||
(*
|
||||
// Set the path Pen and Brush options
|
||||
ADest.Pen.Style := Pen.Style;
|
||||
ADest.Pen.Width := Round(Pen.Width * AMulX);
|
||||
@ -4076,7 +4172,7 @@ begin
|
||||
ACanvas.Pen.SetPattern(Pen.Pattern);
|
||||
{$endif}
|
||||
ADest.Brush.FPColor := Brush.Color;
|
||||
}
|
||||
*)
|
||||
// Prepare the Clipping Region, if any
|
||||
{$ifdef USE_CANVAS_CLIP_REGION}
|
||||
if ClipPath <> nil then
|
||||
@ -4095,8 +4191,10 @@ begin
|
||||
{$endif}
|
||||
|
||||
// useful in some paths, like stars!
|
||||
{ -- wp: causes artifacts in case of concave path
|
||||
if ADoDraw then
|
||||
RenderInternalPolygon(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
|
||||
}
|
||||
|
||||
if CanFill then
|
||||
begin
|
||||
@ -4122,11 +4220,7 @@ begin
|
||||
DrawBrushGradient(ADest, ARenderInfo, x1, y1, x2, y2, ADestX, ADestY, AMulX, AMulY);
|
||||
end;
|
||||
{$ENDIF}
|
||||
end
|
||||
else
|
||||
// Paths with curved segments cannot be filled properly at the moment.
|
||||
// Better to have no fill at all...
|
||||
Brush.Style := bsClear;
|
||||
end;
|
||||
|
||||
//
|
||||
// For other paths, draw more carefully
|
||||
@ -4134,6 +4228,9 @@ begin
|
||||
ApplyPenToCanvas(ADest, ARenderInfo, Pen); // Restore pen
|
||||
PrepareForSequentialReading;
|
||||
|
||||
SetLength(lPoints, POINT_BUFFER);
|
||||
NumPoints := 0;
|
||||
|
||||
for j := 0 to Len - 1 do
|
||||
begin
|
||||
//WriteLn('j = ', j);
|
||||
@ -4144,8 +4241,32 @@ begin
|
||||
begin
|
||||
CoordX := CoordToCanvasX(Cur2DSegment.X, ADestX, AMulX);
|
||||
CoordY := CoordToCanvasY(Cur2DSegment.Y, ADestY, AMulY);
|
||||
|
||||
if ADoDraw then
|
||||
begin
|
||||
// Draw previous polygon
|
||||
if NumPoints > 0 then
|
||||
begin
|
||||
SetLength(lPoints, NumPoints);
|
||||
if Length(lPoints) = 2 then
|
||||
ADest.Line(lPoints[0].X, lPoints[0].Y, lPoints[1].X, lPoints[1].Y)
|
||||
else
|
||||
ADest.Polygon(lPoints);
|
||||
// Start new polygon
|
||||
SetLength(lPoints, POINT_BUFFER);
|
||||
NumPoints := 0;
|
||||
end;
|
||||
|
||||
lPoints[0].X := CoordX;
|
||||
lPoints[0].Y := CoordY;
|
||||
NumPoints := 1;
|
||||
end;
|
||||
|
||||
|
||||
{
|
||||
if ADoDraw then
|
||||
ADest.MoveTo(CoordX, CoordY);
|
||||
}
|
||||
CalcEntityCanvasMinMaxXY(ARenderInfo, CoordX, CoordY);
|
||||
PosX := Cur2DSegment.X;
|
||||
PosY := Cur2DSegment.Y;
|
||||
@ -4153,7 +4274,12 @@ begin
|
||||
Write(Format(' M%d,%d', [CoordX, CoordY]));
|
||||
{$endif}
|
||||
end;
|
||||
|
||||
// This element can override temporarely the Pen
|
||||
|
||||
// TO DO: Paint these segments with correct pen at end !!!!
|
||||
|
||||
|
||||
st2DLineWithPen:
|
||||
begin
|
||||
ADest.Pen.FPColor := AdjustColorToBackground(T2DSegmentWithPen(Cur2DSegment).Pen.Color, ARenderInfo);
|
||||
@ -4163,7 +4289,14 @@ begin
|
||||
CoordY2 := CoordToCanvasY(Cur2DSegment.Y, ADestY, AMulY);
|
||||
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX, CoordY, CoordX2, CoordY2);
|
||||
if ADoDraw then
|
||||
ADest.Line(CoordX, CoordY, CoordX2, CoordY2);
|
||||
begin
|
||||
if NumPoints >= Length(lPoints) then
|
||||
SetLength(lPoints, Length(lPoints) + POINT_BUFFER);
|
||||
lPoints[NumPoints].X := CoordX2;
|
||||
lPoints[NumPoints].Y := CoordY2;
|
||||
inc(NumPoints);
|
||||
// ADest.Line(CoordX, CoordY, CoordX2, CoordY2);
|
||||
end;
|
||||
|
||||
PosX := Cur2DSegment.X;
|
||||
PosY := Cur2DSegment.Y;
|
||||
@ -4174,6 +4307,7 @@ begin
|
||||
Write(Format(' L%d,%d', [CoordX2, CoordY2]));
|
||||
{$endif}
|
||||
end;
|
||||
|
||||
st2DLine, st3DLine:
|
||||
begin
|
||||
CoordX := CoordToCanvasX(PosX, ADestX, AMulX);
|
||||
@ -4182,13 +4316,21 @@ begin
|
||||
CoordY2 := CoordToCanvasY(Cur2DSegment.Y, ADestY, AMulY);
|
||||
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX, CoordY, CoordX2, CoordY2);
|
||||
if ADoDraw then
|
||||
ADest.Line(CoordX, CoordY, CoordX2, CoordY2);
|
||||
begin
|
||||
if NumPoints >= Length(lPoints) then
|
||||
SetLength(lPoints, Length(lPoints) + POINT_BUFFER);
|
||||
lPoints[NumPoints].X := CoordX2;
|
||||
lPoints[NumPoints].Y := CoordY2;
|
||||
inc(NumPoints);
|
||||
// ADest.Line(CoordX, CoordY, CoordX2, CoordY2);
|
||||
end;
|
||||
PosX := Cur2DSegment.X;
|
||||
PosY := Cur2DSegment.Y;
|
||||
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
|
||||
Write(Format(' L%d,%d', [CoordX2, CoordY2]));
|
||||
{$endif}
|
||||
end;
|
||||
|
||||
{ To draw a bezier we need to divide the interval in parts and make
|
||||
lines between this parts }
|
||||
st2DBezier, st3DBezier:
|
||||
@ -4201,21 +4343,35 @@ begin
|
||||
CoordY3 := CoordToCanvasY(Cur2DBSegment.Y3, ADestY, AMulY);
|
||||
CoordX4 := CoordToCanvasX(Cur2DBSegment.X, ADestX, AMulX);
|
||||
CoordY4 := CoordToCanvasY(Cur2DBSegment.Y, ADestY, AMulY);
|
||||
SetLength(lPoints, 0);
|
||||
// SetLength(lPoints, 0);
|
||||
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX, CoordY, CoordX2, CoordY2);
|
||||
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, CoordX3, CoordY3, CoordX4, CoordY4);
|
||||
SetLength(pts, 0);
|
||||
AddBezierToPoints(
|
||||
Make2DPoint(CoordX, CoordY),
|
||||
Make2DPoint(CoordX2, CoordY2),
|
||||
Make2DPoint(CoordX3, CoordY3),
|
||||
Make2DPoint(CoordX4, CoordY4),
|
||||
lPoints
|
||||
pts //lPoints
|
||||
);
|
||||
|
||||
if ADoDraw then
|
||||
begin
|
||||
if NumPoints + Length(pts) >= POINT_BUFFER then
|
||||
SetLength(lPoints, NumPoints + Length(pts));
|
||||
for i:=0 to High(pts) do
|
||||
begin
|
||||
lPoints[NumPoints].X := pts[i].X;
|
||||
lPoints[NumPoints].Y := pts[i].Y;
|
||||
inc(numPoints);
|
||||
end;
|
||||
end;
|
||||
|
||||
ADest.Brush.Style := Brush.Style;
|
||||
{
|
||||
if (Length(lPoints) >= 3) and ADoDraw then
|
||||
ADest.Polygon(lPoints);
|
||||
|
||||
}
|
||||
PosX := Cur2DSegment.X;
|
||||
PosY := Cur2DSegment.Y;
|
||||
|
||||
@ -4227,34 +4383,19 @@ begin
|
||||
CoordToCanvasX(Cur2DBSegment.X, ADestX, AMulX), CoordToCanvasY(Cur2DBSegment.Y, ADestY, AMulY)]));
|
||||
{$endif}
|
||||
end;
|
||||
// Alligned Ellipse equation:
|
||||
// x^2 / RX^2 + Y^2 / RY^2 = 1
|
||||
//
|
||||
// Rotated Ellipse equation:
|
||||
// (xcosθ+ysinθ)^2 / RX^2 + (ycosθ−xsinθ)^2 / RY^2 = 1
|
||||
//
|
||||
// parametrized:
|
||||
// x = Cx + a*cos(t)*cos(phi) - b*sin(t)*sin(phi) [1]
|
||||
// y = Cy + b*sin(t)*cos(phi) + a*cos(t)*sin(phi) [2]
|
||||
// ...where ellipse has centre (h,k) semimajor axis a and semiminor axis b, and is rotated through angle phi.
|
||||
//
|
||||
// You can then differentiate and solve for gradient = 0:
|
||||
// 0 = dx/dt = -a*sin(t)*cos(phi) - b*cos(t)*sin(phi)
|
||||
// => tan(t) = -b*tan(phi)/a [3]
|
||||
// => t = arctan(-b*tan(phi)/a) + n*Pi [4]
|
||||
//
|
||||
// calculate some values of t for n in -2, -1, 0, 1, 2 and see which are the smaller, bigger ones
|
||||
// done!
|
||||
|
||||
st2DEllipticalArc:
|
||||
begin
|
||||
CoordX := CoordToCanvasX(PosX, ADestX, AMulX);
|
||||
CoordX := CoordToCanvasX(PosX, ADestX, AMulX); // start point of segment
|
||||
CoordY := CoordToCanvasY(PosY, ADestY, AMulY);
|
||||
CoordX2 := CoordToCanvasX(Cur2DArcSegment.RX, ADestX, AMulX);
|
||||
CoordY2 := CoordToCanvasY(Cur2DArcSegment.RY, ADestY, AMulY);
|
||||
CoordX3 := CoordToCanvasX(Cur2DArcSegment.XRotation, ADestX, AMulX);
|
||||
CoordX4 := CoordToCanvasX(Cur2DArcSegment.X, ADestX, AMulX);
|
||||
CoordX2 := CoordToCanvasX(Cur2DArcSegment.RX, ADestX, AMulX); // major axis radius
|
||||
CoordY2 := CoordToCanvasY(Cur2DArcSegment.RY, ADestY, AMulY); // minor axis radius
|
||||
CoordX3 := CoordToCanvasX(Cur2DArcSegment.XRotation, 0, sign(AMulX)); // axis rotation angle
|
||||
CoordX4 := CoordToCanvasX(Cur2DArcSegment.X, ADestX, AMulX); // end point of segment
|
||||
CoordY4 := CoordToCanvasY(Cur2DArcSegment.Y, ADestY, AMulY);
|
||||
SetLength(lPoints, 0);
|
||||
CoordX5 := CoordToCanvasX(Cur2DArcSegment.Cx, ADestX, AMulX); // Ellipse center
|
||||
CoordY5 := CoordToCanvasY(Cur2DArcSegment.Cy, ADestY, AMulY);
|
||||
// SetLength(lPoints, 0);
|
||||
|
||||
Cur2DArcSegment.CalculateEllipseBoundingBox(nil, BoxLeft, BoxTop, BoxRight, BoxBottom);
|
||||
|
||||
@ -4266,28 +4407,63 @@ begin
|
||||
{$ifdef FPVECTORIAL_TOCANVAS_ELLIPSE_VISUALDEBUG}
|
||||
ACanvas.Pen.Color := clRed;
|
||||
ACanvas.Brush.Style := bsClear;
|
||||
ACanvas.Rectangle(
|
||||
ACanvas.Rectangle( // Ellipse bounding box
|
||||
EllipseRect.Left, EllipseRect.Top, EllipseRect.Right, EllipseRect.Bottom);
|
||||
ACanvas.Line(CoordX5-5, CoordY5, CoordX5+5, CoordY5); // Ellipse center
|
||||
ACanvas.Line(CoordX5, CoordY5-5, CoordX5, CoordY5+5);
|
||||
ACanvas.Pen.Color := clBlue;
|
||||
ACanvas.Line(CoordX-5, CoordY, CoordX+5, CoordY); // Start point
|
||||
ACanvas.Line(CoordX, CoordY-5, CoordX, CoordY+5);
|
||||
ACanvas.Line(CoordX4-5, CoordY4, CoordX4+5, CoordY4); // End point
|
||||
ACanvas.Line(CoordX4, CoordY4-5, CoordX4, CoordY4+5);
|
||||
|
||||
{$endif}
|
||||
|
||||
ADest.Brush.Style := Brush.Style;
|
||||
// ADest.Brush.Style := Brush.Style;
|
||||
CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo,
|
||||
EllipseRect.Left, EllipseRect.Top, EllipseRect.Right, EllipseRect.Bottom);
|
||||
|
||||
if ADoDraw then
|
||||
begin
|
||||
// Arc draws counterclockwise
|
||||
if Cur2DArcSegment.ClockwiseArcFlag then
|
||||
Cur2DArcSegment.PolyApproximate(pts3D);
|
||||
// Cur2DArcSegment.BezierApproximate(pts3D);
|
||||
if NumPoints + Length(pts3D) >= POINT_BUFFER then
|
||||
SetLength(lPoints, NumPoints + Length(pts3D));
|
||||
for i:=1 to High(pts3D) do // i=0 is end point of prev segment -> we can skip it.
|
||||
begin
|
||||
lPoints[NumPoints].X := CoordToCanvasX(pts3D[i].X, ADestX, AMulX);
|
||||
lPoints[NumPoints].Y := CoordToCanvasY(pts3D[i].Y, ADestY, AMulY);
|
||||
inc(numPoints);
|
||||
end;
|
||||
{
|
||||
SetLength(lPoints, Length(pts3D));
|
||||
for i:=0 to High(pts3D) do
|
||||
begin
|
||||
lPoints[i].X := CoordToCanvasX(pts3D[i].X, ADestX, AMulX);
|
||||
lPoints[i].Y := CoordToCanvasY(pts3D[i].Y, ADestY, AMulY);
|
||||
end;
|
||||
ADest.Polygon(lPoints);
|
||||
}
|
||||
{
|
||||
i := 0;
|
||||
while i < Length(lPoints) do
|
||||
begin
|
||||
ADest.Polygon([lPoints[i], lPoints[i+1], lPoints[i+2], lPoints[i+3]]);
|
||||
inc(i, 4);
|
||||
end;
|
||||
}
|
||||
{
|
||||
// Arc draws counterclockwise
|
||||
if Cur2DArcSegment.ClockwiseArcFlag then
|
||||
ACanvas.Arc(
|
||||
EllipseRect.Left, EllipseRect.Top, EllipseRect.Right, EllipseRect.Bottom,
|
||||
CoordX4, CoordY4, CoordX, CoordY);
|
||||
end else
|
||||
begin
|
||||
CoordX4, CoordY4, CoordX, CoordY)
|
||||
else
|
||||
ACanvas.Arc(
|
||||
EllipseRect.Left, EllipseRect.Top, EllipseRect.Right, EllipseRect.Bottom,
|
||||
CoordX, CoordY, CoordX4, CoordY4);
|
||||
end;
|
||||
}
|
||||
end;
|
||||
PosX := Cur2DArcSegment.X;
|
||||
PosY := Cur2DArcSegment.Y;
|
||||
@ -4306,6 +4482,15 @@ begin
|
||||
WriteLn('');
|
||||
{$endif}
|
||||
|
||||
// Draw polygon
|
||||
if ADoDraw then begin
|
||||
SetLength(lPoints, NumPoints);
|
||||
if Length(lPoints) = 2 then
|
||||
ADest.Line(lPoints[0].X, lPoints[0].Y, lPoints[1].X, lPoints[1].Y)
|
||||
else
|
||||
ADest.Polygon(lPoints);
|
||||
end;
|
||||
|
||||
// Restores the previous Clip Region
|
||||
{$ifdef USE_CANVAS_CLIP_REGION}
|
||||
if ClipPath <> nil then
|
||||
|
||||
@ -62,6 +62,8 @@ function BezierEquation_GetPointAndTangentForLength(P1, P2, P3, P4: T3DPoint;
|
||||
ADistance: Double; out AX, AY, ATangentAngle: Double; ASteps: Integer = 30): Boolean;
|
||||
function CalcEllipseCenter(x1,y1, x2,y2, rx,ry, phi: Double; fa, fs: Boolean;
|
||||
out cx,cy, lambda: Double): Boolean;
|
||||
function CalcEllipsePointAngle(x,y, rx,ry, cx,cy, phi: Double): Double;
|
||||
procedure CalcEllipsePoint(angle, rx,ry, cx,cy, phi: Double; out x,y: Double);
|
||||
procedure ConvertPathToPoints(APath: TPath; ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray);
|
||||
function Rotate2DPoint(P, RotCenter: TPoint; alpha:double): TPoint;
|
||||
function Rotate3DPointInXY(P, RotCenter: T3DPoint; alpha:double): T3DPoint;
|
||||
@ -409,8 +411,8 @@ begin
|
||||
SinCos(phi, sinphi, cosphi);
|
||||
|
||||
// (F.6.5.1) in above document
|
||||
x1p := (cosphi*(x1-x2) + sinphi*(y1-y2)) / 2;
|
||||
y1p := -(sinphi*(x1-x2) + cosphi*(y1-y2)) / 2;
|
||||
x1p := ( cosphi*(x1-x2) + sinphi*(y1-y2)) / 2;
|
||||
y1p := (-sinphi*(x1-x2) + cosphi*(y1-y2)) / 2;
|
||||
|
||||
lambda := sqr(x1p/rx) + sqr(y1p/ry);
|
||||
if lambda > 1 then
|
||||
@ -425,8 +427,9 @@ begin
|
||||
lambda := 1.0;
|
||||
|
||||
// (F.6.5.2)
|
||||
m := (sqr(rx)*sqr(Ry) - sqr(rx)*sqr(y1p) - sqr(ry)*sqr(x1p)) / (sqr(rx)*sqr(y1p) + sqr(ry)*sqr(x1p));
|
||||
m := (sqr(rx*ry) - sqr(rx*y1p) - sqr(ry*x1p)) / (sqr(rx*y1p) + sqr(ry*x1p));
|
||||
if SameValue(m, 0.0, EPS) then
|
||||
// Prevent a crash caused by a tiny negative sqrt argument due to rounding error.
|
||||
m := 0
|
||||
else if m < 0 then
|
||||
exit;
|
||||
@ -434,8 +437,8 @@ begin
|
||||
// should no happen after having applied lambda!
|
||||
m := sqrt(m); // Positive root for fa <> fs
|
||||
if fa = fs then m := -m; // Negative root for fa = fs.
|
||||
cxp := m * rx / ry * y1p;
|
||||
cyp := m * ry / rx * x1p;
|
||||
cxp := m * rx / ry * y1p;
|
||||
cyp := -m * ry / rx * x1p;
|
||||
|
||||
// (F.6.5.3)
|
||||
cx := cosphi*cxp - sinphi*cyp + (x1 + x2) / 2;
|
||||
@ -445,6 +448,43 @@ begin
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
{ Calculates the arc angle (in radians) of the point (x,y) on the perimeter of
|
||||
an ellipse with radii rx,ry and center cx,cy. phi is the rotation angle of
|
||||
the ellipse major axis with the x axis.
|
||||
The result is in the range 0 .. 2pi}
|
||||
function CalcEllipsePointAngle(x,y, rx,ry, cx,cy, phi: Double): Double;
|
||||
var
|
||||
p: T3DPoint;
|
||||
begin
|
||||
// Rotate ellipse back to align its major axis with the x axis
|
||||
P := Rotate3dPointInXY(Make3dPoint(x-cx, y-cy, 0), Make3dPoint(0, 0, 0), phi);
|
||||
// Correctly speaking, above line should use -phi, instead of phi. But
|
||||
// Make3DPointInXY seems to define the angle in the opposite way.
|
||||
Result := arctan2(P.Y, P.X);
|
||||
if Result < 0 then Result := TWO_PI + Result;
|
||||
end;
|
||||
|
||||
{ Calculates the x,y coordinates of a point on an ellipse defined by these
|
||||
parameters:
|
||||
- rx, ry: major and minor radius
|
||||
- phi: rotation angle of the ellipse (angle between major axis and x axis)
|
||||
- angle: angle from ellipse center between x axis and the point
|
||||
|
||||
parameterized:
|
||||
x = Cx + RX*cos(t)*cos(phi) - RY*sin(t)*sin(phi) [1]
|
||||
y = Cy + RY*sin(t)*cos(phi) + RX*cos(t)*sin(phi) [2] }
|
||||
procedure CalcEllipsePoint(angle, rx,ry, cx,cy, phi: Double; out x,y: Double);
|
||||
var
|
||||
P: T3dPoint;
|
||||
cost, sint: Extended;
|
||||
cosphi, sinphi: Extended;
|
||||
begin
|
||||
SinCos(angle, sint, cost);
|
||||
SinCos(phi, sinphi, cosphi);
|
||||
x := cx + rx*cost*cosphi - ry*sint*sinphi;
|
||||
y := cy + ry*sint*cosphi + rx*cost*sinphi;
|
||||
end;
|
||||
|
||||
procedure ConvertPathToPoints(APath: TPath; ADestX, ADestY: Integer; AMulX, AMulY: Double; var Points: TPointsArray);
|
||||
var
|
||||
i, LastPoint: Integer;
|
||||
@ -508,6 +548,8 @@ end;
|
||||
|
||||
// Rotates a point P around RotCenter
|
||||
// alpha angle in radians
|
||||
// Be CAREFUL: the angle used here grows in clockwise direction. This is
|
||||
// against mathematical convention!
|
||||
function Rotate3DPointInXY(P, RotCenter: T3DPoint; alpha:double): T3DPoint;
|
||||
var
|
||||
sinus, cosinus : Extended;
|
||||
@ -602,7 +644,7 @@ begin
|
||||
else
|
||||
begin
|
||||
lParam1 := Pi;
|
||||
lParam2 := 2*Pi;
|
||||
lParam2 := TWO_PI;
|
||||
end;
|
||||
|
||||
// Iterate as many times necessary to get the best answer!
|
||||
|
||||
@ -2263,19 +2263,23 @@ begin
|
||||
Ynew := CurY + Y;
|
||||
end else
|
||||
begin
|
||||
Xnew := CurX;
|
||||
Ynew := CurY;
|
||||
Xnew := X;
|
||||
Ynew := Y;
|
||||
end;
|
||||
|
||||
if CalcEllipseCenter(CurX, CurY, Xnew, Ynew, X2, Y2, -phi, LargeArcFlag, SweepFlag, cx, cy, tmp) then
|
||||
AData.AddEllipticalArcWithCenterToPath(X2*tmp, Y2*tmp, -phi, Xnew, Ynew, cx, cy, SweepFlag)
|
||||
// in svg the y axis increases downward, in fpv upward. Therefore, angles
|
||||
// change their sign!
|
||||
phi := -phi;
|
||||
SweepFlag := not SweepFlag; // i.e. "clockwise" turns into "counter-clockwise"!
|
||||
|
||||
if CalcEllipseCenter(CurX, CurY, Xnew, Ynew, X2, Y2, phi, LargeArcFlag, SweepFlag, cx, cy, tmp) then
|
||||
AData.AddEllipticalArcWithCenterToPath(X2*tmp, Y2*tmp, phi, Xnew, Ynew, cx, cy, SweepFlag)
|
||||
else
|
||||
// Use a straight segment in case of no solution existing for the ellipse center
|
||||
AData.AddLineToPath(Xnew, Ynew);
|
||||
|
||||
CurX := Xnew;
|
||||
CurY := Ynew;
|
||||
|
||||
{
|
||||
// Convert SVG flags to fpvectorial flags
|
||||
LeftmostEllipse := (LargeArcFlag and (not SweepFlag))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user