Using C# to measure the width of a string in pixels in a cross platform way

2.1k Views Asked by At

I have some existing C# code that uses System.Drawing.Common to measure the approximate width of a string in pixels:

var text = "abc123 this is some long text my dog's name is fido.";

using (var bitmap = new Bitmap(500, 50))
using (var graphics = Graphics.FromImage(bitmap))
{
    // Size: 9 Points
    using var font = new System.Drawing.Font(familyName: "Times New Roman", emSize: 9f);
    
    var ms = graphics.MeasureString(text, font);
    
    // Output: 'abc123 this is some long text my dog's name is fido.' via System.Drawing: 394.00195 x 22.183594
    Console.WriteLine($"'{text}' via System.Drawing: {ms.Width} x {ms.Height}");
}

After upgrading to .NET 6.0, I got a bunch of warning messages telling me that these graphics primitives are only supported on Windows. I want this measurement to work on other platforms, so I tried to do something similar with both SkiaSharp:

var text = "abc123 this is some long text my dog's name is fido.";

using (var paint = new SKPaint())
{
    paint.Typeface = SKTypeface.FromFamilyName("Times New Roman");
    
    // Size: 12px
    paint.TextSize = 12f;
    
    var skBounds = SKRect.Empty;
    var textWidth = paint.MeasureText(text.AsSpan(), ref skBounds);
    
    // Output: 'abc123 this is some long text my dog's name is fido.' via SkiaSharp: 251.13867 x 12
    Console.WriteLine($"'{text}' via SkiaSharp: {skBounds.Width} x {skBounds.Height}");
}

And ImageSharp:

var text = "abc123 this is some long text my dog's name is fido.";

// Size: 12px
var imgSharpFont = SixLabors.Fonts.SystemFonts.CreateFont("Times New Roman", 12f);
var imgSharpMeasurement = TextMeasurer.Measure(text, new RendererOptions(imgSharpFont));

// Output: 'abc123 this is some long text my dog's name is fido.' via ImageSharp: 251.13869 x 14.589844
Console.WriteLine($"'{text}' via ImageSharp: {imgSharpMeasurement.Width} x {imgSharpMeasurement.Height}");

However, as you can see, I can't get SkiaSharp or ImageSharp to produce the same width, although they produce similar results:

'abc123 this is some long text my dog's name is fido.' via System.Drawing: 394.00195
'abc123 this is some long text my dog's name is fido.' via SkiaSharp: 251.13867
'abc123 this is some long text my dog's name is fido.' via ImageSharp: 251.13869

I don't understand graphics programming enough to know what I'm missing. It might be a unit conversion between Points and Pixels, or perhaps I'm not setting the correct properties. Any ideas on how to make SkiaSharp and/or ImageSharp return the same width measurement as System.Drawing.Common?

Thank you.

1

There are 1 best solutions below

0
On

Basically the issue is due to the fact that System.Drawing.Graphics will be using the DPI of your machine (The reason is because System.Drawing is built around GDI which is a windows subsystem for drawing images to screens and printers) by default.

This is opposition to ImageSharp and SkiaSharp as both be defaulting to 72 DPI. (I know ImageSharp definitely does, and as the numbers between SkiaSharp and ImageSharp are within a rounding error of each other it must be using a DPI of 72 as well).

Based on the numbers you provided, I would have to guess you are running a 112 DPI monitor?