Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw a GDI + text independent of DPI

Tags:

text

dpi

draw

gdi+

I'm drawing a text using GDI+. I recently noticed that this text is automatically scaled when the DPI is changed. Is there a way to make the GDI+ text drawing independent of the DPI? E.g. I want to draw a text up to 20 pixels, regardless of the DPI. Is it possible? How to do this?

Below is a sample code. I want to draw the first text with a constant size, regardless of the DPI, and the second text normally:

    case WM_PAINT:
    {
        inherited::WndProc(message);

        Canvas->Brush->Style = bsSolid;
        Canvas->Brush->Color = clWhite;
        Canvas->FillRect(ClientRect);

        // get GDI+ graphics from canvas
        Gdiplus::Graphics graphics(Canvas->Handle);

        // set text rendering hint
        graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintSystemDefault);

        std::auto_ptr<Gdiplus::Font>         pFont(new Gdiplus::Font(Canvas->Handle, Font->Handle));
        std::auto_ptr<Gdiplus::SolidBrush>   pBrush(new Gdiplus::SolidBrush(Gdiplus::Color(255, 0, 0, 0)));
        std::auto_ptr<Gdiplus::StringFormat> pFormat(new Gdiplus::StringFormat());

        Gdiplus::FontFamily fontFamily;
        pFont->GetFamily(&fontFamily);

        std::auto_ptr<Gdiplus::Font> pFont2(new Gdiplus::Font(&fontFamily, pFont->GetSize(),
                pFont->GetStyle(), Gdiplus::UnitPixel));
        Gdiplus::Unit test = pFont->GetUnit();
        Gdiplus::Unit test2 = pFont2->GetUnit();

        pFormat->SetAlignment(Gdiplus::StringAlignmentNear);
        pFormat->SetLineAlignment(Gdiplus::StringAlignmentNear);

        Gdiplus::StringFormatFlags flags = Gdiplus::StringFormatFlagsBypassGDI;
        //flags = (Gdiplus::StringFormatFlags)(flags | Gdiplus::StringFormatFlagsDirectionRightToLeft);
        //flags = (Gdiplus::StringFormatFlags)(flags | Gdiplus::StringFormatFlagsDirectionVertical);
        //flags = (Gdiplus::StringFormatFlags)(flags | Gdiplus::StringFormatFlagsNoWrap);
        //flags = (Gdiplus::StringFormatFlags)(flags | Gdiplus::StringFormatFlagsNoClip);
        pFormat->SetFormatFlags(flags);

        pFormat->SetTrimming(Gdiplus::StringTrimmingEllipsisCharacter);
        pFormat->SetHotkeyPrefix(Gdiplus::HotkeyPrefixNone);

        std::wstring text = L"This is a sample code";

        Gdiplus::Unit prevPageUnit = graphics.GetPageUnit();

        try
        {
            graphics.SetPageUnit(Gdiplus::UnitPixel);

            // draw text
            graphics.DrawString(text.c_str(), text.length(), pFont2.get(), Gdiplus::RectF(ClientRect.Left,
                    ClientRect.Top, ClientWidth, ClientHeight), pFormat.get(), pBrush.get());
        }
        __finally
        {
            graphics.SetPageUnit(prevPageUnit);
        }

        // draw text 2
        graphics.DrawString(text.c_str(), text.length(), pFont.get(), Gdiplus::RectF(ClientRect.Left,
                ClientRect.Top + 25, ClientWidth, ClientHeight), pFormat.get(), pBrush.get());

        return;
    }

Regards

like image 722
user2066994 Avatar asked Jan 10 '23 23:01

user2066994


2 Answers

I wanted to mention something, slightly unrelated to your question. You shouldn't be using Graphics.DrawString in GDI+ anymore. It was deprecated in .NET 2. Instead Microsoft created TextRenderer.DrawString.

There are two ways of drawing text in .NET:

  • GDI+ (graphics.MeasureString and graphics.DrawString)
  • GDI (TextRenderer.MeasureText and TextRenderer.DrawText)

In .NET 1.1 everything used GDI+ for text rendering. But there were some problems:

  • There are some performance issues caused by the somewhat stateless nature of GDI+, where device contexts would be set and then the original restored after each call.
  • The shaping engines for international text have been updated many times for Windows/Uniscribe and for Avalon (Windows Presentation Foundation), but have not been updated for GDI+, which causes international rendering support for new languages to not have the same level of quality.

So they knew they wanted to change the .NET framework to stop using GDI+'s text rendering system, and use GDI. At first they hoped they could simply change:

graphics.DrawString

to call the old DrawText API instead of GDI+. But they couldn't make the text-wrapping and spacing match exactly as what GDI+ did. So they were forced to keep graphics.DrawString to call GDI+ (compatiblity reasons; people who were calling graphics.DrawString would suddenly find that their text didn't wrap the way it used to).

A new static TextRenderer class was created to wrap GDI text rendering. It has two methods:

TextRenderer.MeasureText
TextRenderer.DrawText

Note:
- TextRenderer is a wrapper around GDI
- graphics.DrawString is still a wrapper around GDI+


Then there was the issue of what to do with all the existing .NET controls, e.g.:

  • Label
  • Button
  • TextBox

They wanted to switch them over to use TextRenderer (i.e. GDI), but they had to be careful. There might be people who depended on their controls drawing like they did in .NET 1.1. And so was born "compatible text rendering".

By default controls in application behave like they did in .NET 1.1 (they are "compatible").

You turn off compatibility mode by calling:

Application.SetCompatibleTextRenderingDefault(false);

This makes your application better, faster, with better international support. To sum up:

SetCompatibleTextRenderingDefault(true)  SetCompatibleTextRenderingDefault(false)
=======================================  ========================================
 default                                  opt-in
 bad                                      good
 the one we don't want to use             the one we want to use
 uses GDI+ for text rendering             uses GDI for text rendering
 graphics.MeasureString                   TextRenderer.MeasureText
 graphics.DrawString                      TextRenderer.DrawText
 Behaves same as 1.1                      Behaves *similar* to 1.1
                                          Looks better
                                          Localizes better
                                          Faster

It's also useful to note the mapping between GDI+ TextRenderingHint and the corresponding LOGFONT Quality used for GDI font drawing:

TextRenderingHint           mapped by TextRenderer to LOGFONT quality
========================    =========================================================
ClearTypeGridFit            CLEARTYPE_QUALITY (5) (Windows XP: CLEARTYPE_NATURAL (6))
AntiAliasGridFit            ANTIALIASED_QUALITY (4)
AntiAlias                   ANTIALIASED_QUALITY (4)
SingleBitPerPixelGridFit    PROOF_QUALITY (2)
SingleBitPerPixel           DRAFT_QUALITY (1)
else (e.g.SystemDefault)    DEFAULT_QUALITY (0)

Samples

Here's some comparisons of GDI+ (graphics.DrawString) verses GDI (TextRenderer.DrawText) text rendering:

GDI+: TextRenderingHintClearTypeGridFit, GDI: CLEARTYPE_QUALITY:

enter image description here

GDI+: TextRenderingHintAntiAlias, GDI: ANTIALIASED_QUALITY:

enter image description here

GDI+: TextRenderingHintAntiAliasGridFit, GDI: not supported, uses ANTIALIASED_QUALITY:

enter image description here

GDI+: TextRenderingHintSingleBitPerPixelGridFit, GDI: PROOF_QUALITY:

enter image description here

GDI+: TextRenderingHintSingleBitPerPixel, GDI: DRAFT_QUALITY:

enter image description here

i find it odd that DRAFT_QUALITY is identical to PROOF_QUALITY, which is identical to CLEARTYPE_QUALITY.

See also

like image 103
Ian Boyd Avatar answered Apr 28 '23 09:04

Ian Boyd


This is what works for me.

using namespace Gdiplus; 

HDC hDC = ::GetDC( NULL );
int nDPI = ::GetDeviceCaps( hDC, LOGPIXELSY );
::ReleaseDC( NULL, hDC );

REAL fFontHeight = 96 / (REAL)nDPI * 8;

FontFamily fontFamily( L"Arial" );
Gdiplus::Font font( &fontFamily, fFontHeight, UnitPixel );

REAL fMeasuredFontHeight = font.GetHeight( &gr );

It turns out that Gdiplus::Font, despite being specified in pixels, uses the user's DPI setting to adjust the resulting font (even when the font is to be used to draw in a bitmap!). The standard DPI of 96 is a good value to use determine the correct ratio to adjust the font size.

In the above snippet, the font height sought was 8 pixels high.

fMeasuredFontHeight remains nearly constant (at approx. 12), through all DPI settings.

like image 22
Dave Foster Avatar answered Apr 28 '23 08:04

Dave Foster