These are my friends, it will be long ...
I get a rather strange behavior when I try to make text in a layered window.
It is strange that for some font / font-style / font-size combinations, GDI + changes the rendering method. For Tahoma-Bold fonts, the size is from 8.49 to 16.49 (pixel units) inclusive of "failure". For other fonts and styles, I get a crash of different sizes.

For clarity, I have provided a complete executable example below. Two key parameters for the game: on line 23:
Color g_oTextColor( 255, 240, 0, 0 ); // Simply change Color to ( 254, 240, 0, 0 ) [to add slight transparency] and everything will work!
When using multi-level windows and full opacity, the fonts draw a transparent āholeā in the background. However, if I add a little transparency to the text color (alpha channel = 254), the fonts become opaque. Or, if I use regular (non-layered) windows, fonts render opacity. What's going on here?
But even without problems with layering / transparency, it is clear that something strange is happening here. Fonts of sizes 8.49 - 16.48 receive a rendered pixel, other fonts have a slight blurry quality, especially small ones. Thus, it seems that the system takes a different approach to rendering these medium sizes. Can someone shed some light on this, how can I display, for example, 8.0 pixel fonts without blurring above? I tried all sorts of settings for SetTextRenderingHint() and SetTextContrast() , but no one looked crisp for size 8 fonts. I tried Tahoma and Arial only ...
Side question 1: I wanted to use pure GDI + for off-screen drawing, but I couldnāt get it working just by creating Bitmap and Graphics objects. I still had to use the old GDI stuff to create DC and select HBitmap in it. How can I do all this in GDI +?
Side question 2 (Geeks only): I also tried to draw fonts in good GDI, but there I got even more bizarre effects: (1) In a multi-level window, the text became transparent, but in an additive way. (Thus, the red text would look great if the window behind was dark, but if the window behind it was white, the text completely disappeared!) Also, if I filled my window with a translucent square, then it would behave as expected. (The red square will turn dark red if the window behind it turns black and the square turns light red above the white window). And I can observe both of these behaviors simultaneously in one multi-level window. And (2) as a highly undesirable bonus, did the drawn text lose its hit test and become non-contact? Any explanations there?
And if you read this far, thanks for your patience and thanks for any answers!
// 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 <stdio.h> #include <tchar.h> #include <windows.h> #include <iostream> #include <cassert> #include <Gdiplus.h> using namespace Gdiplus; GdiplusStartupInput g_oGdiPlusStartupInput; ULONG_PTR g_pGdiPlusToken = NULL; // #*#*#*#*#*#*#*#*# LINES TO CHANGE ---------->---------->----------> 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( &g_pGdiPlusToken, &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, &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( &oFamily, 6.00, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF800( &oFamily, 8.00, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF848( &oFamily, 8.48, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF849( &oFamily, 8.49, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1200( &oFamily, 12.00, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1500( &oFamily, 15.00, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1648( &oFamily, 16.48, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1649( &oFamily, 16.49, FontStyle::FontStyleBold, Unit::UnitPixel ); #else // Use regular Font oF600( &oFamily, 6.00, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF800( &oFamily, 8.00, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF848( &oFamily, 8.48, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF849( &oFamily, 8.49, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1200( &oFamily, 12.00, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1500( &oFamily, 15.00, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1648( &oFamily, 16.48, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1649( &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, &oF600, PointF( 30.0, dy += 18.0 ), &oBrush ); oGraph.DrawString( L"Size 8.00", -1, &oF800, PointF( 30.0, dy += 18.0 ), &oBrush ); oGraph.DrawString( L"Size 8.48", -1, &oF848, PointF( 30.0, dy += 18.0 ), &oBrush ); oGraph.DrawString( L"Size 8.49", -1, &oF849, PointF( 30.0, dy += 18.0 ), &oBrush ); oGraph.DrawString( L"Size 12.00", -1, &oF1200, PointF( 30.0, dy += 18.0 ), &oBrush ); oGraph.DrawString( L"Size 15.00", -1, &oF1500, PointF( 30.0, dy += 18.0 ), &oBrush ); oGraph.DrawString( L"Size 16.48", -1, &oF1648, PointF( 30.0, dy += 18.0 ), &oBrush ); oGraph.DrawString( L"Size 16.49", -1, &oF1649, PointF( 30.0, dy += 18.0 ), &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, &oRect ); POINT oPTWnd = { 0 }; oPTWnd.x = oRect.left; oPTWnd.y = oRect.top; //HDC hDC = oGraph.GetHDC(); BOOL bOK = ::UpdateLayeredWindow( g_hWndGdiPlus, NULL, //HDC hdcDst, &oPTWnd, // POINT &oPtNull, &oSize, // SIZE *psize, hOff, // HDC hdcSrc, &oPTZero, // POINT *pptSrc, RGB(255,255,255), // COLORREF crKey, &oBF, // BLENDFUNCTION *pblend, ULW_ALPHA // DWORD dwFlags ); } // Draw void MsgLoop() { ::SetTimer( g_hWndGdiPlus, 0, 19999, NULL ); // Self-destruct timer MSG msg = { 0 }; while ( ::GetMessage( &msg, NULL, 0, 0 ) ) { ::TranslateMessage(&msg); ::DispatchMessage(&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(&wcex); } // RegWndClass LRESULT CALLBACK WndProc( HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ) { switch( uiMsg ) { case WM_TIMER: { std::wstring s; std::wcout << 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
[EDIT] Problem Solved! The code below is in accordance with Rodrogy excellent offers. Kudos and many thanks to him. I am very grateful.
All changes are marked as // # MOD
// 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 <stdio.h> #include <tchar.h> #include <windows.h> #include <iostream> #include <cassert> // GDI+ stuff #include <Gdiplus.h> using namespace Gdiplus; GdiplusStartupInput g_oGdiPlusStartupInput; ULONG_PTR g_pGdiPlusToken = NULL; // #*#*#*#*#*#*#*#*# LINES TO CHANGE ---------->---------->----------> 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( &g_pGdiPlusToken, &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, &hBMit ); //#MOD // HGDIOBJ hSave = ::SelectObject( hOff, hBMit ); //#MOD { // Limit oGraph scope //#MOD #ifdef USE_LAYERED_WINDOW //Graphics oGraph( hOff ); //#MOD Graphics oGraph( &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( &oFamily, 6.00, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF848( &oFamily, 8.48, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF849( &oFamily, 8.49, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1200( &oFamily, 12.00, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1500( &oFamily, 15.00, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1648( &oFamily, 16.48, FontStyle::FontStyleBold, Unit::UnitPixel ); Font oF1649( &oFamily, 16.49, FontStyle::FontStyleBold, Unit::UnitPixel ); #else // Use regular Font oF600( &oFamily, 6.00, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF848( &oFamily, 8.48, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF849( &oFamily, 8.49, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1200( &oFamily, 12.00, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1500( &oFamily, 15.00, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1648( &oFamily, 16.48, FontStyle::FontStyleRegular, Unit::UnitPixel ); Font oF1649( &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, &oF600, PointF( 30.0, dy += 18.0 ), &oBrush ); oGraph.DrawString( L"Size 8.48", -1, &oF848, PointF( 30.0, dy += 18.0 ), &oBrush ); oGraph.DrawString( L"Size 8.49", -1, &oF849, PointF( 30.0, dy += 18.0 ), &oBrush ); oGraph.DrawString( L"Size 12.00", -1, &oF1200, PointF( 30.0, dy += 18.0 ), &oBrush ); oGraph.DrawString( L"Size 15.00", -1, &oF1500, PointF( 30.0, dy += 18.0 ), &oBrush ); oGraph.DrawString( L"Size 16.48", -1, &oF1648, PointF( 30.0, dy += 18.0 ), &oBrush ); oGraph.DrawString( L"Size 16.49", -1, &oF1649, PointF( 30.0, dy += 18.0 ), &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, &oRect ); POINT oPTWnd = { 0 }; oPTWnd.x = oRect.left; oPTWnd.y = oRect.top; oDaBigOne.GetHBITMAP( oCol, &hBMit ); //#MOD HGDIOBJ hSave = ::SelectObject( hOff, hBMit ); //#MOD //HDC hDC = oGraph.GetHDC(); BOOL bOK = ::UpdateLayeredWindow( g_hWndGdiPlus, NULL, //HDC hdcDst, &oPTWnd, // POINT &oPtNull, &oSize, // SIZE *psize, hOff, // HDC hdcSrc, &oPTZero, // POINT *pptSrc, RGB(255,255,255), // COLORREF crKey, &oBF, // BLENDFUNCTION *pblend, ULW_ALPHA // DWORD dwFlags ); } // Draw void MsgLoop() { ::SetTimer( g_hWndGdiPlus, 0, 19999, NULL ); // Self-destruct timer MSG msg = { 0 }; while ( ::GetMessage( &msg, NULL, 0, 0 ) ) { ::TranslateMessage(&msg); ::DispatchMessage(&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(&wcex); } // RegWndClass LRESULT CALLBACK WndProc( HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ) { switch( uiMsg ) { case WM_TIMER: { std::wstring s; std::wcout << 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