0

I'm attempting to use the Bebas font to render text to a Bitmap in C#.

Consider the following code that loads a .png from the filesystem to render overlaying text:

public Bitmap CreateCard(AccumulatedStats player, string template, bool includeGamerpic = false)
    {
        Bitmap imageCache = new Bitmap(template);
        using (Graphics g = Graphics.FromImage(imageCache))
        {
            //AddAvatar(g, player);
            AddGamertag(g, player);
            //AddStats(g, player);
            //AddLogo(g, player);
            AddPrimaryPosition(g, player);
        }
        return imageCache;
    }

The AddGamertag method draws text at size 38 font, and renders correctly:

good text

The AddPrimaryPosition method draws text at size 12 font with unacceptable quality:

        private void AddPrimaryPosition(Graphics g, AccumulatedStats player)
    {
        Brush bgText = new SolidBrush(Color.FromArgb(red: 238, green: 243, blue: 234));
        var centerAlign = new StringFormat() { Alignment = StringAlignment.Center };
        Font bebas = new Font("Bebas", 12);

        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
        g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
        g.DrawString($"Lorem Ipsum\nYour Name Here", bebas, bgText, new PointF(601, 221), centerAlign);
        //g.DrawString($"Lorem Ipsum\nYour Name Here", bebas, Brushes.Black, new PointF(843, 221), centerAlign);
    }

bad text

Note that I am using SmoothingMode and TextRenderingHint to specify antialiasing.

Compare that to the quality from Paint.NET: pdn settings

pdn results

I found a question noting similar behaviour, where the accepted answer suggested that the area was not initialized. To remedy this I tried to draw a colored rectangle first before drawing the text, in hopes that the antialiasing would be able to sample from the rectangle behind the text:

        private void AddPrimaryPosition(Graphics g, AccumulatedStats player)
    {
        Brush bgText = new SolidBrush(Color.FromArgb(red: 238, green: 243, blue: 234));
        var centerAlign = new StringFormat() { Alignment = StringAlignment.Center };
        Font bebas = new Font("Bebas", 12);

        // Slight red offset for testing, should be 0
        var islesBlue = new SolidBrush(Color.FromArgb(red: 200, green: 83, blue: 155));
        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
        Rectangle rect = new Rectangle(499, 219, 203, 66);
        g.FillRectangle(islesBlue, rect);
        g.DrawRectangle(new Pen(islesBlue), rect);
        

        g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
        g.DrawString($"Lorem Ipsum\nYour Name Here", bebas, bgText, new PointF(601, 221), centerAlign);
        g.DrawString($"Lorem Ipsum\nYour Name Here", bebas, Brushes.Black, new PointF(843, 221), centerAlign);
    }

still bad text

Note that I also rendered the text further to the right, where I have a background that matches the color of the background where AddGamertag draws the larger text.

I changed this method to set SmoothingMode=None and TextRenderingHint=SingleBitPerPixel, which looks different (indicating some antialiasing was happening previously) but also unacceptably poor:

bad text no aa

Why is the antialiasing quality so poor at smaller font sizes? Is there a way to resolve this without resorting to larger text or a different font face?

ravibhagw
  • 1,740
  • 17
  • 28
  • 1
    What is `template` pointing to? What kind of Bitmap it that? Have you tested with a 32bpp ARGB image? You can keep `TextRenderingHint.AntiAliasGridFit` -- Unrelated: you should pass `rect` to the first call to `DrawString()` / `DrawRectangle()` is useless / Disposable stuff needs to be disposed (Font, Brush, StringFormat, which may want to initialize with `StringFormat.GenericTypographic`) – Jimi Aug 23 '23 at 18:41
  • 1
    BTW, a 32bpp ARGB format is the only format you should use when performing graphic operations. If you then want to store the image in another format, do that after. If you reload that bitmap for further operations, copy it to a 32bpp image and work with that – Jimi Aug 23 '23 at 18:47
  • 1
    Maybe not the actual answer by try to use `TextRenderer.DrawText` instead of `Graphics.DrawString`. Starting with .NET Framework 2.0 `TextRenderer` is the preferred way even by controls unless `UseCompatibleTextRendering` is set. It has a better quality in some cases, uses better spacing and for some cultures it simply works where latter has issues. Also, use `TextRenderingHint.ClearTypeGridFit` instead of `AntiAlias`. Maybe it won't help for this font: classic fonts contain specific hints for very small sizes that no one uses anymore because it's very tedious to calibrate these parameters. – György Kőszeg Aug 23 '23 at 19:08
  • 1
    @GyörgyKőszeg The OP is drawing on a Bitmap, not the surface of a Control. Of course you can use TextRenderer, but with some differences. `TextRenderingHint.ClearTypeGridFit` won't do, for example. `UseCompatibleTextRendering` becomes quite irrelevant :) – Jimi Aug 23 '23 at 19:38
  • 1
    @Jimi: Based on the smiley I suspect you try to intentionally misinterpret my point. I'm not native English but I think it's totally understandable when I'm talking about the default text rendering method that is preferred today even by controls unless it's explicitly declared that they should use the old approach. But I think you got that perfectly. I nowhere stated the OP was drawing on controls. – György Kőszeg Aug 23 '23 at 20:21
  • @GyörgyKőszeg I understand what you meant. My point is that the reference to compatible rendering is not relevant in this context. But, mostly, that if you suggest using TextRenderer - perfectly valid suggestion per se - then `TextRenderingHint.ClearTypeGridFit` is not a good tip. [Grid-fitting applies to GDI+, not GDI](https://stackoverflow.com/a/49953353/7444103) – Jimi Aug 23 '23 at 20:51
  • @Jimi: I didn't consider ASCII ART or a bunch of repeated letters just the result of some natural text from close. And for me it provided the best quality, which was definitely [better](https://i.imgur.com/ekI6o6A.png) better with `TextRenderer` than by `Drawing.DrawString`. Just compare the letters "i", "d" and "l" in the linked image. They have much more better quality by GDI (`TextRenderer`) than by GDI+. But maybe there might be driver differences as the OP has noticed. As you can see for me it was the only setting that worked as expected with GDI. – György Kőszeg Aug 23 '23 at 21:31
  • @GyörgyKőszeg I've posted that link not because of the rendering part, but in relation to the notes about GDI+, GDI and, specifically, the Grid-Fitting feature (GDI+ only, hence `TextRenderingHint.ClearTypeGridFit` is not a *good fit* when rendering with GDI. You don't need anti-aliasing either) -- The *driver* the OP mentioned is not relevant, they probably used a different bitmap as canvas in another machine, or something like that. Hardware / drivers issues would reproduce also with a larger Font – Jimi Aug 23 '23 at 21:54
  • @jimi I used the same Bitmap as canvas on both machines. All template images are in the git repo. I cloned the git repo to my desktop and re-ran the code. I have also tried it with multiple images on both machines to try and understand and consistently the MacBook draws incorrectly while the desktop draws correctly. – ravibhagw Aug 23 '23 at 23:05
  • HA, a MacBook, I had not considered it. It could be interesting. You still haven't said what type of images you're dealing with. So, make a test. You need to work on a copy of the Bitmap that is for sure 32bpp ARGB, otherwise it doesn't matter. Take the method [here](https://stackoverflow.com/a/61977870/7444103). Then, as mentioned, GDI+ grid-fitting has specific alignment features. Try instead to render the text with TextRenderer (get it from, e.g., [here](https://stackoverflow.com/a/53745899/7444103)) (as shown, you have to specify a background color). See how it goes and please let me know – Jimi Aug 23 '23 at 23:47

1 Answers1

0

I tried running the code on a different computer, and the resulting quality was significantly better:

better version

The PC I ran this on initially is an old Intel-based Macbook running Windows 10 via Bootcamp, with an Intel Core i2xxx series CPU and Intel HD Graphics 4000.

The PC I re-ran this on is a Windows 10 desktop with an Intel Core i2500k CPU and an nvidia Geforce GTX970 GPU.

My guess is that it's a Windows GDI+ issue,as all other variables (source code, source image loaded as Bitmap, etc) have remained the same.

The explanation for why it may be a GDI+ issue is presented here: Graphics.DrawString vs TextRenderer.DrawText?Which can Deliver Better Quality

Consider using TextRenderer.DrawText

ravibhagw
  • 1,740
  • 17
  • 28
  • ClearText sub-pixel antialiasing is tuned per-display, so it is expected to vary from computer to computer. Screenshots of sub-pixel antialiased text are not expected to look reasonable on any other display. – Ben Voigt Aug 28 '23 at 14:24