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
source to share
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
andGraphics.DrawString
) - GDI (
TextRenderer.MeasureText
andTextRenderer.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
:
+ The GDI : TextRenderingHintAntiAlias
, the GDI : ANTIALIASED_QUALITY
:
+ The GDI : TextRenderingHintAntiAliasGridFit
, the GDI : not supported, uses ANTIALIASED_QUALITY:
+ The GDI : TextRenderingHintSingleBitPerPixelGridFit
, the GDI : PROOF_QUALITY
:
+ The GDI : TextRenderingHintSingleBitPerPixel
, the GDI : DRAFT_QUALITY
:
I consider it odd that it DRAFT_QUALITY
is identical PROOF_QUALITY
, which is identical CLEARTYPE_QUALITY
.
see also
- UseCompatibleTextRendering - compatible with whaaaaaat?
- Sort everything: Whidbey TextRenderer Quick View
- MSDN: LOGFONT Structure
- AppCompat Guy: GDI and GDI + Text Rendering Efficiency
- GDI + Text, Resolution Independence, and Rendering Techniques. Or - Why does my text look different in GDI + and in GDI?
source to share
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.
source to share