Skip to content

Commit 48a803e

Browse files
authored
Merge pull request #70 from SixLabors/sw/issue-28
Offset draw line operations to draw from pixel center
2 parents 8e10c1a + 65b27ac commit 48a803e

57 files changed

Lines changed: 111 additions & 12 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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: 2 additions & 2 deletions
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);
7979
}
8080

8181
JoinType style = Convert(jointStyle);
@@ -187,7 +187,7 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
187187
/// <param name="jointStyle">The style to render the joints.</param>
188188
/// <param name="endCapStyle">The style to render the end caps of open paths (ignored on closed paths).</param>
189189
/// <returns>A new path representing the outline.</returns>
190-
public static IPath GenerateOutline(this IPath path, float width, JointStyle jointStyle = JointStyle.Square, EndCapStyle endCapStyle = EndCapStyle.Butt)
190+
public static IPath GenerateOutline(this IPath path, float width, JointStyle jointStyle = JointStyle.Square, EndCapStyle endCapStyle = EndCapStyle.Square)
191191
{
192192
var offset = new ClipperOffset()
193193
{
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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+
}

0 commit comments

Comments
 (0)