Skip to content

Commit fb7da4e

Browse files
committed
ensure all lines offset to align to pixel centers
1 parent 04c6011 commit fb7da4e

5 files changed

Lines changed: 120 additions & 8 deletions

File tree

src/ImageSharp.Drawing/Primitives/ShapePath.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ internal class ShapePath : ShapeRegion
1616
/// <param name="shape">The shape.</param>
1717
/// <param name="pen">The pen to apply to the shape.</param>
1818
public ShapePath(IPath shape, IPen pen)
19-
: base(shape.GenerateOutline(pen.StrokeWidth, pen.StrokePattern))
19+
: base(shape.GenerateOutline(pen.StrokeWidth, pen.StrokePattern, false, JointStyle.Square, EndCapStyle.Square))
2020
{
2121
}
2222
}

src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawPathProcessor.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ public DrawPathProcessor(ShapeGraphicsOptions options, IPen pen, IPath shape)
4343
/// <inheritdoc />
4444
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
4545
where TPixel : unmanaged, IPixel<TPixel>
46-
=> new FillRegionProcessor(this.Options, this.Pen.StrokeFill, new ShapePath(this.Shape, this.Pen)).CreatePixelSpecificProcessor(configuration, source, sourceRectangle);
46+
{
47+
// offset drawlines to align drawing outlines to pixel centers
48+
var shape = this.Shape.Translate(0.5f, 0.5f);
49+
return new FillRegionProcessor(this.Options, this.Pen.StrokeFill, new ShapePath(shape, this.Pen)).CreatePixelSpecificProcessor(configuration, source, sourceRectangle);
50+
}
4751
}
4852
}

src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,8 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
6161
// basically if the line is [1,2] => [3,2] then when outlining at 1 we end up with a region of [0.5,1.5],[1.5, 1.5],[3.5,2.5],[2.5,2.5]
6262
// and this can cause missed fills when not using antialiasing.so we offset the pixel grid by 0.5 in the x & y direction thus causing the#
6363
// region to align with the pixel grid.
64-
float offset = 0.5f;
6564
if (graphicsOptions.Antialias)
6665
{
67-
offset = 0f; // we are antialiasing skip offsetting as real antialiasing should take care of offset.
6866
subpixelCount = graphicsOptions.AntialiasSubpixelDepth;
6967
if (subpixelCount < 4)
7068
{
@@ -97,7 +95,7 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
9795
float yPlusOne = y + 1;
9896
for (float subPixel = y; subPixel < yPlusOne; subPixel += subpixelFraction)
9997
{
100-
int pointsFound = region.Scan(subPixel + offset, buffer, configuration, shapeOptions.IntersectionRule);
98+
int pointsFound = region.Scan(subPixel, buffer, configuration, shapeOptions.IntersectionRule);
10199
if (pointsFound == 0)
102100
{
103101
// nothing on this line, skip
@@ -109,8 +107,8 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
109107
// points will be paired up
110108
float scanStart = buffer[point] - minX;
111109
float scanEnd = buffer[point + 1] - minX;
112-
int startX = (int)MathF.Floor(scanStart + offset);
113-
int endX = (int)MathF.Floor(scanEnd + offset);
110+
int startX = (int)MathF.Floor(scanStart);
111+
int endX = (int)MathF.Floor(scanEnd);
114112

115113
if (startX >= 0 && startX < scanline.Length)
116114
{

src/ImageSharp.Drawing/Shapes/Outliner.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
7575
{
7676
if (pattern.Length < 2)
7777
{
78-
return path.GenerateOutline(width);
78+
return path.GenerateOutline(width, jointStyle: jointStyle, endCapStyle: patternSectionCapStyle);
7979
}
8080

8181
JoinType style = Convert(jointStyle);
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using SixLabors.ImageSharp.ColorSpaces;
6+
using SixLabors.ImageSharp.Drawing.Processing;
7+
using SixLabors.ImageSharp.PixelFormats;
8+
using SixLabors.ImageSharp.Processing;
9+
using Xunit;
10+
11+
namespace SixLabors.ImageSharp.Drawing.Tests.Issues
12+
{
13+
public class Issue_28
14+
{
15+
private Rgba32 red = Color.Red.ToRgba32();
16+
17+
[Fact]
18+
public void DrawingLineAtTopShouldDisplay()
19+
{
20+
using var image = new Image<Rgba32>(Configuration.Default, 100, 100, Color.Black);
21+
image.Mutate(x => x
22+
.SetGraphicsOptions(g => g.Antialias = false)
23+
.DrawLines(
24+
this.red,
25+
1f,
26+
new PointF(0, 0),
27+
new PointF(100, 0)
28+
));
29+
30+
var locations = Enumerable.Range(0, 100).Select(i => (x: i, y: 0));
31+
Assert.All(locations, l =>
32+
{
33+
Assert.Equal(this.red, image[l.x, l.y]);
34+
});
35+
}
36+
37+
[Fact]
38+
public void DrawingLineAtBottomShouldDisplay()
39+
{
40+
using var image = new Image<Rgba32>(Configuration.Default, 100, 100, Color.Black);
41+
image.Mutate(x => x
42+
.SetGraphicsOptions(g => g.Antialias = false)
43+
.DrawLines(
44+
this.red,
45+
1f,
46+
new PointF(0, 99),
47+
new PointF(100, 99)
48+
));
49+
50+
var locations = Enumerable.Range(0, 100).Select(i => (x: i, y: 99));
51+
Assert.All(locations, l =>
52+
{
53+
Assert.Equal(this.red, image[l.x, l.y]);
54+
});
55+
}
56+
57+
[Fact]
58+
public void DrawingLineAtLeftShouldDisplay()
59+
{
60+
using var image = new Image<Rgba32>(Configuration.Default, 100, 100, Color.Black);
61+
image.Mutate(x => x
62+
.SetGraphicsOptions(g => g.Antialias = false)
63+
.DrawLines(
64+
this.red,
65+
1f,
66+
new PointF(0, 0),
67+
new PointF(0, 99)
68+
));
69+
70+
var locations = Enumerable.Range(0, 100).Select(i => (x: 0, y: i));
71+
Assert.All(locations, l =>
72+
{
73+
Assert.Equal(this.red, image[l.x, l.y]);
74+
});
75+
}
76+
77+
[Fact]
78+
public void DrawingLineAtRightShouldDisplay()
79+
{
80+
using var image = new Image<Rgba32>(Configuration.Default, 100, 100, Color.Black);
81+
image.Mutate(x => x
82+
.SetGraphicsOptions(g => g.Antialias = false)
83+
.DrawLines(
84+
this.red,
85+
1f,
86+
new PointF(99, 0),
87+
new PointF(99, 99)
88+
));
89+
90+
var locations = Enumerable.Range(0, 100).Select(i => (x: 99, y: i));
91+
Assert.All(locations, l =>
92+
{
93+
Assert.Equal(this.red, image[l.x, l.y]);
94+
});
95+
}
96+
97+
//public void DrawWithSystemDrawing()
98+
//{
99+
// GraphicsPath p = new GraphicsPath();
100+
// p.AddString(
101+
// "My Text String", // text to draw
102+
// FontFamily.GenericSansSerif, // or any other font family
103+
// (int)FontStyle.Regular, // font style (bold, italic, etc.)
104+
// g.DpiY * fontSize / 72, // em size
105+
// new Point(0, 0), // location where to draw text
106+
// new StringFormat()); // set options here (e.g. center alignment)
107+
// g.DrawPath(Pens.Black, p);
108+
//}
109+
}
110+
}

0 commit comments

Comments
 (0)