【问题标题】:How to draw text with transparency using GDI?如何使用 GDI 绘制具有透明度的文本?
【发布时间】:2017-07-02 10:59:10
【问题描述】:

我的目标是动态地将一些任意文本放入 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);

然后我可以在我的窗口中显示来自OnPaint() 处理程序的图标(这样我就可以看到结果如何):

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

这就是我得到的:

要查看我的hIcon2 中像素方面的情况,我从上面的代码中调用了GetDIBitsii.hbmColor。应该显示我的单词“Hello”的结果像素数组如下所示:

像素在该内存转储中被编码为BGRA,因此每个 DWORD 中的第 4 个字节代表透明度:0=透明,FF=不透明。但是在这种情况下TextOut 没有填写透明度,或者将其保留为 0,这被解释为“完全透明”。相反,它似乎将其预乘到 RGB 颜色本身。

请注意,如果我继续向下看同一个位图,即绿色菱形开始的位置,图像像素似乎具有正确设置的透明度字节:

知道如何绘制文本以便 API 可以设置这些透明度字节吗?

编辑:如下所示,我尝试了以下 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);

除了无法设置字符间距(我可以使用 GDI 使用 SetTextCharacterExtra)之外,这是我得到的(为了可见性而略微放大):

很明显,透明度仍然是个问题。

【问题讨论】:

  • GDI 不理解 alpha;请考虑使用 gdiplus。
  • @JonathanPotter:谢谢。是的,看起来 GDI+ 是支持 Alpha 通道的唯一方法。虽然,我选择普通 GDI 的原因是因为它提供了 SetTextCharacterExtra 函数来更改字符间距。你知道我是否还能在 GDI+ 中使用它吗?

标签: c++ windows winapi text gdi


【解决方案1】:

取自 Microsoft MVP Mike D Sutton here 的一篇旧帖子。

当您创建 DC 时,它最初会选择默认的“库存”对象 放入其中,包括库存的 1*1*1 位图。既然有位图 当您调用 DrawText() 时已经选择到 DC 它仍然会 即使几乎所有东西(除了 一个像素)将被剪裁。

你需要做的是创建一个位图, DDB 或 DIBSection,并在绘制之前将其选择到您的 DC 给它。

首先虽然你需要找到你的位图的大小,因为你 希望它足够大以显示您的文本,因此您可以使用 在初始 DC 上再次调用 DrawText(),但包括 DT_CALCRECT 旗帜。它所做的不是简单地绘制任何东西 测量文本的大小并将其转储到您传递的 RECT 中 通话。从这里您可以继续使用创建您的 DIBSection 这些尺寸并将其选择到您的 DC 中。最后执行你的 现有的 DrawText () 调用(您可能还想使用 SetBkMode/Color()) 这会将文本呈现到您可以从中获取的 DIBSection 数据。

这似乎在这里工作得很好:

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;
}

如果您需要一个如何调用它的示例,请尝试类似这样的操作 (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);
        }
    }
}

答案和代码的所有功劳都归于该论坛上的原始发帖者,我只是重新发布了它,以便在链接失效时此答案将有效。

【讨论】:

  • 它并没有真正回答这个问题。似乎他正在做的事情(在谷歌帖子中)是用纯色背景预乘文本。我的背景是透明的。
  • @c00000fd,我认为确实如此。您需要将文本渲染到具有预乘 alpha 的不同 HBitmap 中。然后 AlphaBlend (msdn.microsoft.com/en-us/library/windows/desktop/…) 将其添加到您的最终位图上。
【解决方案2】:

这个回复是在问题发布近 3 年后才出现的,但人们仍然会在很长一段时间内咨询这些东西。所以我会解释发生了什么。

DrawText(和其他 GDI 文本函数)将在透明位图上工作。即使以这种方式显示,文本也不会变黑。文本绘制到的所有像素上的 alpha 通道都设置为 0,覆盖您之前设置的任何 alpha。如果在 SetTextColor 中设置 alpha 值,则文本将呈现全黑。如果您感到雄心勃勃,您可以逐个像素地运行并定位任何不是您的填充颜色(需要单一填充颜色)但问题随后成为 ClearType 被覆盖的性质之一,并且所有 alpha 都设置为您设置的任何内容他们到。文本最终看起来非常时髦。如果您使用恒定的 alpha 作为背景填充,您可以在绘制文本后简单地在整个位图的位上进行覆盖并重置所有 alpha 值。由于您必须读取一个字节来确定它是否是背景,因此您最好将每个像素的 alpha 设置为该图像的标准 alpha 并绕过慢速比较。这工作得相当好,我发现它是非常可以接受的。在这个时代,MS 早就应该解决这个问题,但事实并非如此。

【讨论】:

    【解决方案3】:

    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 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-23
      • 2011-10-22
      • 2010-11-04
      • 2012-09-10
      • 2019-03-16
      • 1970-01-01
      • 2018-04-02
      • 1970-01-01
      相关资源
      最近更新 更多