Skip to content

Commit e79c5e2

Browse files
Implement smart decoration pixel grid clamping
1 parent 3da0d26 commit e79c5e2

15 files changed

+93
-34
lines changed

src/ImageSharp.Drawing/Processing/Processors/Text/RichTextGlyphRenderer.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ public override void SetDecoration(TextDecorations textDecorations, Vector2 star
225225
}
226226
else
227227
{
228+
// Clamp the thickness to whole pixels.
229+
thickness = MathF.Max(1F, MathF.Round(thickness));
228230
pen = new SolidPen(this.currentBrush ?? this.defaultBrush, thickness);
229231
}
230232

@@ -242,7 +244,14 @@ public override void SetDecoration(TextDecorations textDecorations, Vector2 star
242244
offset = rotated ? new(-(thickness * .5F), 0) : new(0, thickness * .5F);
243245
}
244246

245-
this.AppendDecoration(ref targetDecoration, start + offset, end + offset, pen, thickness, rotated);
247+
// We clamp the start and end points to the pixel grid to avoid anti-aliasing.
248+
this.AppendDecoration(
249+
ref targetDecoration,
250+
ClampToPixel(start + offset, (int)thickness, rotated),
251+
ClampToPixel(end + offset, (int)thickness, rotated),
252+
pen,
253+
thickness,
254+
rotated);
246255
}
247256

248257
protected override void EndGlyph()
@@ -384,6 +393,24 @@ protected override void EndText()
384393
[MethodImpl(MethodImplOptions.AggressiveInlining)]
385394
private static Point ClampToPixel(PointF point) => Point.Truncate(point);
386395

396+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
397+
private static PointF ClampToPixel(PointF point, int thickness, bool rotated)
398+
{
399+
// Even. Clamp to whole pixels.
400+
if ((thickness & 1) == 0)
401+
{
402+
return Point.Truncate(point);
403+
}
404+
405+
// Odd. Clamp to half pixels.
406+
if (rotated)
407+
{
408+
return Point.Truncate(point) + new Vector2(.5F, 0);
409+
}
410+
411+
return Point.Truncate(point) + new Vector2(0, .5F);
412+
}
413+
387414
// Point.Truncate(point);
388415
private void FinalizeDecoration(ref TextDecorationDetails? decoration)
389416
{
@@ -468,7 +495,7 @@ private void AppendDecoration(
468495
Start = start,
469496
End = end,
470497
Pen = pen,
471-
Thickness = MathF.Abs(thickness)
498+
Thickness = thickness
472499
};
473500
}
474501

src/ImageSharp.Drawing/Shapes/Text/BaseGlyphBuilder.cs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

4+
using System;
45
using System.Collections.Generic;
56
using System.Numerics;
7+
using System.Runtime.CompilerServices;
68
using SixLabors.Fonts;
79

810
namespace SixLabors.ImageSharp.Drawing.Text
@@ -145,11 +147,18 @@ public virtual void SetDecoration(TextDecorations textDecorations, Vector2 start
145147
return;
146148
}
147149

150+
thickness = MathF.Max(1F, (float)Math.Round(thickness));
148151
var renderer = (IGlyphRenderer)this;
149152

150-
// Clamp the line to whole pixels
153+
// Expand the points to create a rectangle centered around the line.
151154
bool rotated = this.parameters.LayoutMode.IsVertical() || this.parameters.LayoutMode.IsVerticalMixed();
152155
Vector2 pad = rotated ? new(thickness * .5F, 0) : new(0, thickness * .5F);
156+
157+
// Clamp the line to the pixel grid.
158+
start = ClampToPixel(start, (int)thickness, rotated);
159+
end = ClampToPixel(end, (int)thickness, rotated);
160+
161+
// Offset to create the rectangle.
153162
Vector2 a = start - pad;
154163
Vector2 b = start + pad;
155164
Vector2 c = end + pad;
@@ -168,12 +177,35 @@ public virtual void SetDecoration(TextDecorations textDecorations, Vector2 start
168177
offset = rotated ? new(-(thickness * .5F), 0) : new(0, thickness * .5F);
169178
}
170179

171-
// MoveTo calls StartFigure();
172-
renderer.MoveTo(a + offset);
173-
renderer.LineTo(b + offset);
174-
renderer.LineTo(c + offset);
175-
renderer.LineTo(d + offset);
180+
renderer.BeginFigure();
181+
182+
// Now draw the rectangle clamped to the pixel grid.
183+
renderer.MoveTo(ClampToPixel(a + offset));
184+
renderer.LineTo(ClampToPixel(b + offset));
185+
renderer.LineTo(ClampToPixel(c + offset));
186+
renderer.LineTo(ClampToPixel(d + offset));
176187
renderer.EndFigure();
177188
}
189+
190+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
191+
private static Point ClampToPixel(PointF point) => Point.Truncate(point);
192+
193+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
194+
private static PointF ClampToPixel(PointF point, int thickness, bool rotated)
195+
{
196+
// Even. Clamp to whole pixels.
197+
if ((thickness & 1) == 0)
198+
{
199+
return Point.Truncate(point);
200+
}
201+
202+
// Odd. Clamp to half pixels.
203+
if (rotated)
204+
{
205+
return Point.Truncate(point) + new Vector2(.5F, 0);
206+
}
207+
208+
return Point.Truncate(point) + new Vector2(0, .5F);
209+
}
178210
}
179211
}
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading

0 commit comments

Comments
 (0)