1

I have DirectWrite setup to render single glyphs and then shape them programmatically based on the glyph run and font metrics. (Due to my use case, I can't store every full texture in an OpenGL texture otherwise it's essentially a memory leak. So we store each glyph into one texture to lay them out glyph by glyph.)

However, I have two issues:

  1. Inconsistent rendering results.
  2. Scaling metrics leads to inconsistent distances between glyphs.

These results are are transferred to a bitmap using Direct2D and WIC bitmap (CreateWicBitmapRenderTarget).

Let's look at an example, font size 12 with Segoe UI. enter image description here

Full string rendered 1st line is rendered using DrawTextLayout drawn with D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT. 2nd line is drawn with each Glyph using DrawGlyphRun with DWRITE_MEASURING_MODE_NATURAL. 3rd is rendered with paint.net just for reference.

This leads to the second issue, the distance between each letter can be off. I am not sure if this is a symptom of the previous issue. You can see the distance between s and P is now 2 pixels when drawn separately. Because i is no longer 3 pixels wide, it visually looks too close to c now when zoomed out. p and e look too close.

I have checked the metrics, and I am receiving the right metrics from the font from shaping. Above string metrics from DirectWrite : [1088.0, 1204.0, 1071.0, 946.0, 496.0, 1071.0, 869.0]. I am comparing output with Harfbuzz: [S=0+1088|p=1+1204|e=2+1071|c=3+946|i=4+496|e=5+1071|s=6+869] which is correct.

To convert to DIP I am using this formula for the ratio multiplier: (size * dpi) / 72 / metrics.designUnitsPerEm

So with a default DPI of 96 and default size of 12 we get the following ratio: 0.0078125.

Let's look at S is 1088. So the advance should be 1088 * 0.0078125 = 8.5. Since we can't write between half a pixel, which way do we go? I tried every value from the lsb, to the advance, to render offset in every combination of flooring, ceiling, rounding, converting to int. Whichever way I choose, even if it fixes it for one situation, I'll test with another font, or another size, it will be one or two pixels too close in another string. I just can't seem to find a proper balance that is universal.

I am not really sure where to go from here. Any suggestions are appreciated. Here is the code: https://github.com/pyglet/pyglet/blob/master/pyglet/font/directwrite.py#L1736

EDIT: After a suggestion of using DrawGlyphRun using the full run, it does appear exactly what the DrawTextLayout outputs. So the DrawGlyphRun should produce the same appearance. Here's where it gets interesting:

Line1: DrawTextLayout

Line2: Single glyphs drawn by DrawGlyphRun

Line3: All glyphs drawn using DrawGlyphRun

enter image description here

You can see something interesting. If I render each 'c' by itself (right side), you can see that it has 4 pixels on the left of the character by itself. But in the strings it looks like it's missing. Actually, taking a deeper look, and a color dropper, it appears the color is indeed there, but it's much darker. So somehow each glyph is affecting the blend of the pixels around it. I am not really sure how it's doing this.

EDIT2: Talking with another, I think we narrowed this down to anti-aliasing. Applying the antialias to the whole string vs each character produces a different result. Setting D2D1_TEXT_ANTIALIAS_MODE_ALIASED each character looks and appears exactly the same now compared to both.

Charlie
  • 680
  • 4
  • 11
  • Have you marked your program as high DPI aware (preferably per-monitor DPI aware)? – SoronelHaetir Apr 18 '22 at 01:09
  • The windows being created aren't marked as DPI aware. I also do not use any scaling when testing. – Charlie Apr 18 '22 at 05:20
  • 1
    Have you tried the same text as a group of glyphs? Should be ok (difficult to compare with Pain.NET as you're not sure you're using the same code). In the general case, you can't render glyphs one by one and hope the final result will be identical and visually ok. There are font features unrelated to individual glyphs (https://learn.microsoft.com/en-us/windows/win32/api/dwrite/ne-dwrite-dwrite_font_feature_tag) and you can't manually reproduce what clear types does (sub-pixel rendering) – Simon Mourier Apr 18 '22 at 06:11
  • That is a very good point on the first idea. Not sure why I didn't think of that. If I draw them together as one glyph run, using `DrawGlyphRun` the output is the exact same as `DrawTextLayout`. I also do not use ClearType, just the default AA. I do know about the font features, and the text analyzer is supposed to take care of finding and applying the features to get the correct glyphs (Not to mention `DrawGlyphRun` only takes the glyph run). However upon closer inspection, you are right in that it is doing something different. I think I may have a clue. I'll update my main post. – Charlie Apr 18 '22 at 19:52
  • Turns out it's a combination of advance going into subpixel placements, certain systems and displays may place a pixel a certain way when it goes into subpixel area. It's also an issue with antialiasing being applied to an entire string, vs being applied to one character, the effect it has can be different when next to other characters. I can't seem to find a good combination in how Direct2D keeps things placed without subpixel issues. – Charlie Apr 19 '22 at 00:28

0 Answers0