Note that there are some explanatory texts on larger screens.

plurals
  1. POGDI+ Font Rendering, especially in layered windows
    text
    copied!<p>This my friends, is a going to be long one...</p> <p>I am getting some quite bizarre behaviour when I try to render text in a layered window.</p> <p>The strange thing is that for some combinations of font/font-style/font-size, GDI+ changes the rendering method. For Tahoma-Bold fonts sizes <em>between</em> 8.49 and 16.49 (Pixel-Units) inclusive "fail". For other fonts and styles I get "fails" at different sizes.</p> <p><img src="https://i.stack.imgur.com/lFvX4.png" alt="output from code below"></p> <p><strong>For clarity I have provided a complete executable example further down.</strong> The two key parameters to play with are on line 23:</p> <pre><code>Color g_oTextColor( 255, 240, 0, 0 ); // Simply change Color to ( 254, 240, 0, 0 ) [to add slight transparency] and everything will work! #define USE_LAYERED_WINDOW // or just comment this line out [to use a regular window], and everything will work! </code></pre> <hr> <p>When using layered windows and full opacity the fonts draw a transparent "hole" in the background. However if I add a slight transparency to the text colour (alpha-channel = 254) the fonts become opaque. Or if I use regular (non-layered) windows the fonts render opaque. <strong>What is going on here??</strong></p> <p>But even without the layered/transparency problems it is clear that something strange is happening here. The fonts size 8.49 - 16.48 get rendered pixel perfect, the other fonts have slight blurry quality, especially the small ones. So it seems that the system takes a different approach to rendering these medium sizes. <strong>Can somebody shed some light on this, how can I possibly render for example fonts size 8.0 pixels without the blurriness above?</strong> I have tried all sorts of settings for <code>SetTextRenderingHint()</code> and <code>SetTextContrast()</code> but none looked crisp for fonts of size 8. I have tried Tahoma &amp; Arial only...</p> <hr> <p><strong>Side question 1:</strong> I wanted to use pure GDI+ for the off-screen drawing but I could not get it work by simply creating <code>Bitmap</code> &amp; <code>Graphics</code> objects. I still had to use old GDI stuff to create a DC and to select the HBitmap into it. How can I do it all in GDI+?</p> <p><strong>Side question 2 (Geeks only):</strong> I also tried to draw the fonts in good-old GDI but there I got some even more bizarre effects: <strong>(1)</strong> In a layered window the text became transparent but in an <em>additive</em> way. (So a red text would look fine if the window behind was dark but if the window behind it was whit the text disappeared completely!) Furthermore if I filled my own window with a semi-transparent square, then that behave as expected. (A red square would turn dark red if the window behind it was black, and the square would turn light red over a white window). And I can observe both these behaviours simultaneously in one layered window. And <strong>(2)</strong> as a highly undesired bonus the drawn text lost it's hit-test and became un-clickable? <strong>Any explanations out there?</strong> </p> <p>And if you have read this far, thanks for enduring, and thanks for any answers!</p> <pre><code>// Create as a console application project // + Unicode charset // + Precompiled headers off // + make sure to add linker input: gdiplus.lib #ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. #define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows. #endif // Standard and GDI+ stuffstuff #include &lt;stdio.h&gt; #include &lt;tchar.h&gt; #include &lt;windows.h&gt; #include &lt;iostream&gt; #include &lt;cassert&gt; #include &lt;Gdiplus.h&gt; using namespace Gdiplus; GdiplusStartupInput g_oGdiPlusStartupInput; ULONG_PTR g_pGdiPlusToken = NULL; // #*#*#*#*#*#*#*#*# LINES TO CHANGE ----------&gt;----------&gt;----------&gt; Color g_oTextColor( 255, 240, 0, 0 ); // Simply change Color to ( 254, 240, 0, 0 ) [to add slight transparency] and everything will work! #define USE_LAYERED_WINDOW // or just comment this line out [to use a regular window], and everything will work! // Forward declarations void RegWndClass(); LRESULT CALLBACK WndProc( HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ); void CreateWindows(); void Draw(); void MsgLoop(); // Other Globals ATOM g_iWndClass = 0; HWND g_hWndGdiPlus = NULL; HWND g_hWndGdi = NULL; const wchar_t* g_pWndClass = L"TST"; int g_iWidth = 200; int g_iHeight = 200; // Main entry-point int _tmain( int argc, _TCHAR* argv[] ) { GdiplusStartup( &amp;g_pGdiPlusToken, &amp;g_oGdiPlusStartupInput, NULL ); RegWndClass(); CreateWindows(); Draw(); MsgLoop(); ::UnregisterClass( g_pWndClass, NULL ); ::Sleep( 500 ); GdiplusShutdown( g_pGdiPlusToken ); return 0; } // _tmain void CreateWindows() { #ifdef USE_LAYERED_WINDOW // The key trick is to create a window with style WS_EX_LAYERED, but WITHOUT any subsequent calls to SetLayeredWindowAttributes() // This gives us a magic window that must be updated with UpdateLayeredWindow() ( and it does NOT recieve any WM_PAINT messages ) // as brilliantly described in: http://alexkr.com/source-code/50/layered-windows-and-updatelayeredwindow/ g_hWndGdiPlus = ::CreateWindowEx( WS_EX_LAYERED, g_pWndClass, L"", WS_POPUP | WS_VISIBLE, 1000, 200, g_iWidth, g_iHeight, NULL, NULL, NULL, NULL ); #else g_hWndGdiPlus = ::CreateWindowEx( 0, g_pWndClass, L"", WS_OVERLAPPEDWINDOW | WS_POPUP | WS_VISIBLE, 1000, 200, g_iWidth, g_iHeight, NULL, NULL, NULL, NULL ); #endif //g_hWndGdi = ::CreateWindowEx( WS_EX_LAYERED, g_pWndClass, L"", WS_POPUP | WS_VISIBLE, 720, 500, 200, 200, NULL, NULL, NULL, NULL ); } // CreateWindows void Draw() { // Init GDI+ surface HDC hOff = ::CreateCompatibleDC( NULL ); Bitmap oDaBigOne( g_iWidth, g_iHeight, PixelFormat32bppARGB ); HBITMAP hBMit = NULL; Color oCol( 0, 0, 0, 0 ); oDaBigOne.GetHBITMAP( oCol, &amp;hBMit ); HGDIOBJ hSave = ::SelectObject( hOff, hBMit ); #ifdef USE_LAYERED_WINDOW Graphics oGraph( hOff ); #else Graphics oGraph( g_hWndGdiPlus ); #endif oGraph.Clear( Color( 255, 55, 155, 255 ) ); // Draw text oGraph.SetTextRenderingHint( TextRenderingHintAntiAliasGridFit ); oGraph.SetTextContrast( 0xffffffff ); oGraph.SetCompositingMode( CompositingModeSourceOver ); oGraph.SetCompositingQuality( CompositingQualityHighQuality ); oGraph.SetPixelOffsetMode( PixelOffsetModeHighQuality ); const FontFamily oFamily( L"Tahoma", NULL ); #if 1 // Use bold Font oF600( &amp;oFamily, 6.00, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF800( &amp;oFamily, 8.00, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF848( &amp;oFamily, 8.48, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF849( &amp;oFamily, 8.49, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1200( &amp;oFamily, 12.00, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1500( &amp;oFamily, 15.00, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1648( &amp;oFamily, 16.48, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1649( &amp;oFamily, 16.49, FontStyle::FontStyleBold, Unit::UnitPixel ); #else // Use regular Font oF600( &amp;oFamily, 6.00, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF800( &amp;oFamily, 8.00, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF848( &amp;oFamily, 8.48, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF849( &amp;oFamily, 8.49, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1200( &amp;oFamily, 12.00, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1500( &amp;oFamily, 15.00, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1648( &amp;oFamily, 16.48, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1649( &amp;oFamily, 16.49, FontStyle::FontStyleRegular, Unit::UnitPixel ); #endif assert( oF600.GetLastStatus() == Ok ); // Make sure font is OK SolidBrush oBrush( g_oTextColor ); double dy = 1.0; oGraph.DrawString( L"Size 6.00", -1, &amp;oF600, PointF( 30.0, dy += 18.0 ), &amp;oBrush ); oGraph.DrawString( L"Size 8.00", -1, &amp;oF800, PointF( 30.0, dy += 18.0 ), &amp;oBrush ); oGraph.DrawString( L"Size 8.48", -1, &amp;oF848, PointF( 30.0, dy += 18.0 ), &amp;oBrush ); oGraph.DrawString( L"Size 8.49", -1, &amp;oF849, PointF( 30.0, dy += 18.0 ), &amp;oBrush ); oGraph.DrawString( L"Size 12.00", -1, &amp;oF1200, PointF( 30.0, dy += 18.0 ), &amp;oBrush ); oGraph.DrawString( L"Size 15.00", -1, &amp;oF1500, PointF( 30.0, dy += 18.0 ), &amp;oBrush ); oGraph.DrawString( L"Size 16.48", -1, &amp;oF1648, PointF( 30.0, dy += 18.0 ), &amp;oBrush ); oGraph.DrawString( L"Size 16.49", -1, &amp;oF1649, PointF( 30.0, dy += 18.0 ), &amp;oBrush ); #ifndef USE_LAYERED_WINDOW return; #endif // Do da layered window magic stuff BLENDFUNCTION oBF = { 0 }; oBF.BlendOp = AC_SRC_OVER; oBF.BlendFlags = 0; oBF.SourceConstantAlpha = 255; oBF.AlphaFormat = AC_SRC_ALPHA; SIZE oSize = { 0 }; oSize.cx = g_iWidth; oSize.cy = g_iHeight; POINT oPTZero = { 0 }; RECT oRect = { 0 }; ::GetWindowRect( g_hWndGdiPlus, &amp;oRect ); POINT oPTWnd = { 0 }; oPTWnd.x = oRect.left; oPTWnd.y = oRect.top; //HDC hDC = oGraph.GetHDC(); BOOL bOK = ::UpdateLayeredWindow( g_hWndGdiPlus, NULL, //HDC hdcDst, &amp;oPTWnd, // POINT &amp;oPtNull, &amp;oSize, // SIZE *psize, hOff, // HDC hdcSrc, &amp;oPTZero, // POINT *pptSrc, RGB(255,255,255), // COLORREF crKey, &amp;oBF, // BLENDFUNCTION *pblend, ULW_ALPHA // DWORD dwFlags ); } // Draw void MsgLoop() { ::SetTimer( g_hWndGdiPlus, 0, 19999, NULL ); // Self-destruct timer MSG msg = { 0 }; while ( ::GetMessage( &amp;msg, NULL, 0, 0 ) ) { ::TranslateMessage(&amp;msg); ::DispatchMessage(&amp;msg); } } // MsgLoop void RegWndClass() { WNDCLASSEX wcex = { 0 }; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 8; // 8 bytes, to allow for 64-bit architecture wcex.hInstance = NULL; // CHECK wcex.hIcon = NULL; wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)NULL_BRUSH; // CHECK wcex.lpszMenuName = NULL; wcex.lpszClassName = g_pWndClass; wcex.hIconSm = NULL; g_iWndClass = ::RegisterClassEx(&amp;wcex); } // RegWndClass LRESULT CALLBACK WndProc( HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ) { switch( uiMsg ) { case WM_TIMER: { std::wstring s; std::wcout &lt;&lt; L"Let´s quit" ; ::PostQuitMessage( 0 ); return 0; } case WM_PAINT: Draw(); break; default: { return DefWindowProc( hWnd, uiMsg, wParam, lParam ); } } return DefWindowProc( hWnd, uiMsg, wParam, lParam ); } // WndProc </code></pre> <p><strong>[EDIT] Problem solved! Code below according to <a href="https://stackoverflow.com/users/865874/rodrigo">Rodrogo´s</a> excellent suggestions. Kudos and an immense amount of thanks to him. I am really grateful.</strong></p> <p>All edits are marked with //#MOD</p> <pre><code>// Create as a console application project // + Unicode charset // + Precompiled headers off // + make sure to add linker input: gdiplus.lib #ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. #define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows. #endif // Standard stuff #include &lt;stdio.h&gt; #include &lt;tchar.h&gt; #include &lt;windows.h&gt; #include &lt;iostream&gt; #include &lt;cassert&gt; // GDI+ stuff #include &lt;Gdiplus.h&gt; using namespace Gdiplus; GdiplusStartupInput g_oGdiPlusStartupInput; ULONG_PTR g_pGdiPlusToken = NULL; // #*#*#*#*#*#*#*#*# LINES TO CHANGE ----------&gt;----------&gt;----------&gt; Color g_oTextColor( 255, 240, 0, 0 ); // Simply change Color to ( 254, 240, 0, 0 ) [to add slight transparency] and everything will work! #define USE_LAYERED_WINDOW // or just omment this line [to use a regular window], and everything will work! // Forward declarations void RegWndClass(); LRESULT CALLBACK WndProc( HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ); void CreateWindows(); void Draw(); void MsgLoop(); // Other Globals ATOM g_iWndClass = 0; HWND g_hWndGdiPlus = NULL; HWND g_hWndGdi = NULL; const wchar_t* g_pWndClass = L"TST"; int g_iWidth = 200; int g_iHeight = 200; // Main entry-point int _tmain( int argc, _TCHAR* argv[] ) { GdiplusStartup( &amp;g_pGdiPlusToken, &amp;g_oGdiPlusStartupInput, NULL ); RegWndClass(); CreateWindows(); Draw(); MsgLoop(); ::UnregisterClass( g_pWndClass, NULL ); ::Sleep( 500 ); GdiplusShutdown( g_pGdiPlusToken ); return 0; } // _tmain void CreateWindows() { #ifdef USE_LAYERED_WINDOW // The key trick is to create a window with style WS_EX_LAYERED, but WITHOUT any subsequent calls to SetLayeredWindowAttributes() // This gives us a magic window that must be updated with UpdateLayeredWindow() ( and it does NOT recieve any WM_PAINT messages ) // as brilliantly described in: http://alexkr.com/source-code/50/layered-windows-and-updatelayeredwindow/ g_hWndGdiPlus = ::CreateWindowEx( WS_EX_LAYERED, g_pWndClass, L"", WS_POPUP | WS_VISIBLE, 1000, 200, g_iWidth, g_iHeight, NULL, NULL, NULL, NULL ); #else g_hWndGdiPlus = ::CreateWindowEx( 0, g_pWndClass, L"", WS_OVERLAPPEDWINDOW | WS_POPUP | WS_VISIBLE, 1000, 200, g_iWidth, g_iHeight, NULL, NULL, NULL, NULL ); #endif //g_hWndGdi = ::CreateWindowEx( WS_EX_LAYERED, g_pWndClass, L"", WS_POPUP | WS_VISIBLE, 720, 500, 200, 200, NULL, NULL, NULL, NULL ); } // CreateWindows void Draw() { // Init GDI+ surface HDC hOff = ::CreateCompatibleDC( NULL ); Bitmap oDaBigOne( g_iWidth, g_iHeight, PixelFormat32bppARGB ); HBITMAP hBMit = NULL; Color oCol( 0, 0, 0, 0 ); // oDaBigOne.GetHBITMAP( oCol, &amp;hBMit ); //#MOD // HGDIOBJ hSave = ::SelectObject( hOff, hBMit ); //#MOD { // Limit oGraph scope //#MOD #ifdef USE_LAYERED_WINDOW //Graphics oGraph( hOff ); //#MOD Graphics oGraph( &amp;oDaBigOne ); //#MOD #else Graphics oGraph( g_hWndGdiPlus ); #endif oGraph.Clear( Color( 255, 55, 155, 255 ) ); // Draw text oGraph.SetTextRenderingHint( TextRenderingHintAntiAliasGridFit ); oGraph.SetCompositingMode( CompositingModeSourceOver ); oGraph.SetCompositingQuality( CompositingQualityHighQuality ); oGraph.SetPixelOffsetMode( PixelOffsetModeHighQuality ); const FontFamily oFamily( L"Tahoma", NULL ); #if 1 // Use bold Font oF600( &amp;oFamily, 6.00, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF848( &amp;oFamily, 8.48, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF849( &amp;oFamily, 8.49, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1200( &amp;oFamily, 12.00, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1500( &amp;oFamily, 15.00, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1648( &amp;oFamily, 16.48, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1649( &amp;oFamily, 16.49, FontStyle::FontStyleBold, Unit::UnitPixel ); #else // Use regular Font oF600( &amp;oFamily, 6.00, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF848( &amp;oFamily, 8.48, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF849( &amp;oFamily, 8.49, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1200( &amp;oFamily, 12.00, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1500( &amp;oFamily, 15.00, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1648( &amp;oFamily, 16.48, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1649( &amp;oFamily, 16.49, FontStyle::FontStyleRegular, Unit::UnitPixel ); #endif assert( oF600.GetLastStatus() == Ok ); // Make sure font is OK SolidBrush oBrush( g_oTextColor ); double dy = 10.0; oGraph.DrawString( L"Size 6.00", -1, &amp;oF600, PointF( 30.0, dy += 18.0 ), &amp;oBrush ); oGraph.DrawString( L"Size 8.48", -1, &amp;oF848, PointF( 30.0, dy += 18.0 ), &amp;oBrush ); oGraph.DrawString( L"Size 8.49", -1, &amp;oF849, PointF( 30.0, dy += 18.0 ), &amp;oBrush ); oGraph.DrawString( L"Size 12.00", -1, &amp;oF1200, PointF( 30.0, dy += 18.0 ), &amp;oBrush ); oGraph.DrawString( L"Size 15.00", -1, &amp;oF1500, PointF( 30.0, dy += 18.0 ), &amp;oBrush ); oGraph.DrawString( L"Size 16.48", -1, &amp;oF1648, PointF( 30.0, dy += 18.0 ), &amp;oBrush ); oGraph.DrawString( L"Size 16.49", -1, &amp;oF1649, PointF( 30.0, dy += 18.0 ), &amp;oBrush ); #ifndef USE_LAYERED_WINDOW return; #endif } // Limit oGraph scope //#MOD // Do da layered window magic stuff BLENDFUNCTION oBF = { 0 }; oBF.BlendOp = AC_SRC_OVER; oBF.BlendFlags = 0; oBF.SourceConstantAlpha = 255; oBF.AlphaFormat = AC_SRC_ALPHA; SIZE oSize = { 0 }; oSize.cx = g_iWidth; oSize.cy = g_iHeight; POINT oPTZero = { 0 }; RECT oRect = { 0 }; ::GetWindowRect( g_hWndGdiPlus, &amp;oRect ); POINT oPTWnd = { 0 }; oPTWnd.x = oRect.left; oPTWnd.y = oRect.top; oDaBigOne.GetHBITMAP( oCol, &amp;hBMit ); //#MOD HGDIOBJ hSave = ::SelectObject( hOff, hBMit ); //#MOD //HDC hDC = oGraph.GetHDC(); BOOL bOK = ::UpdateLayeredWindow( g_hWndGdiPlus, NULL, //HDC hdcDst, &amp;oPTWnd, // POINT &amp;oPtNull, &amp;oSize, // SIZE *psize, hOff, // HDC hdcSrc, &amp;oPTZero, // POINT *pptSrc, RGB(255,255,255), // COLORREF crKey, &amp;oBF, // BLENDFUNCTION *pblend, ULW_ALPHA // DWORD dwFlags ); } // Draw void MsgLoop() { ::SetTimer( g_hWndGdiPlus, 0, 19999, NULL ); // Self-destruct timer MSG msg = { 0 }; while ( ::GetMessage( &amp;msg, NULL, 0, 0 ) ) { ::TranslateMessage(&amp;msg); ::DispatchMessage(&amp;msg); } } // MsgLoop void RegWndClass() { WNDCLASSEX wcex = { 0 }; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 8; // 8 bytes, to allow for 64-bit architecture wcex.hInstance = NULL; // CHECK wcex.hIcon = NULL; wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)NULL_BRUSH; // CHECK wcex.lpszMenuName = NULL; wcex.lpszClassName = g_pWndClass; wcex.hIconSm = NULL; g_iWndClass = ::RegisterClassEx(&amp;wcex); } // RegWndClass LRESULT CALLBACK WndProc( HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ) { switch( uiMsg ) { case WM_TIMER: { std::wstring s; std::wcout &lt;&lt; L"Let´s quit" ; ::PostQuitMessage( 0 ); return 0; } case WM_PAINT: Draw(); break; default: { return DefWindowProc( hWnd, uiMsg, wParam, lParam ); } } return DefWindowProc( hWnd, uiMsg, wParam, lParam ); } // WndProc </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload