2010-06-05 23 views
6

El único método en un StreamGeometryContext que parece relacionado con las elipses es el método ArcTo. Desafortunadamente está fuertemente orientado a unir líneas en lugar de dibujar elipsis.Cómo dibujar una elipse completa en StreamGeometry en WPF?

En particular, la posición del arco está determinada por un punto inicial y un punto final. Para una elipse completa, los dos coinciden obviamente, y la orientación exacta se vuelve indefinida.

Hasta ahora la mejor manera de dibujar una elipse centrada en 100,100 tamaño de 10,10 que he encontrado es la siguiente:

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); 
} 

que es bastante feo, y también deja una pequeña zona "plana" (aunque no visible a niveles de zoom normales).

¿De qué otra forma podría dibujar una elipse completa usando StreamGeometryContext?

Respuesta

26

Como ha señalado, ArcTo no puede dibujar una elipse completa. De hecho, se vuelve numéricamente inestable a medida que intenta reducir el área "plana". Otra consideración es que el dibujo del arco es más lento que el dibujo de Bezier en hardware moderno. Este sistema más moderno usa cuatro curvas de bezier para aproximarse a una elipse en lugar de dibujar una elipse verdadera.

Se puede ver que EllipseGeometry de WPF hace esto ejecutando el siguiente código, que se rompen en la llamada al método DrawBezierFigure, y examinando la PathFigure en el depurador:

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); 
} 

El código anterior es una forma sencilla de dibujar una elipse eficiente en StreamGeometry, pero es un código de caso muy especial. En la práctica real utilizo varios métodos de extensión para fines generales definidas para la elaboración de una geometría arbitraria en un StreamGeometryContext por lo que basta con escribir:

using(var ctx = geometry.Open()) 
{ 
    ctx.DrawGeometry(new EllipseGeometry(new Point(100,100), 10, 10)); 
} 

Aquí es la implementación del método de extensión 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; } 
    } 
    } 
} 

Otra alternativa es calcular los puntos usted mismo. La mejor aproximación a una elipse se encuentra al establecer los puntos de control en (Math.Sqrt (2) -1) * 4/3 del radio. Así se puede calcular de forma explícita los puntos de Bézier y dibujar la forma siguiente:

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); 

Otra opción sería usar dos llamadas arcto, pero como he mencionado antes de que esto es menos eficiente. Estoy seguro de que puede averiguar los detalles de las dos llamadas de ArcTo si quiere ir por ese camino.

+0

¡Gracias por la respuesta detallada! –

Cuestiones relacionadas