This my friends, is a going to be long one...
I am getting some quite bizarre behaviour when I try to render text in a layered window.
The strange thing is that for some combinations of font/font-style/font-size, GDI+ changes the rendering method. For Tahoma-Bold fonts sizes between 8.49 and 16.49 (Pixel-Units) inclusive "fail". For other fonts and styles I get "fails" at different sizes.
For clarity I have provided a complete executable example further down. The two key parameters to play with are 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!
#define USE_LAYERED_WINDOW // or just comment this line out [to use a regular window], and everything will work!
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. What is going on here??
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. Can somebody shed some light on this, how can I possibly render for example fonts size 8.0 pixels without the blurriness above? I have tried all sorts of settings for SetTextRenderingHint()
and SetTextContrast()
but none looked crisp for fonts of size 8. I have tried Tahoma & Arial only...
Side question 1:
I wanted to use pure GDI+ for the off-screen drawing but I could not get it work by simply creating Bitmap
& Graphics
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+?
Side question 2 (Geeks only): I also tried to draw the fonts in good-old GDI but there I got some even more bizarre effects: (1) In a layered window the text became transparent but in an additive 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 (2) as a highly undesired bonus the drawn text lost it's hit-test and became un-clickable? Any explanations out there?
And if you have read this far, thanks for enduring, 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! Code below according to Rodrogo´s excellent suggestions. Kudos and an immense amount of thanks to him. I am really grateful.
All edits are marked with //#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