From 3cab480c11a12524c144d989ad2a90823d44bdfb Mon Sep 17 00:00:00 2001 From: wp Date: Thu, 24 Dec 2015 12:00:46 +0000 Subject: [PATCH] fpvectorial: Fix TPath to avoid rendering of internal lines for bezier segments. Fix elliptic path segment with rotated axis. git-svn-id: trunk@51021 - --- components/fpvectorial/fpvectorial.pas | 303 ++++++++++++++---- components/fpvectorial/fpvutils.pas | 54 +++- components/fpvectorial/svgvectorialreader.pas | 14 +- 3 files changed, 301 insertions(+), 70 deletions(-) diff --git a/components/fpvectorial/fpvectorial.pas b/components/fpvectorial/fpvectorial.pas index f9f36de928..9b31310787 100644 --- a/components/fpvectorial/fpvectorial.pas +++ b/components/fpvectorial/fpvectorial.pas @@ -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 diff --git a/components/fpvectorial/fpvutils.pas b/components/fpvectorial/fpvutils.pas index 384c8b8196..48febaa022 100644 --- a/components/fpvectorial/fpvutils.pas +++ b/components/fpvectorial/fpvutils.pas @@ -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! diff --git a/components/fpvectorial/svgvectorialreader.pas b/components/fpvectorial/svgvectorialreader.pas index 8a3266323b..1fd8f6ee46 100644 --- a/components/fpvectorial/svgvectorialreader.pas +++ b/components/fpvectorial/svgvectorialreader.pas @@ -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))