Browse Source

Improve text rendering accuracy

pull/755/head
Anton Firszov 8 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 private class CachingGlyphRenderer : IGlyphRenderer, IDisposable
{ {
// just enough accuracy to allow for half pixel differences which // just enough accuracy to allow for 1/8 pixel differences which
// later are componded into full pixel offsets while rendering. // later are accumulated while rendering, but do not grow into full pixel offsets
private const float AccuracyMultiple = 2; // 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; private readonly PathBuilder builder;
@ -222,14 +225,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
public bool BeginGlyph(RectangleF bounds, GlyphRendererParameters parameters) public bool BeginGlyph(RectangleF bounds, GlyphRendererParameters parameters)
{ {
this.currentRenderPosition = Point.Truncate(bounds.Location); 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; subPixelOffset.X = MathF.Round(subPixelOffset.X * AccuracyMultiple) / AccuracyMultiple;
dif.Y = ((int)(dif.Y * 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 // 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.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)) if (this.glyphData.ContainsKey(this.currentGlyphRenderParams))
{ {
// we have already drawn the glyph vectors skip trying again // 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 SixLabors.Primitives;
using Xunit; using Xunit;
using Xunit.Abstractions;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Drawing.Text 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 TextDrawingComparer = ImageComparer.TolerantPercentage(1e-5f);
public static ImageComparer OutlinedTextDrawingComparer = ImageComparer.TolerantPercentage(1e-4f); public static ImageComparer OutlinedTextDrawingComparer = ImageComparer.TolerantPercentage(1e-4f);
public DrawTextOnImageTests(ITestOutputHelper output)
{
this.Output = output;
}
private ITestOutputHelper Output { get; }
[Theory] [Theory]
[WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)] [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)] [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; 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( provider.VerifyOperation(
TextDrawingComparer, comparer,
img => img =>
{ {
img.Mutate(c => c.DrawText(textOptions, sb.ToString(), font, color, new PointF(10, 5))); 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] [Theory]
[WithSolidFilledImages(1000, 1500, "White", PixelTypes.Rgba32)] [WithSolidFilledImages(1000, 1500, "White", PixelTypes.Rgba32, "Baskerville Old Face")]
public static void TextPositioningIsRobust<TPixel>(TestImageProvider<TPixel> provider) [WithSolidFilledImages(1000, 1500, "White", PixelTypes.Rgba32, "Arial")]
public void TextPositioningIsRobust<TPixel>(TestImageProvider<TPixel> provider, string fontName)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (!TestEnvironment.IsWindows) if (!TestEnvironment.IsWindows)
@ -162,14 +176,24 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
return; 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", 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); 20);
var textOptions = new TextGraphicsOptions(true) { WrapTextWidth = 1000 }; 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( 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)); 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