How to draw GDI + text regardless of DPI

I am drawing text using GDI +. I recently noticed that this text automatically scales when the DPI changes. Is there a way to make the GDI + text drawing DPI independent? For example. I want to draw text up to 20px regardless of DPI. Is it possible? How to do it?

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

    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;
    }

      

Hello

+3


source to share


2 answers


I wanted to mention something slightly unrelated to your question. You shouldn't use it Graphics.DrawString

in GDI + anymore . It is deprecated in .NET 2. Instead, Microsoft created the TextRenderer.DrawString

.

There are two ways to draw 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 statelessness of GDI + where device contexts will be set and then the original restored after each call.
  • The shaping mechanisms for international text have been repeatedly updated for Windows / Uniscribe and for Avalon (Windows Presentation Foundation), but have not been updated for GDI +, which causes international support for new languages ​​to not be of the same quality level.

So they knew they wanted to change the .NET platform to stop using the GDI + rendering engine and use GDI . At first, they hoped they could just change:

graphics.DrawString

      

to call the old API DrawText

instead of GDI +. But they couldn't make the text match and spacing exactly the same as GDI +. So they had to leave Graphics.DrawString

GDI + to invoke (compatibility reasons; people who called Graphics.DrawString

suddenly found their text didn't wrap the way it was used).

Created a new static class TextRenderer

for wrapping GDI text. It has two methods:

TextRenderer.MeasureText
TextRenderer.DrawText

      

Note:
- TextRenderer

- wrapper around GDI
- Graphics.DrawString

is still a wrapper around GDI +


Then there was a problem with what to do with all the existing .NET controls like:

  • Label

  • Button

  • TextBox

They wanted to switch them over to use TextRenderer

(i.e. GDI), but they had to be careful. There may be people who have depended on their controls, as they did in .NET 1.1. And so "compatible text rendering" was born.

By default, controls in an application behave the same as in .NET 1.1 (they are "compatible").

Disable compatibility mode by calling:

Application.SetCompatibleTextRenderingDefault(false);

      

It makes your application better, faster, with better international support. Summarizing:

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 is also helpful to note the mapping between GDI + TextRenderingHint

and the corresponding LOGFONT

Quality
used to draw the GDI font:

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)

      


Examples of

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

+ The GDI : TextRenderingHintClearTypeGridFit

, the GDI : CLEARTYPE_QUALITY

:

enter image description here

+ The GDI : TextRenderingHintAntiAlias

, the GDI : ANTIALIASED_QUALITY

:

enter image description here

+ The GDI : TextRenderingHintAntiAliasGridFit

, the GDI : not supported, uses ANTIALIASED_QUALITY:

enter image description here

+ The GDI : TextRenderingHintSingleBitPerPixelGridFit

, the GDI : PROOF_QUALITY

:

enter image description here

+ The GDI : TextRenderingHintSingleBitPerPixel

, the GDI : DRAFT_QUALITY

:

enter image description here

I consider it odd that it DRAFT_QUALITY

is identical PROOF_QUALITY

, which is identical CLEARTYPE_QUALITY

.

see also

+5


source


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 a custom DPI setting to adjust the resulting font (even if the font is used to draw in a bitmap!). The standard DPI of 96 is a good value to use to determine the correct ratio for adjusting the font size.



In the above snippet, the height of the searched font is 8px high.

fMeasuredFontHeight stays almost constant (about 12) using all DPI settings.

+1


source







All Articles