简体   繁体   English

如何使用 GDI 绘制具有透明度的文本?

[英]How to draw text with transparency using GDI?

My goal is to dynamically put some arbitrary text into an HICON image (at runtime.) I'm using the following code:我的目标是将一些任意文本动态放入 HICON 图像(在运行时)。我使用以下代码:

//Error checks are omitted for brevity

//First create font
LOGFONT lf = {0};
lf.lfHeight = -58;
lf.lfWeight = FW_NORMAL;
lf.lfOutPrecision = OUT_TT_PRECIS;  //Use TrueType fonts for anti-alliasing
lf.lfQuality = CLEARTYPE_QUALITY;
lstrcpy(lf.lfFaceName, L"Segoe UI");

HFONT hFont = ::CreateFontIndirect(&lf);


//HICON hIcon = original icon to use as a source
//I'm using a large 256x256 pixel icon
hIcon = (HICON)::LoadImage(theApp.m_hInstance, MAKEINTRESOURCE(IDI_ICON_GREEN_DIAMOND), IMAGE_ICON, 256, 256, LR_DEFAULTCOLOR);

ICONINFO ii = {0};
::GetIconInfo(hIcon, &ii);

BITMAP bm = {0};
::GetObject(ii.hbmColor, sizeof(bm), &bm);
SIZE szBmp = {bm.bmWidth, bm.bmHeight};

HDC hDc = ::GetDC(hWnd);
HDC hMemDC = ::CreateCompatibleDC(hDc);

HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
HGDIOBJ hOldFont = ::SelectObject(hMemDC, hFont);

::SetBkMode(hMemDC, TRANSPARENT);
::SetTextColor(hMemDC, RGB(255, 0, 0));     //Red text

//Draw text
//NOTE that DrawText API behaves in a similar way
::TextOut(hMemDC, 0, 0, L"Hello", 5);

::SelectObject(hMemDC, hOldFont);
::SelectObject(hMemDC, hOldBmp);


//We need a simple mask bitmap for the icon
HBITMAP hBmpMsk = ::CreateBitmap(szBmp.cx, szBmp.cy, 1, 1, NULL);

ICONINFO ii2 = {0};
ii2.fIcon = TRUE;
ii2.hbmColor = ii.hbmColor;
ii2.hbmMask = hBmpMsk;

//Create updated icon
HICON hIcon2 = ::CreateIconIndirect(&ii2);


//Cleanup
::DeleteObject(hBmpMsk);
::DeleteDC(hMemDC);
::ReleaseDC(hWnd, hDc);
::DeleteObject(ii.hbmColor);
::DeleteObject(ii.hbmMask);

::DeleteObject(hFont);

and then I can display the icon in my window from OnPaint() handler (so that I can see how it turns out) as such:然后我可以通过OnPaint()处理程序在我的窗口中显示图标(以便我可以看到结果),如下所示:

::DrawIconEx(dc.GetSafeHdc(), 0, 0,
    hIcon2, 
    256, 256, NULL, 
    ::GetSysColorBrush(COLOR_BTNFACE),
    DI_NORMAL);

So here's what I get:所以这就是我得到的:

在此处输入图片说明

To see what's going on pixel-wise in my hIcon2 I called GetDIBits on its ii.hbmColor from the code above.要查看我的hIcon2像素方面的情况,我从上面的代码中调用了ii.hbmColor上的GetDIBits The resulting pixel array where my word "Hello" was supposed to be shown looked like this:应该显示我的单词“Hello”的结果像素阵列如下所示:

在此处输入图片说明

The pixels are encoded as BGRA in that memory dump, so the 4th byte in each DWORD stands for transparency: 0=transparent, FF=opaque.像素在该内存转储中被编码为BGRA ,因此每个 DWORD 中的第 4 个字节代表透明度:0=透明,FF=不透明。 But in this case TextOut doesn't fill out transparency, or leaves it as 0, which is interpreted as "fully transparent."但在这种情况下, TextOut不会填充透明度,或者将其TextOut为 0,这被解释为“完全透明”。 Instead it seems to pre-multiply it into the RGB colors themselves.相反,它似乎预先将其乘以RGB颜色本身。

Note that if I keep looking further down the same bitmap, where the green diamond begins, the image pixels seem to have transparency bytes set correctly:请注意,如果我继续向下看同一个位图,绿色菱形开始的地方,图像像素似乎正确设置了透明度字节:

在此处输入图片说明

Any idea how to draw text so that the API could set those transparency bytes?知道如何绘制文本以便 API 可以设置这些透明度字节吗?

EDIT: As was suggested below I tried the following GDI+ method:编辑:正如下面所建议的,我尝试了以下 GDI+ 方法:

HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);

Graphics grpx(hMemDC);

RectF rcfTxt(0.0f, 0.0f, (REAL)szBmp.cx, (REAL)szBmp.cy);
Font gdiFont(L"Segoe UI", 58.0f, FontStyleRegular, UnitPixel);

SolidBrush gdiBrush(Color(255, 0, 0));

StringFormat gdiSF;
gdiSF.SetAlignment(StringAlignmentNear);
gdiSF.SetFormatFlags(StringFormatFlagsNoWrap);
gdiSF.SetHotkeyPrefix(HotkeyPrefixNone);

//The reason I was using GDI was because I was setting
//spacing between letters using SetTextCharacterExtra()
//Unfortunately with GDI+ this does not work!
HDC hTmpDC = grpx.GetHDC();
::SetTextCharacterExtra(hTmpDC, -4);  //This doesn't do anything!
grpx.ReleaseHDC(hTmpDC);

grpx.DrawString(L"Hello", 5, &gdiFont, rcfTxt, &gdiSF, &gdiBrush);

::SelectObject(hMemDC, hOldBmp);

and besides not being able to set character spacing (which I could with GDI using SetTextCharacterExtra ) here's what I got (slightly enlarged for visibility):除了无法设置字符间距(我可以使用SetTextCharacterExtra使用 GDI SetTextCharacterExtra )这是我得到的(稍微放大以提高可见性):

在此处输入图片说明

So clearly still an issue with transparency.很明显,透明度仍然是一个问题。

Taken from an old post by Microsoft MVP Mike D Sutton here .从旧的文章由微软MVP迈克·d萨顿采取这里

When you create a DC it initially has default 'stock' objects selected into it, including the stock 1*1*1 Bitmap.当您创建 DC 时,它最初会选择默认的“库存”对象,包括库存 1*1*1 位图。 Since there is a Bitmap already selected into the DC when you call DrawText() it will still try and render to it even though pretty much everything (apart from one pixel) will be clipped.由于在您调用 DrawText() 时已经选择了一个位图到 DC 中,它仍然会尝试渲染它,即使几乎所有内容(除了一个像素)都将被剪裁。

What you need to do is to create a Bitmap, either DDB or DIBSection, and select that into your DC before drawing to it.您需要做的是创建一个位图,无论是 DDB 还是 DIBSection,然后在绘制之前将其选择到您的 DC 中。

First though you need to find the size of your Bitmap since you want it large enough to display your text in, so for that you use the DrawText() call again on the initial DC but include the DT_CALCRECT flag.首先虽然您需要找到位图的大小,因为您希望它足够大以显示您的文本,因此您在初始 DC 上再次使用 DrawText() 调用,但包括 DT_CALCRECT 标志。 What this does is rather than drawing anything it simply measures how large the text is and dumps that into the RECT you pass the call.它所做的不是绘制任何东西,而是简单地测量文本的大小并将其转储到您传递调用的 RECT 中。 From here you can go ahead and create your DIBSection using those dimensions and select it into your DC.从这里您可以继续使用这些维度创建您的 DIBSection 并将其选择到您的 DC 中。 Finally perform your existing DrawText ()call (you may also want to use SetBkMode/Color()) which will render the text to the DIBSection from which you can get at the data.最后执行您现有的 DrawText () 调用(您可能还想使用 SetBkMode/Color()),它将将文本呈现到您可以从中获取数据的 DIBSection。

This seems to work pretty well here:这似乎在这里工作得很好:

 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, 0x00FFFFFF); SetBkColor(hTextDC, 0x00000000); SetBkMode(hTextDC, OPAQUE); // 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; }

If you need an example of how to call it then try something like this (inDC is the DC to render to):如果您需要一个如何调用它的示例,请尝试这样的操作(inDC 是要渲染到的 DC):

 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) { BITMAP BMInf; // Get Bitmap image size 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); } } }

All credit to answer and code go to original posters on that forum, I've simply reposted it so that this answer will be valid if the links die.答案和代码的所有功劳都归于该论坛上的原始海报,我只是重新发布了它,以便在链接失效时此答案仍然有效。

This reply is coming almost 3 years after the question was posted, but people still consult these things long into the future.这个回复是在问题发布将近 3 年后才出现的,但人们仍然会在很长一段时间内咨询这些事情。 So I'll explain what's happening.所以我会解释发生了什么。

DrawText (and other GDI text functions) will work on a transparent bitmap. DrawText(和其他 GDI 文本函数)将在透明位图上工作。 The text is not coming out black even though it displays that way.即使以这种方式显示,文本也不会变黑。 The alpha channel is set to 0 on all pixels the text draws to, overriding whatever alpha you had set previously.文本绘制到的所有像素上的 alpha 通道设置为 0,覆盖您之前设置的任何 alpha。 If you set an alpha value in SetTextColor the text will render all black.如果您在 SetTextColor 中设置了 alpha 值,则文本将呈现为全黑。 If you're feeling ambitious you can run through pixel by pixel and target anything not your fill color (which requires a single fill color) but the problem then becomes one of the nature of ClearType being overridden and all alphas are set to whatever you set them to.如果您感到雄心勃勃,您可以逐个像素地运行并定位任何不是您的填充颜色(需要单一填充颜色)的内容,但问题随后成为 ClearType 被覆盖的性质之一,并且所有 alpha 都设置为您设置的任何内容他们到。 The text ends up looking very funky.文本最终看起来非常时髦。 If you use a constant alpha for your background fill you can simply do a blanket run across the entire bitmap's bits after the text is drawn and reset all the alpha values.如果您对背景填充使用常量 alpha,则可以在绘制文本并重置所有 alpha 值后简单地对整个位图的位进行全面运行。 Since you have to read a byte to determine if it's background or not, you might as well just set every pixel's alpha to whatever the standard alpha is for that image and bypass the slow compares.由于您必须读取一个字节来确定它是否是背景,因此您不妨将每个像素的 alpha 设置为该图像的标准 alpha 值并绕过慢速比较。 This works reasonably well and I've found it to be very acceptable.这相当有效,我发现它是非常可接受的。 In this day and age, MS should have taken care of this long ago but it's not to be.在这个时代,MS 早就应该解决这个问题,但事实并非如此。

https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-antialiasing-with-text-use https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-antialiasing-with-text-use

    Gdiplus::Bitmap bmp( your_Width, your_Height, PixelFormat64bppARGB);
//PixelFormat64bppARGB ARGB needed

FontFamily      fontFamily(L"Arial");
    Font            font(&fontFamily, 29, FontStyleRegular, UnitPoint);
    Gdiplus::RectF  rectF(00.0f, 10.0f, your_Width, your_Height);
    StringFormat    stringFormat;
    SolidBrush      solidBrush(Color(63, 0, 0, 255));
    stringFormat.SetAlignment(StringAlignmentCenter);
//solidBrush Color(63, 0, 0, 255) ARGB neede

graphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
    graphics.DrawString("your_text", -1, &font, rectF, &stringFormat, &solidBrush);
//TextRenderingHintAntiAlias this needed 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM