Comme vous l'avez noté, ArcTo ne peut pas dessiner une ellipse complète. En fait, il devient numériquement instable lorsque vous essayez de réduire la zone "plate". Une autre considération est que le dessin en arc est plus lent que Bézier en dessinant du matériel moderne. Ces systèmes les plus modernes utilisent quatre courbes de Bézier pour approcher une ellipse plutôt que de dessiner une vraie ellipse.
Vous pouvez voir que EllipseGeometry WPF fait en exécutant le code suivant, déferlant sur l'appel de la méthode DrawBezierFigure et examiner le PathFigure dans le débogueur:
using(var ctx = geometry.Open())
{
var ellipse = new EllipseGeometry(new Point(100,100), 10, 10);
var figure = PathGeometry.CreateFromGeometry(ellipse).Figures[0];
DrawBezierFigure(ctx, figure);
}
void DrawBezierFigure(StreamGeometryContext ctx, PathFigure figure)
{
ctx.BeginFigure(figure.StartPoint, figure.IsFilled, figure.IsClosed);
foreach(var segment in figure.Segments.OfType<BezierSegment>())
ctx.BezierTo(segment.Point1, segment.Point2, segment.Point3, segment.IsStroked, segment.IsSmoothJoin);
}
Le code ci-dessus est un moyen simple de dessiner un ellipse efficace dans un StreamGeometry, mais est un code très spécial. Dans la pratique, j'utilise plusieurs méthodes d'extension à usage général définies pour dessiner une géométrie arbitraire dans un StreamGeometryContext je peux simplement écrire:
using(var ctx = geometry.Open())
{
ctx.DrawGeometry(new EllipseGeometry(new Point(100,100), 10, 10));
}
Voici la mise en œuvre de la méthode d'extension DrawGeometry:
public static class GeometryExtensions
{
public static void DrawGeometry(this StreamGeometryContext ctx, Geometry geo)
{
var pathGeometry = geo as PathGeometry ?? PathGeometry.CreateFromGeometry(geo);
foreach(var figure in pathGeometry.Figures)
ctx.DrawFigure(figure);
}
public static void DrawFigure(this StreamGeometryContext ctx, PathFigure figure)
{
ctx.BeginFigure(figure.StartPoint, figure.IsFilled, figure.IsClosed);
foreach(var segment in figure.Segments)
{
var lineSegment = segment as LineSegment;
if(lineSegment!=null) { ctx.LineTo(lineSegment.Point, lineSegment.IsStroked, lineSegment.IsSmoothJoin); continue; }
var bezierSegment = segment as BezierSegment;
if(bezierSegment!=null) { ctx.BezierTo(bezierSegment.Point1, bezierSegment.Point2, bezierSegment.Point3, bezierSegment.IsStroked, bezierSegment.IsSmoothJoin); continue; }
var quadraticSegment = segment as QuadraticBezierSegment;
if(quadraticSegment!=null) { ctx.QuadraticBezierTo(quadraticSegment.Point1, quadraticSegment.Point2, quadraticSegment.IsStroked, quadraticSegment.IsSmoothJoin); continue; }
var polyLineSegment = segment as PolyLineSegment;
if(polyLineSegment!=null) { ctx.PolyLineTo(polyLineSegment.Points, polyLineSegment.IsStroked, polyLineSegment.IsSmoothJoin); continue; }
var polyBezierSegment = segment as PolyBezierSegment;
if(polyBezierSegment!=null) { ctx.PolyBezierTo(polyBezierSegment.Points, polyBezierSegment.IsStroked, polyBezierSegment.IsSmoothJoin); continue; }
var polyQuadraticSegment = segment as PolyQuadraticBezierSegment;
if(polyQuadraticSegment!=null) { ctx.PolyQuadraticBezierTo(polyQuadraticSegment.Points, polyQuadraticSegment.IsStroked, polyQuadraticSegment.IsSmoothJoin); continue; }
var arcSegment = segment as ArcSegment;
if(arcSegment!=null) { ctx.ArcTo(arcSegment.Point, arcSegment.Size, arcSegment.RotationAngle, arcSegment.IsLargeArc, arcSegment.SweepDirection, arcSegment.IsStroked, arcSegment.IsSmoothJoin); continue; }
}
}
}
Une autre alternative est de calculer les points vous-même. La meilleure approximation d'une ellipse est trouvée en réglant les points de contrôle sur (Math.Sqrt (2) -1) * 4/3 du rayon. Ainsi, vous pouvez calculer explicitement des points et d'en tirer le Bézier comme suit:
const double ControlPointRatio = (Math.Sqrt(2)-1)*4/3;
var x0 = centerX - radiusX;
var x1 = centerX - radiusX * ControlPointRatio;
var x2 = centerX;
var x3 = centerX + radiusX * ControlPointRatio;
var x4 = centerX + radiusX;
var y0 = centerY - radiusY;
var y1 = centerY - radiusY * ControlPointRatio;
var y2 = centerY;
var y3 = centerY + radiusY * ControlPointRatio;
var y4 = centerY + radiusY;
ctx.BeginFigure(new Point(x2,y0), true, true);
ctx.BezierTo(new Point(x3, y0), new Point(x4, y1), new Point(x4,y2), true, true);
ctx.BezierTo(new Point(x4, y3), new Point(x3, y4), new Point(x2,y4), true, true);
ctx.BezierTo(new Point(x1, y4), new Point(x0, y3), new Point(x0,y2), true, true);
ctx.BezierTo(new Point(x0, y1), new Point(x1, y0), new Point(x2,y0), true, true);
Une autre option serait d'utiliser deux appels arcTo, mais comme je l'ai mentionné avant que ce soit moins efficace. Je suis sûr que vous pouvez comprendre les détails des deux appels ArcTo si vous voulez aller de cette façon.
Merci pour la réponse détaillée! –