2010-06-05 6 views
6

La seule méthode dans un StreamGeometryContext qui semble liée à ellipses est la méthode ArcTo. Malheureusement, il est fortement axé sur la jonction de lignes plutôt que de dessiner des ellipses.Comment dessiner une ellipse complète dans un StreamGeometry dans WPF?

En particulier, la position de l'arc est déterminée par un point de départ et d'arrivée. Pour une ellipse complète, les deux coïncident évidemment, et l'orientation exacte devient indéfinie.

Jusqu'à présent, la meilleure façon de dessiner une ellipse centrée sur 100100 de la taille 10,10 que je trouve est comme ceci:

using (var ctx = geometry.Open()) 
{ 
    ctx.BeginFigure(new Point(100+5, 100), isFilled: true, isClosed: true); 
    ctx.ArcTo(
     new Point(100 + 5*Math.Cos(0.01), 100 + 5*Math.Sin(0.01)), // need a small angle but large enough that the ellipse is positioned accurately 
     new Size(10/2, 10/2), // docs say it should be 10,10 but in practice it appears that this should be half the desired width/height... 
     0, true, SweepDirection.Counterclockwise, true, true); 
} 

Ce qui est assez laid, et laisse aussi une petite zone « plat » (bien que non visible aux niveaux de zoom normaux).

Sinon, comment puis-je dessiner une ellipse complète en utilisant StreamGeometryContext?

Répondre

26

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.

+0

Merci pour la réponse détaillée! –

Questions connexes