Replies: 4 comments
|
You are reading the surface correctly — there is no point-array entry point left for polygons. The full set of public helpers in Worth noting for your request, though, because it argues against this being a deliberate move away from point arrays in general: public void DrawBezier(Pen pen, params PointF[] points)So a point-array overload is not against the grain of the new API — it survives, in the same file, for the neighbouring primitive. In the meantime there is a route that avoids materialising a public PathBuilder Reset()
public PathBuilder AddLines(params PointF[] points)
public PathBuilder CloseFigure()together with public void Draw(Pen pen, PathBuilder pathBuilder)
public void Fill(Brush brush, PathBuilder pathBuilder)So you can hold one builder for the lifetime of a render pass and, per shape, Two things to be accurate about rather than overselling it. Use the array overload — there is also Which is really the argument for your request. If the goal is to avoid intermediate objects entirely on a hot path, only an overload taking the points directly gets there, and the bezier signature shows there is nothing in the new design that forbids one. |
|
Interesting points! Well, API wise, I think the current API veers a little on the too sparse side compared to some comparable 2D graphics API's. People commonly expect lines, polylines, polygons, arcs, ellipse and beziers as basic primitives for Draw and Fill methods, like the classic System.Drawing.Graphics has. SkiaSharp also has a DrawPoints method and you can specify SKPointMode.Points/Lines/Polygon (https://learn.microsoft.com/en-us/dotnet/api/skiasharp.skcanvas.drawpoints), and also has a path drawing option. DrawingCanvas supports drawing/filling all of these expected primitives, except polygons!!! And then when you need a Path, System.Drawing.Graphics ans SkiaSharp give you the freedom of doing it that way as well (GraphicsPath). Not that System.Drawing.Graphics is the holy grail, but I think it's great that it provides both styles of drawing. DrawingCanvas does so as well for most primitives, but again: except for polygons! Also for discoverability and programming you just get things done a little quicker with the DrawX/FillX methods over the IPath approach, and that's probably 95% of what people need in day to day drawing. Right now I'm directly constructing a SixLabors.ImageSharp.Drawing.Polygon instance, which inherits from Path, and looks like a bit of a bloated object to represent a PointF[]. Alternatively I can use PathBuilder.AddPolygon, which creates a new Figure and a new LinearLineSegment interally, plus you'd need a call to PathBuilder.Build() before passing it to DrawCanvas.Draw/Fill, which looks to also creates an array and who knows what else in that linq select. I think both approaches are more overhead than necessary for drawing/filling lines/polygons. Ideally DrawingCanvas treats polylines (DrawLine) and polygons (DrawPolygon) as first class citizens, taking PointF[] as argument, with minimal object allocation. Interestingly ImageSharp internally already has this concept: the ISimplePath, this already looks a bit closer to the metal. But I'm gonna stick with my initial request for providing DrawPolygon/FillPolygon with PointF[], it has the potential for minimal performance overhead and maximum programmer convenience for a very common drawing primitive. |
|
I don't agree with the premise here, and I don't plan to restore The statement that
canvas.Draw(pen, new Polygon(points));
canvas.Fill(brush, new Polygon(points));That is polygon support. It is not a workaround for a missing primitive. The convenience methods on So the distinction here is not "polygons are unsupported". The request is specifically for a second spelling of an operation which is already supported. There also appears to be a misconception about what the old API did. In ImageSharp.Drawing 2.1.7, public static IImageProcessingContext DrawPolygon(
this IImageProcessingContext source,
Pen pen,
params PointF[] points) =>
source.Draw(source.GetDrawingOptions(), pen, new Polygon(points));The overload taking source.Draw(options, pen, new Polygon(points));
source.Fill(options, brush, new Polygon(points));or: source.Fill(brush, new Polygon(points));So the old point-array APIs did not bypass ImageSharp.Drawing geometry primitives. They allocated a This was also how the other point-array APIs worked. The old new Path(points)before passing the result to There was never an old path where a Consequently, adding: canvas.DrawPolygon(pen, points);
canvas.FillPolygon(brush, points);with equivalent semantics would simply implement: this.Draw(pen, new Polygon(points));
this.Fill(brush, new Polygon(points));It would move The description of The constructor is essentially: public Polygon(PointF[] points)
: this(new LinearLineSegment(points))
{
}and The more substantial derived path data needed by the rendering pipeline is created as required. Constructing There are managed geometry objects involved because this is a geometry API. Hiding those objects behind The
public void Draw(Pen pen, PathBuilder pathBuilder)
{
this.Draw(pen, pathBuilder.Build());
}
So reusing a The comparison with
this.Draw(pen, new Path(new CubicBezierLineSegment(points)));It isn't a special point-array fast path. It creates geometry objects too. Bezier is different because the supplied Without the helper, a caller wanting to draw a Bezier would need to understand that its representation in our geometry model is: new Path(new CubicBezierLineSegment(points))
A polygon is different. The supplied points already describe the linear geometry, and there is already a first-class public type whose purpose is exactly to represent that geometry: new Polygon(points)There is nothing equivalent for The SkiaSharp comparison is also not comparing equivalent operations.
Skia's own documentation describes It is also stroke-only. Stroke joins are ignored as well. Skia draws each line element individually rather than generating a path mask, which means its behaviour is intentionally different from So this: canvas.DrawPoints(SKPointMode.Polygon, points, paint);is effectively an open connected series of individually stroked line segments. It is not equivalent to a closed, fillable polygon with proper path semantics. It cannot provide the equivalent of: canvas.Fill(brush, new Polygon(points));at all. For an actual closed/fillable polygon with normal Skia path semantics, you use path geometry and draw the path. In other words, once you require the semantics ImageSharp's The fact that Skia has a specialized immediate-mode There is another architectural difference here as well.
That means geometry passed to For example, a hypothetical: DrawPolygon(Pen pen, ReadOnlySpan<PointF> points)could not simply retain that span for replay. We would have to copy it into owned storage. A That is also very different from restoring the old API, because, as shown above, the old API itself just constructed On discoverability, I don't think duplicating the geometry API is the right solution either. We have invested heavily in the ImageSharp.Drawing documentation, and I would argue that it is substantially more comprehensive than the documentation available for most alternative .NET graphics libraries. The documentation explicitly explains the core design:
There is a dedicated "Primitive Drawing Helpers" section and a separate "Paths and Shapes" section. The Paths and Shapes documentation explicitly lists: and demonstrates those shapes being passed directly to: canvas.Fill(brush, shape);
canvas.Draw(pen, shape);It also explains open versus closed paths, reusable geometry, fill rules, custom paths, complex polygons, clipping and path collections. We additionally have dedicated migration documentation for both System.Drawing and SkiaSharp which explains how their concepts map onto ImageSharp.Drawing, as well as getting-started guides, recipes, troubleshooting documentation and generated API documentation. So I don't accept that every supported geometry operation has to appear as a specifically named The API is deliberately designed around a small set of composable concepts rather than reproducing the enormous overload surface of the old processing API or copying the API shape of System.Drawing. In fact, adding: canvas.DrawPolygon(pen, points);when the canonical geometry operation is already: canvas.Draw(pen, new Polygon(points));would actively work against that design. It adds another way to express exactly the same operation without adding capability or improving the underlying implementation. Finally, the performance change in the new Drawing implementation is considerably larger than "50% faster". Across the workloads we benchmarked during development, the new pipeline is generally around an order of magnitude faster than the previous implementation. If your CAD workload exposes a genuine GC or throughput bottleneck when drawing very large numbers of flattened polygons, then a representative benchmark would be useful. That is exactly the sort of workload worth profiling, and if there is an unnecessary allocation in the underlying retained geometry or command pipeline we should optimize that. But adding back The old methods already constructed For those reasons I don't think these methods should return. |
|
Thanks Jim for clarifying. From looking at the Aside from this, e.g. for a simple polygon (let's say a triangle), would be the recommended Drawing call be using a |
Uh oh!
There was an error while loading. Please reload this page.
I have just upgraded to ImageSharp 4.1.1, which looks to be 50% faster, so very happy with that, thank you!
The old drawing methods DrawPolygon/FillPolygon were removed. In my CAD library I extensively use PointF[] interally, together with a bool to specify whether the polyline is closed or not. When it's closed I'm now forced to create a SixLabors.ImageSharp.Drawing.Polygon instance to be able to call the DrawingCanvas.Draw/Fill methods, which increases the garbage collector load. I'm trying to avoid creating class instances as much as possible, as drawings often contain huge numbers of polygons. The DrawPolygon/FillPolygon calls are really a hot path for my rendering scenario, as I flatten everything into polylines and polygons before handing them over to ImageSharp.
All reactions