Browse Source

Improve text rendering accuracy

pull/755/head
Anton Firszov 7 years ago
parent
commit
381f83d543
  1. 18
      src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs
  2. 34
      tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs
  3. 2
      tests/Images/External

18
src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs

@ -165,9 +165,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
private class CachingGlyphRenderer : IGlyphRenderer, IDisposable
{
// just enough accuracy to allow for half pixel differences which
// later are componded into full pixel offsets while rendering.
private const float AccuracyMultiple = 2;
// just enough accuracy to allow for 1/8 pixel differences which
// later are accumulated while rendering, but do not grow into full pixel offsets
// The value 8 is benchmarked to:
// - Provide a good accuracy (smaller than 0.2% image difference compared to the non-caching variant)
// - Cache hit ratio above 60%
private const float AccuracyMultiple = 8;
private readonly PathBuilder builder;
@ -222,14 +225,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
public bool BeginGlyph(RectangleF bounds, GlyphRendererParameters parameters)
{
this.currentRenderPosition = Point.Truncate(bounds.Location);
PointF dif = bounds.Location - this.currentRenderPosition;
PointF subPixelOffset = bounds.Location - this.currentRenderPosition;
dif.X = ((int)(dif.X * AccuracyMultiple)) / AccuracyMultiple;
dif.Y = ((int)(dif.Y * AccuracyMultiple)) / AccuracyMultiple;
subPixelOffset.X = MathF.Round(subPixelOffset.X * AccuracyMultiple) / AccuracyMultiple;
subPixelOffset.Y = MathF.Round(subPixelOffset.Y * AccuracyMultiple) / AccuracyMultiple;
// we have offset our rendering origion a little bit down to prevent edge cropping, move the draw origin up to compensate
this.currentRenderPosition = new Point(this.currentRenderPosition.X - this.offset, this.currentRenderPosition.Y - this.offset);
this.currentGlyphRenderParams = (parameters, dif);
this.currentGlyphRenderParams = (parameters, subPixelOffset);
if (this.glyphData.ContainsKey(this.currentGlyphRenderParams))
{
// we have already drawn the glyph vectors skip trying again

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

@ -11,6 +11,8 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using Xunit;
using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Drawing.Text
@ -25,6 +27,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
public static ImageComparer TextDrawingComparer = ImageComparer.TolerantPercentage(1e-5f);
public static ImageComparer OutlinedTextDrawingComparer = ImageComparer.TolerantPercentage(1e-4f);
public DrawTextOnImageTests(ITestOutputHelper output)
{
this.Output = output;
}
private ITestOutputHelper Output { get; }
[Theory]
[WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)]
[WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)]
@ -87,8 +96,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
TPixel color = NamedColors<TPixel>.Black;
// Based on the reported 0.0270% difference with AccuracyMultiple = 8
// We should avoid quality regressions leading to higher difference!
var comparer = ImageComparer.TolerantPercentage(0.03f);
provider.VerifyOperation(
TextDrawingComparer,
comparer,
img =>
{
img.Mutate(c => c.DrawText(textOptions, sb.ToString(), font, color, new PointF(10, 5)));
@ -152,8 +165,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
}
[Theory]
[WithSolidFilledImages(1000, 1500, "White", PixelTypes.Rgba32)]
public static void TextPositioningIsRobust<TPixel>(TestImageProvider<TPixel> provider)
[WithSolidFilledImages(1000, 1500, "White", PixelTypes.Rgba32, "Baskerville Old Face")]
[WithSolidFilledImages(1000, 1500, "White", PixelTypes.Rgba32, "Arial")]
public void TextPositioningIsRobust<TPixel>(TestImageProvider<TPixel> provider, string fontName)
where TPixel : struct, IPixel<TPixel>
{
if (!TestEnvironment.IsWindows)
@ -162,14 +176,24 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
return;
}
Font font = SystemFonts.CreateFont("Baskerville Old Face", 30, FontStyle.Regular);
Font font = SystemFonts.CreateFont(fontName, 30, FontStyle.Regular);
string text = Repeat("Beware the Jabberwock, my son! The jaws that bite, the claws that catch! Beware the Jubjub bird, and shun The frumious Bandersnatch!\n",
20);
var textOptions = new TextGraphicsOptions(true) { WrapTextWidth = 1000 };
string details = fontName.Replace(" ", "");
// Based on the reported 0.1755% difference with AccuracyMultiple = 8
// We should avoid quality regressions leading to higher difference!
var comparer = ImageComparer.TolerantPercentage(0.2f);
provider.RunValidatingProcessorTest(
x => x.DrawText(textOptions, text, font, NamedColors<TPixel>.Black, new PointF(10, 50)), appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
x => x.DrawText(textOptions, text, font, NamedColors<TPixel>.Black, new PointF(10, 50)),
details,
comparer,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
private static string Repeat(string str, int times) => string.Concat(Enumerable.Repeat(str, times));

2
tests/Images/External

@ -1 +1 @@
Subproject commit 5634b8b2affa1158cec31ccbb67f9583e9137234
Subproject commit b7a7eacc0446a1acc266f30ddadbda40fccde231
Loading…
Cancel
Save