Skip to content

Commit 8f316ca

Browse files
committed
apply matrix transform while drawing text
1 parent c29e5de commit 8f316ca

11 files changed

Lines changed: 99 additions & 7 deletions

src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ protected override void BeforeImageApply()
6363
this.Configuration.MemoryAllocator,
6464
this.Text.Length,
6565
this.Pen,
66-
this.Brush != null)
66+
this.Brush != null,
67+
this.Options.TextOptions.Transform)
6768
{
6869
Options = this.Options
6970
};
@@ -185,7 +186,7 @@ private class CachingGlyphRenderer : IColorGlyphRenderer, IDisposable
185186
// - Provide a good accuracy (smaller than 0.2% image difference compared to the non-caching variant)
186187
// - Cache hit ratio above 60%
187188
private const float AccuracyMultiple = 8;
188-
189+
private readonly Matrix3x2 transform;
189190
private readonly PathBuilder builder;
190191

191192
private Point currentRenderPosition;
@@ -201,7 +202,7 @@ private class CachingGlyphRenderer : IColorGlyphRenderer, IDisposable
201202
private readonly bool renderFill;
202203
private bool rasterizationRequired;
203204

204-
public CachingGlyphRenderer(MemoryAllocator memoryAllocator, int size, IPen pen, bool renderFill)
205+
public CachingGlyphRenderer(MemoryAllocator memoryAllocator, int size, IPen pen, bool renderFill, Matrix3x2 transform)
205206
{
206207
this.MemoryAllocator = memoryAllocator;
207208
this.currentRenderPosition = default;
@@ -220,6 +221,7 @@ public CachingGlyphRenderer(MemoryAllocator memoryAllocator, int size, IPen pen,
220221
this.OutlineOperations = new List<DrawingOperation>(size);
221222
}
222223

224+
this.transform = transform;
223225
this.builder = new PathBuilder();
224226
}
225227

@@ -251,8 +253,12 @@ public void BeginFigure()
251253
public bool BeginGlyph(FontRectangle bounds, GlyphRendererParameters parameters)
252254
{
253255
this.currentColor = null;
254-
this.currentRenderPosition = Point.Truncate(bounds.Location);
255-
PointF subPixelOffset = bounds.Location - this.currentRenderPosition;
256+
var currentBounds = new RectangularPolygon(bounds.X, bounds.Y, bounds.Width, bounds.Height)
257+
.Transform(this.transform)
258+
.Bounds;
259+
260+
this.currentRenderPosition = Point.Truncate(currentBounds.Location);
261+
PointF subPixelOffset = currentBounds.Location - this.currentRenderPosition;
256262

257263
subPixelOffset.X = MathF.Round(subPixelOffset.X * AccuracyMultiple) / AccuracyMultiple;
258264
subPixelOffset.Y = MathF.Round(subPixelOffset.Y * AccuracyMultiple) / AccuracyMultiple;
@@ -272,7 +278,11 @@ public bool BeginGlyph(FontRectangle bounds, GlyphRendererParameters parameters)
272278
this.builder.Clear();
273279

274280
// ensure all glyphs render around [zero, zero] so offset negative root positions so when we draw the glyph we can offset it back
275-
this.builder.SetOrigin(new PointF(-(int)bounds.X + this.offset, -(int)bounds.Y + this.offset));
281+
var origionTransform = new Vector2(-(int)currentBounds.X + this.offset, -(int)currentBounds.Y + this.offset);
282+
283+
var transform = this.transform;
284+
transform.Translation = transform.Translation + origionTransform;
285+
this.builder.SetTransform(transform);
276286

277287
this.rasterizationRequired = true;
278288
return true;
@@ -367,7 +377,7 @@ public void EndGlyph()
367377

368378
private Buffer2D<float> Render(IPath path)
369379
{
370-
Size size = Rectangle.Ceiling(path.Bounds).Size;
380+
Size size = Rectangle.Ceiling(path.Bounds).Size * 2;
371381
size = new Size(size.Width + (this.offset * 2), size.Height + (this.offset * 2));
372382

373383
int subpixelCount = FillRegionProcessor.MinimumSubpixelCount;

src/ImageSharp.Drawing/Processing/TextOptions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System.Collections.Generic;
5+
using System.Numerics;
56
using SixLabors.Fonts;
67

78
namespace SixLabors.ImageSharp.Drawing.Processing
@@ -136,6 +137,11 @@ public float LineSpacing
136137
/// </summary>
137138
public bool RenderColorFonts { get; set; } = true;
138139

140+
/// <summary>
141+
/// Gets or sets the transform to apply to the text during rendering.
142+
/// </summary>
143+
public Matrix3x2 Transform { get; set; } = Matrix3x2.Identity;
144+
139145
/// <inheritdoc/>
140146
public TextOptions DeepClone() => new TextOptions(this);
141147
}

tests/ImageSharp.Drawing.Tests/Drawing/Text/DrawTextOnImageTests.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Linq;
6+
using System.Numerics;
67
using System.Text;
78
using SixLabors.Fonts;
89
using SixLabors.ImageSharp.Drawing.Processing;
@@ -158,6 +159,81 @@ public void FontShapesAreRenderedCorrectly<TPixel>(
158159
appendSourceFileOrDescription: true);
159160
}
160161

162+
[Theory]
163+
[WithSolidFilledImages(50, 50, "White", PixelTypes.Rgba32, 50, 25, 25, "OpenSans-Regular.ttf", "i", 45, 25, 25)]
164+
[WithSolidFilledImages(200, 200, "White", PixelTypes.Rgba32, 50, 100, 100, "SixLaborsSampleAB.woff", AB, 45, 100, 100)]
165+
[WithSolidFilledImages(1100, 1100, "White", PixelTypes.Rgba32, 50, 550, 550, "OpenSans-Regular.ttf", TestText, 45, 550, 550)]
166+
[WithSolidFilledImages(400, 400, "White", PixelTypes.Rgba32, 20, 200, 200, "OpenSans-Regular.ttf", TestText, 45, 200, 200)]
167+
public void FontShapesAreRenderedCorrectly_WithRotationApplied<TPixel>(
168+
TestImageProvider<TPixel> provider,
169+
int fontSize,
170+
int x,
171+
int y,
172+
string fontName,
173+
string text,
174+
float angle,
175+
float rotationOriginX,
176+
float rotationOriginY)
177+
where TPixel : unmanaged, IPixel<TPixel>
178+
{
179+
Font font = CreateFont(fontName, fontSize);
180+
181+
var radians = (float)Math.PI * angle / 180f;
182+
183+
provider.RunValidatingProcessorTest(
184+
c => c
185+
.SetTextOptions(o =>
186+
{
187+
o.HorizontalAlignment = HorizontalAlignment.Center;
188+
o.VerticalAlignment = VerticalAlignment.Center;
189+
o.Transform = Matrix3x2.CreateRotation(radians, new Vector2(rotationOriginX, rotationOriginY));
190+
})
191+
.DrawText(text, font, Color.Black, new PointF(x, y)),
192+
$"{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})",
193+
TextDrawingComparer,
194+
appendPixelTypeToFileName: false,
195+
appendSourceFileOrDescription: true);
196+
}
197+
198+
199+
[Theory]
200+
[WithSolidFilledImages(50, 50, "White", PixelTypes.Rgba32, 50, 25, 25, "OpenSans-Regular.ttf", "i", -12, 0, 25, 25)]
201+
[WithSolidFilledImages(200, 200, "White", PixelTypes.Rgba32, 50, 100, 100, "SixLaborsSampleAB.woff", AB, 10, 0, 100, 100)]
202+
[WithSolidFilledImages(1100, 1100, "White", PixelTypes.Rgba32, 50, 550, 550, "OpenSans-Regular.ttf", TestText, 0, 10, 550, 550)]
203+
[WithSolidFilledImages(400, 400, "White", PixelTypes.Rgba32, 20, 200, 200, "OpenSans-Regular.ttf", TestText, 0, -10, 200, 200)]
204+
public void FontShapesAreRenderedCorrectly_WithSkewApplied<TPixel>(
205+
TestImageProvider<TPixel> provider,
206+
int fontSize,
207+
int x,
208+
int y,
209+
string fontName,
210+
string text,
211+
float angleX,
212+
float angleY,
213+
float rotationOriginX,
214+
float rotationOriginY)
215+
where TPixel : unmanaged, IPixel<TPixel>
216+
{
217+
Font font = CreateFont(fontName, fontSize);
218+
219+
var radianX = (float)Math.PI * angleX / 180f;
220+
var radianY = (float)Math.PI * angleY / 180f;
221+
222+
provider.RunValidatingProcessorTest(
223+
c => c
224+
.SetTextOptions(o =>
225+
{
226+
o.HorizontalAlignment = HorizontalAlignment.Center;
227+
o.VerticalAlignment = VerticalAlignment.Center;
228+
o.Transform = Matrix3x2.CreateSkew(radianX, radianY, new Vector2(rotationOriginX, rotationOriginY));
229+
})
230+
.DrawText(text, font, Color.Black, new PointF(x, y)),
231+
$"{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})",
232+
TextDrawingComparer,
233+
appendPixelTypeToFileName: false,
234+
appendSourceFileOrDescription: true);
235+
}
236+
161237
/// <summary>
162238
/// Based on:
163239
/// https://github.com/SixLabors/ImageSharp/issues/572

0 commit comments

Comments
 (0)