Closed remcoros closed 2 years ago
@remcoros Can you try the latest MyGet build 1.0.0-beta14.16
@JimBobSquarePants I will as soon as I get home.
But I don't think that changes anything. As the issue is in this code: https://github.com/SixLabors/ImageSharp.Drawing/blob/main/src/ImageSharp.Drawing/Shapes/InternalPath.cs#L88
which didn't change recently.
"Max(x) - Min(x)" has a off-by-one error if you start at '0'.
Some background: In a geometrical coordinate system, going from 0mm, 0mm - 10mm, 10mm, actually means you have a line of 10mm. So doing Max(x) - Min(x) gives you the correct length.
But when dealing with a screen/image coordinate system, having a 'line' from 0,0 to 10,10 is actually a line of 11 units (pixels).
This caused me a lot of confusion when translating incoming geometrical shapes (as a list of points) to ImageSharp polygons.
I don't believe this is a bug and from how I interpret the issue is working as intended... this is due to our drawing code being subpixel aware, basically we are not drawing pixel we are drawing an area around your chosen line, and drawing is done from the pixel center not the pixel intersection.
these images should help
the effect you are after here is filling in the entire top row
this is the issue with drawing at the intersection, you only get half of your line in each target pixel, which (with anti-aliasing will result the wrong color
@tocsoft So what's the correct approach/code to get an exact 10x10 square (outline) on a 10x10 image. Pixel perfect, no anti-aliasing?
I'm basically trying to visualize a 2D shape (defined as a list of integer points). My idea was to make a polygon of it, and use an image width/height exactly the same as the shapes height and width, so each point maps to a pixel, avoiding any artifacts due to aliassing
After updating to 1.0.0-beta15 (which fixes 1px vertical line rendering #236) and running a comparison with both SkiaSharp and System.Drawing it looks to me like we do exactly the same thing they do.
// SkiaSharp
using var skImage = new SKBitmap(100, 100);
using var skCanvas = new SKCanvas(skImage);
var skRectangle = SKRect.Create(0, 0, 100, 100);
using var paint = new SKPaint
{
Style = SKPaintStyle.Fill,
Color = SKColors.White
};
skCanvas.DrawRect(skRectangle, paint);
paint.Style = SKPaintStyle.Stroke;
paint.Color = SKColors.Red;
skCanvas.DrawRect(skRectangle, paint);
using var skStream = File.Create(@"C:\Users\james\Documents\MiscDevelopment\skImage.png");
skImage.Encode(skStream, SKEncodedImageFormat.Png, 100);
// System.Drawing
using var sdImage = new Bitmap(100, 100);
using var sdGraphics = Graphics.FromImage(sdImage);
using var sdBrush = new System.Drawing.SolidBrush(System.Drawing.Color.White);
var sdRectangle = new System.Drawing.Rectangle(0, 0, 100, 100);
sdGraphics.FillRectangle(sdBrush, sdRectangle);
using var pen = new System.Drawing.Pen(System.Drawing.Color.Red);
sdGraphics.DrawRectangle(pen, sdRectangle);
using var sdStream = File.Create(@"C:\Users\james\Documents\MiscDevelopment\sdImage.png");
sdImage.Save(sdStream, ImageFormat.Png);
// ImageSharp
using var slImage = new Image<Rgba32>(Configuration.Default, 100, 100, SixLabors.ImageSharp.Color.White);
slImage.Mutate(x =>
{
x.SetGraphicsOptions(new GraphicsOptions
{
Antialias = false
});
x.Draw(SixLabors.ImageSharp.Drawing.Processing.Pens.Solid(SixLabors.ImageSharp.Color.Red, 1), new RectangularPolygon(0, 0, 100, 100).AsClosedPath());
});
slImage.SaveAsPng(@"C:\Users\james\Documents\MiscDevelopment\slImage.png");
SkiaSharp
System.Drawing
ImageSharp
SkiaSharp (Left) - ImageSharp (Right)
Thanks for the extensive explanations! Considering anti-aliasing, what @tocsoft explains makes sense.
Also looking at @JimBobSquarePants example above. Is it correct to assume that, if I want to draw a rectangle of 100x100 on a canvas of 100x100. I would need a (Rectangular)Polygon of 0,0,99,99 ?
So for my case (where I get a list of points from a 2D CAD application), I would need to subtract 1 if x/y > 0 like so:
var imagePoints = sourcePoints.Select(src=> new PointF(src.X == 0 ? 0 : src.X - 1, src.Y == 0 ? 0 : src.Y - 1))
Is that a correct assumption?
edit: the source points are the shape of a 2D object defined in mm. So a 10 mm line, goes from 0,0 - 0,10
edit2: this all started because I saw weird artifacts on some generated images. But that actually also might be because of the mentioned bug. I'll update to that beta and do some more testing.
Is it correct to assume that, if I want to draw a rectangle of 100x100 on a canvas of 100x100. I would need a (Rectangular)Polygon of 0,0,99,99 ?
Yep, 0-99. It's all zero based.
That latest releases fixes a few things so yes, definitely give it a go.
Awesome! For the test geometry I had which had some really weird artifacts (even without anti-aliassing), they are all gone!
Thanks a lot for the explanations, it's a bit weird for me having worked a lot with geometrical data structures (A 'Line' entity with a starting point of 0,0 and an endpoint of 0,10 had a length of 10, and we map these one-to-one to lines/polygons within imagesharp). I understand it's not that simple, considering the subpixel drawing. Thanks a lot, i'll have some fixing to do on our side :)
Prerequisites
DEBUG
andRELEASE
modeDescription
I believe there is an off-by-one error on 'InternalPath.Bounds'
Steps to Reproduce
This fails:
Using '10,10' for the end points is incorrect. If you try to draw a '10,10' polygon on a 10x10 image, the right and bottom part of the outline falls outside the image (since the range of pixels is 0..9 not 0..10)
Either, the current implementation is wrong and width/length should be corrected. Or it could be correct and we should use PointF(10, 10) if we want a 10x10 polygon. But then there is something wrong with drawing this polygon on an Image(10, 10), since when I try to draw a 10x10 polygon (using PointF(10, 10)), the right and bottom outline are not in the image.
System Configuration