Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[QUESTION] Achieve same text rendering like Chrome #1253

Closed
Danielku15 opened this issue Apr 27, 2020 · 23 comments
Closed

[QUESTION] Achieve same text rendering like Chrome #1253

Danielku15 opened this issue Apr 27, 2020 · 23 comments
Projects

Comments

@Danielku15
Copy link

The question might sound stupid at the beginning but please give it a chance 馃槈 It might not be dedicated 100% to this library but I'm giving it a shot to maybe catch an expert.

How can I achieve the exact same text/font rendering as Chrome does it with the HTML5 canvas?

I am maintaining a library available in JavaScript and in .net which is rendering music notation to a canvas. For this library I created a visual regression test suite based on images rendered in Chrome. On my .net version I use SkiaSharp and my tests fail with slight differences on the text rendering.

As Chrome is using Skia underneath and I'm testing on the same machine/platform I guess it should be possible to achieve the same render output. Here an example test that fails:

Reference (Chrome) SkiaSharp
image image
image image

The difference is subtle for humans but significant on technical comparison. On the first example you can see it very good on the character t of "Standard" how the character alignment is different. Also the whole anti aliasing is slightly different heavily on the second example.

Are there some special settings on the font rendering and anti aliasing that chrome uses? Do they use a different text shaper than SkiaSharp? Skia mentions that the text layouting is done by another library and Chrome is using HarfBuzz. For text hinting I guess GDI is used.

I am wondering if SkiaSharp is using the same settings as Chrome for compiling Skia native. Are there settings to control the text rendering allowing me to achieve the same output in SkiaSharp as in Chrome?

@Redth Redth added this to Needs Triage in Triage Apr 28, 2020
@Gillibald
Copy link
Contributor

SkiaSharp also supports using HarfBuzz to properly shape the text. The current version uses DirectWrite on Windows, as far as I know, Chrome uses Freetype. Some shared code might help to investigate this further.

@mattleibow
Copy link
Contributor

It might also be that Chrome is making use of some of the text hinting and subpixel things...

@Gillibald
Copy link
Contributor

Let's compare SkiaSharp v2 with current Chrome. That should yield similar results.

@mattleibow
Copy link
Contributor

You can check out the preview bits in the preview feed: https://aka.ms/skiasharp-eap/index.json

I am busy sorting out the branches and getting CI to a more official preview place. But that is a fairly recent build already.

@Danielku15
Copy link
Author

Danielku15 commented Apr 28, 2020

Some shared code might help to investigate this further.
@Gillibald

I prepared a small example:
C# code here: https://gist.github.com/Danielku15/63a6a58cbe8cf0ac61d5b16e53715fd9
Web variant: https://jsfiddle.net/danielku15/x91g7pte/5/
On my machine I have this output:
Web version:
reference
C# version:
output

After I opened this issue yesterday I found that SubpixelText already made quite a difference on some texts. But with the given example I can still see a significant difference.

I am busy sorting out the branches and getting CI to a more official preview place. But that is a fairly recent build already.

No problem, such a feed works out fine for me in this case 馃槈
I had a peek at the latest preview but without any changes to the code the output is still the same.

@Danielku15
Copy link
Author

I gave HarfBuzz a try. For some characters it gets better, for some worse resulting in a similar difference percentage on my tests.

Unfortunately HarfBuzz breaks the SkTextAlign.Center but I could work around this in worst case.

Comparing the results of my sample project I get this output:
image

Default: has slightly more space before the dot and the text seems more opaque with a stronger black and less anti-aliasing.
HarfBuzz: Similar opaque than the default rendering. The overall spacing is more compact.

@Gillibald
Copy link
Contributor

What happens if you clear the canvas with a non transparent color and enable LcdRenderText on the paint? This should enable ClearType.

@Danielku15
Copy link
Author

Output with LcdRenderText=true and a white background before rendering text:

image

HarfBuzz and the default DrawText got a lot similar with this. Just the dot character on the default rendering occupies slightly more space. Unfortunately still not very similar to Chrome.

@Danielku15
Copy link
Author

I digged now very deep into Skia and Chromium and finally managed to setup the rendering correctly using SkiaSharp 2. I compiled Chromium on my machine and extended Skia with additional logs that tell me what's happening insight. It seems that the default SkiaSharp.HarfBuzz logic does not shape and align the glyphs like Chrome. When I had a look at the SkTextBlobRuns and the glyph positions I saw that there were quite some differences.

I updated my gist with the variant I adopted from the Chromium source and achieved exactly the same glyph positions for my test:
https://gist.github.com/Danielku15/63a6a58cbe8cf0ac61d5b16e53715fd9

image

As a next step I will try using the EAP in my Library to really see if really all text renderings are now done correctly. There is still a slight difference on the grayscale of the glyphs where I'm not sure where it comes from.

I was wondering if there is an estimated release date for SkiaSharp 2 already? Achieving the correct rendering heavily relies on SkiaSharp 2 uses the >=m80 branch of Skia. They reworked the text rendering and API somewhere between m73 and m75.

@mattleibow
Copy link
Contributor

mattleibow commented Oct 14, 2020

@Danielku15 are things working better for you now?

It sort of looks like this set of properties does the main work?

            using var skFont = new SKFont
            {
                Edging = SKFontEdging.Antialias,
                Subpixel = true,
                Hinting = SKFontHinting.Normal,
                Typeface = typeface,
                Size = size
            };

With the color difference, are they actually using black or an off-black/dark grey?

@Danielku15
Copy link
Author

I haven't checked since a while the new NuGet packages. I just saw that 2.80.2 is now officially available since a while. I will try to check again my tests soon to check the differences again in detail. I hope I can have an early look this weekend.

@Danielku15
Copy link
Author

are things working better for you now?

I just made some tests with the 2.80.2 packages and the result is roughly the same as it used to be:
image

  1. Chrome m86
  2. Custom HarfBuzz: Glyphs align perfectly, Chrome is lighter / has less-alpha
  3. Normal DrawText: Glyphs do not align, too wide
  4. Normal DrawShapedText: Glyphs do not align, too narrow

Drawing on a transparent surface or on a white one results in the same differences.

With the color difference, are they actually using black or an off-black/dark grey?

The resulting image from Chrome is having pure black (r: 0, g:0, b: 0) with different opacities. The gray is just the result of drawing it on a white surface for comparison or visual perception on screen.

e.g. the character 'm' of the word 'domain' on the first column of pixels the opacity is 60% in Chrome (left). In my output it is 71% (right). The one darker pixel of the second column is 81% in Chrome, 100% on my output. The very dark column at the end is for both outputs at 100% opacity.
image

@mattleibow
Copy link
Contributor

Thanks for the feedback. Can we close this?

@Danielku15
Copy link
Author

Not really, my question is still open and unanswered: how can I get the exact same output like Chrome using SkiaSharp. While I was able to figure out the text shapingg part on my own, the aspect of colors/opacity is still unclear.

@mattleibow
Copy link
Contributor

Using HarfBuzz is the correct way. Chrome is using a text shaping engine to do what you did. And the colors, are you sure they are not drawing the text with an opacity? Or maybe layers with some sort of blending?

@Gillibald
Copy link
Contributor

The moment ClearType comes into play you will not get the same results. Are you running this on Windows or on Linux, OSX as well?

Are you using a Canvas to draw the text?

@Gillibald
Copy link
Contributor

@Danielku15
Copy link
Author

And the colors, are you sure they are not drawing the text with an opacity?

I am not 100% sure by code. But the fact that some pixels manage to reach the 100% opacity indicates that they are drawing it just normally and the rest happens during drawing.

The moment ClearType comes into play you will not get the same results. Are you running this on Windows or on Linux, OSX as well?

For now I am testing only on Windows 10. For the analysis I also ensure not to compare images generated across different systems.

Are you using a Canvas to draw the text?

I am still using the same code snippets like before:
C#: https://gist.github.com/Danielku15/63a6a58cbe8cf0ac61d5b16e53715fd9
JS: https://jsfiddle.net/danielku15/c3hjmnk2/

@mattleibow
Copy link
Contributor

@Gillibald I'll create a PR with that so we can test, but I did a search in the entire chromium source and that is the only reference to that define.

I am going to ask on skia-discuss to try and understand it a bit better: https://groups.google.com/g/skia-discuss/c/gRU6aa0xhvU

@Danielku15
Copy link
Author

With the latest SkiaSharp I was able to get a rendering that close, that my visual tests succeed. From my perspective we can close this issue. Just some additional bits for you:

  1. Chrome scales the HarfBuzz font differently than the DrawShapedText does today. It scales it as described within my Gist (1 << 16) and then maps it back.
  2. Rendering via DrawShapedText does not respect the text-align anymore. The x-coordinate of the text needs to be be substracted by width/2 or width depending on the alignment. The width is calculated via the XAdvance of the of the positions. (again see last gist version).
  3. The vertical align is the part which gave me the biggest pain. To align on the right baseline (hanging, middle, bottom) like the HTML5 canvas settings you need to do a lot of magic. You need to decode the OS/2 tag from the TypeFace to get the ascender/descender lines. Also they have some special roundings of the ascender and descender in some cases.

Quite a nightmare that they have so many hacks to align the text 馃槰 Makes it hard to get a consistent rendering using Skia directly.

Some reference links on the last point:
https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/html/canvas/text_metrics.cc;l=14?q=GetFontBaseLine&ss=chromium
https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/fonts/simple_font_data.cc;drc=0cf3ae8180a6ed761609f2907b59124e366e41b9;l=269
https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/fonts/font_metrics.cc;drc=0cf3ae8180a6ed761609f2907b59124e366e41b9;l=47

Now I only single failing test left is some edgy rendering of quadratic curves on small scales. But I guess this is another special Chrome thing I will have to find out. The numbers passed into the HTML5 rendering context are the same as I pass into Skia. I might open a different issue for that one if I find something significant in SkiaSharp 馃槈

Chrome:
image
SkiaSharp:
image

Triage automation moved this from Needs Triage to Done Feb 22, 2021
@Gillibald
Copy link
Contributor

To sum it up Chrome isn't using the font author's default scaling and instead just uses a different scaling which results in a slightly different rendering.

@Danielku15
Copy link
Author

@Gillibald To be precise: SkiaSharp uses 512 as harfbuzz<->skia scaling factor while Chrome uses 1 << 16 (65k) (and here). This seem to have a significant impact on this sub-pixel differences of small text sizes. From what I've seen the rest of the logic is quite the same. Chrome just seem to cover more corner cases and setting variations (language, script, subpixel on/off). The biggest impact had the upgrade of the underlying Skia version in SkiaSharp.

I plan to open a PR with an extension to allow configuring this scale factor and also fixing missing handling of the text alignment. (I want to get rid of this custom code on my side 馃榿 )

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
No open projects
Triage
  
Done
Development

No branches or pull requests

3 participants