一次,同事向我请教如何创建文字图片的问题,我想因该是不难的,但是效果总是出不来,我俩为此用了一下午的时间来解决这个问题,后来在一篇外文上找到了解决方案,用它的一试,效果是出来了。问题是有几处关键的代码,我们弄不明白,资料又无从查起,这让我们很老火;都知道做技术的,对所在技术不明其理,那是不行的。 当然我们运气好,最终弄明白了那些代码的含义。归根结底,就是GDI API函数忽略了Alpha值的问题。借此总结下心得,以备自用。
首先要搞明白,什么场景下会用到文字图片?在回答这个问题前,我们需要有个共识,所有的GDI函数都不能处理Alpha channel,除了AlphaBlend函数(但是这个函数只是从一个DC拷贝目的DC,并不能直接控制Alpha,需要通过CreateDIBSection函数创建一个带Alpha channel的HBITMAP,然后选到内存DC里去进行操作。),如果在内存DC里调用DrawText或TextOut绘制文本,那么他的alpha值就全是零。然后调用AlphaBlend函数,把内存DC复制到目的DC,就会导致文字区域显示透明,看不见。所以为了解决这个问题,我们就要用到文字图片,或者用GDI+绘制,GDI+没有Alpha的问题。
HBITMAP CreateAlphaTextBitmap(LPCSTR inText, HFONT inFont, COLORREF inColour)
{
int TextLength = (int)strlen(inText);
if (TextLength <= 0) return NULL;
// Create DC and select font into it
HDC hTextDC = CreateCompatibleDC(NULL);
HFONT hOldFont = (HFONT)SelectObject(hTextDC, inFont);
HBITMAP hMyDIB = NULL;
// Get text area
RECT TextArea = {0, 0, 0, 0};
DrawText(hTextDC, inText, TextLength, &TextArea, DT_CALCRECT);
if ((TextArea.right > TextArea.left) && (TextArea.bottom > TextArea.top))
{
BITMAPINFOHEADER BMIH;
memset(&BMIH, 0x0, sizeof(BITMAPINFOHEADER));
void *pvBits = NULL;
// Specify DIB setup
BMIH.biSize = sizeof(BMIH);
BMIH.biWidth = TextArea.right - TextArea.left;
BMIH.biHeight = TextArea.bottom - TextArea.top;
BMIH.biPlanes = 1;
BMIH.biBitCount = 32;
BMIH.biCompression = BI_RGB;
// Create and select DIB into DC
hMyDIB = CreateDIBSection(hTextDC, (LPBITMAPINFO)&BMIH, 0, (LPVOID*)&pvBits, NULL, 0);
HBITMAP hOldBMP = (HBITMAP)SelectObject(hTextDC, hMyDIB);
if (hOldBMP != NULL)
{
// Set up DC properties
SetTextColor(hTextDC, 0x00FF0000);
SetBkMode(hTextDC, TRANSPARENT);
// Draw text to buffer
DrawText(hTextDC, inText, TextLength, &TextArea, DT_NOCLIP);
BYTE* DataPtr = (BYTE*)pvBits;
BYTE FillR = GetRValue(inColour);
BYTE FillG = GetGValue(inColour);
BYTE FillB = GetBValue(inColour);
BYTE ThisA;
for (int LoopY = 0; LoopY < BMIH.biHeight; LoopY++) {
for (int LoopX = 0; LoopX < BMIH.biWidth; LoopX++) {
ThisA = *DataPtr; // Move alpha and pre-multiply with RGB
*DataPtr++ = (FillB * ThisA) >> 8;
*DataPtr++ = (FillG * ThisA) >> 8;
*DataPtr++ = (FillR * ThisA) >> 8;
*DataPtr++ = ThisA; // Set Alpha
}
}
// De-select bitmap
SelectObject(hTextDC, hOldBMP);
}
}
// De-select font and destroy temp DC
SelectObject(hTextDC, hOldFont);
DeleteDC(hTextDC);
// Return DIBSection
return hMyDIB;
}
void TestAlphaText(HDC inDC, int inX, int inY)
{
const char *DemoText = "Hello World!\0";
RECT TextArea = {0, 0, 0, 0};
HFONT TempFont = CreateFont(50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Arial\0");
HBITMAP MyBMP = CreateAlphaTextBitmap(DemoText, TempFont, 0xFF);
DeleteObject(TempFont);
if (MyBMP)
{
// Create temporary DC and select new Bitmap into it
HDC hTempDC = CreateCompatibleDC(inDC);
HBITMAP hOldBMP = (HBITMAP)SelectObject(hTempDC, MyBMP);
if (hOldBMP)
{
// Get Bitmap image size
BITMAP BMInf;
GetObject(MyBMP, sizeof(BITMAP), &BMInf);
// Fill blend function and blend new text to window
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 0x80;
bf.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(inDC, inX, inY, BMInf.bmWidth, BMInf.bmHeight, hTempDC, 0, 0, BMInf.bmWidth, BMInf.bmHeight, bf);
// Clean up
SelectObject(hTempDC, hOldBMP);
DeleteObject(MyBMP);
DeleteDC(hTempDC);
}
}
}
代码取至http://stackoverflow.com/questions/5309914/updatelayeredwindow-and-drawtext
我们先来分析下CreateAlphaTextBitmap函数,这个函数功能是创建带alpha channel的文本,返回HBTIMAP。主流程是:
1、首先创建内存DC、字体选到内存DC里、获取文本的区域;
2、然后调用CreateDIBSection创建带有alpha通道的HBITMAP、把HBITMAP选进内存DC;然后调用SetTextColor设置文本的颜色,这里需要注意的是,这个颜色并不是最终
绘制出来的颜色,最终的颜色是由inColour值决定。这里0x00FF0000值的顺序是ARGB,但是在内存的布局顺序是BGRA,这个认识很重要。接着因为不需要文字背景,所 以调用SetBkMode(hTextDC, TRANSPARENT);然后就是绘制文本
3、最后改变每个像素的Alpha值、颜色值。先取出第一个字节的值保存下来,前面说过,在内存里的布局顺序BGRA,第一个字节是Blue,在像素扫描过程中,大部分时候Blue值都是0,只有经过文字区域时,Blue值是由之前设置的文本颜色0x00FF0000决定的,所以文本区域Blue是FF; 知道了这个理解起来就相当的容易了,接下来就是分别取出颜色值填充。
然后就是TestAlphaText函数,这个函数很简单,就是调用CreateAlphaTextBitmap函数获取HBITMAP,然后把HBITMAP选进内存DC,最后调用AlphaBlend函数把文字图片画到指定的DC上去。
效果图: