This my friends, is a going to be long one...
I am getting some quite bizarre behaviour when I try to render text with GDI+ 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.
output from code below
http://i.imgur.com/lFvX4.png[
^]
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 );
#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:
(A) 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
(B) 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!
#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
#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;
Color g_oTextColor( 255, 240, 0, 0 );
#define USE_LAYERED_WINDOW // or just comment this line out [to use a regular window], and everything will work!
void RegWndClass();
LRESULT CALLBACK WndProc( HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam );
void CreateWindows();
void Draw();
void MsgLoop();
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;
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;
}
void CreateWindows()
{
#ifdef USE_LAYERED_WINDOW
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
}
void Draw()
{
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 ) );
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 );
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
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;
BOOL bOK = ::UpdateLayeredWindow( g_hWndGdiPlus,
NULL,
&oPTWnd,
&oSize,
hOff,
&oPTZero,
RGB(255,255,255),
&oBF,
ULW_ALPHA
);
}
void MsgLoop()
{
::SetTimer( g_hWndGdiPlus, 0, 19999, NULL );
MSG msg = { 0 };
while ( ::GetMessage( &msg, NULL, 0, 0 ) )
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
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;
wcex.hInstance = NULL;
wcex.hIcon = NULL;
wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)NULL_BRUSH;
wcex.lpszMenuName = NULL;
wcex.lpszClassName = g_pWndClass;
wcex.hIconSm = NULL;
g_iWndClass = ::RegisterClassEx(&wcex);
}
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 );
}