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

1

9

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.

Somnambulate answered 10/11, 2021 at 3:35 Comment(4)
did you render them and see how different they are in size? Very likely different font sizes.Unconditional
Render the images and compare them. What you posted are just rounding errors and both images are 251 pixels wide. Unless antialiasing is used. System.Drawing.Graphics is using Windows defaults, algorithms, resolution, kerning, spacing, margins etc. It's probably affected by the user settings too. Without specifying a device, a default one will be used which is probably different between libraries.Bicameral
Besides, 9 points isn't 12 pixels. The actual pixel size depends on the image resolution (DPI). Points are an actual physical size, similar to inches or centimeters. How many pixels are needed to print 9 points depends on the resolution of the device - fewer pixels are used to draw the same string on a non-HD screen than a 4K screen. And printers work at even higher resolutionsBicameral
Check the Wikipedia article on Point (typography) The DTP point is defined as 1⁄72 of an international inch ( 1 / 72 × 25.4 mm ≈ 0.353 mm) and, as with earlier American point sizes, is considered to be 1⁄12 of a pica.Bicameral
B
5

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?

Burgher answered 10/11, 2021 at 16:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.