使用 Gdiplus::Graphics::DrawText 在黑色背景上绘制白色文本时出现问题

发布于 2024-10-28 12:09:12 字数 8597 浏览 1 评论 0原文

我正在尝试找到一种使用 Gdiplus::Graphics.DrawString 方法来创建在黑色背景上具有白色文本的位图的方法。

(为什么?因为我需要能够旋转文本,并且 Gdiplus::Graphics 对象具有可用的 RotateTransform。)

我编写了一个示例控制台应用程序,它演示了我在尝试在黑色上绘制白色文本时所看到的内容长方形。 (代码粘贴在下面。)

有人知道我做错了什么吗?预先感谢您的任何帮助。

////////////////////////

// DrawTextTest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#pragma comment (lib,"Gdiplus.lib")
#include <Windows.h>
#include <GdiPlusEnums.h>
#include <GdiPlusTypes.h>
#include <GdiPlus.h>
#include <iostream>
#include <string>

using namespace Gdiplus;
using namespace std;

bool RunTheTest(string);
bool SaveBitmapToFile(HDC, HBITMAP, BITMAPINFO&, string);
bool WriteBitmapFile(BYTE*, BITMAPINFOHEADER&, LPCTSTR);
void ShowError();

#define BUFFER_SIZE 1024

int _tmain(int argc, _TCHAR* argv[])
{
    bool bRet(false);
    char szBuff[BUFFER_SIZE];
    size_t retVal;
    string sFileName, sUserInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartupInput gdiplusStartupInput;

    if (argc > 1)
    {
        for (int nArg = 1; nArg < argc; nArg++)
        {
            wcstombs_s(&retVal, szBuff, (size_t)BUFFER_SIZE,  argv[nArg], (size_t)BUFFER_SIZE );
            if (nArg > 1)
                sFileName += " ";
            sFileName += szBuff;
        }
    }
    else
    {
        cout << "Input the full path filename (do not enclose in quotes)" << endl;
        cin >> sUserInput;
        sFileName += sUserInput;
    }
    Status s = GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    bRet = RunTheTest(sFileName);
    GdiplusShutdown(gdiplusToken);
    if (bRet)
        return 0;
    return 1;
}

bool RunTheTest(string sfileName)
{
    BITMAPINFO bitmapInfo;
    bool bRet(false);
    Brush* pBlackBrush = new SolidBrush(Gdiplus::Color::Black);
    Brush* pWhiteBrush = new SolidBrush(Gdiplus::Color::White);
    double inchesPerMeter(39.3700787);
    float fontSize(30);
    Font* pFont(NULL);
    FontFamily* pFontFamily(NULL);
    FontStyle fs(FontStyleRegular);
    HBITMAP hMemoryBitmap(NULL);
    HDC hMemoryDC(NULL);
    HGDIOBJ hDefaultBitmap(NULL);
    int pelsPerMeter(0), resolution(240);
    int oldBkMode(0);
    LPTSTR pFileName(NULL);
    PointF origin(0, 0);
    SIZE sizeRect;
    Status s;

    // Initialize a memory device context compatible with the screen
    hMemoryDC = CreateCompatibleDC(NULL);

    // Prepare some values for creating a memory bitmap
    pelsPerMeter = (int)(resolution * inchesPerMeter);
    sizeRect.cx = sizeRect.cy = 400;

    // Create the memory bitmap
    bitmapInfo.bmiHeader.biBitCount = 24;
    bitmapInfo.bmiHeader.biCompression = BI_RGB;
    bitmapInfo.bmiHeader.biPlanes = 1;
    bitmapInfo.bmiHeader.biXPelsPerMeter = pelsPerMeter;
    bitmapInfo.bmiHeader.biYPelsPerMeter = pelsPerMeter;
    bitmapInfo.bmiHeader.biSize = sizeof(bitmapInfo.bmiHeader);
    bitmapInfo.bmiHeader.biWidth = sizeRect.cx;
    bitmapInfo.bmiHeader.biHeight = sizeRect.cy;
    hMemoryBitmap = CreateCompatibleBitmap(hMemoryDC, sizeRect.cx, sizeRect.cy);
    hDefaultBitmap = SelectObject(hMemoryDC, hMemoryBitmap);

    // Draw a white rectangle on the bitmap
    SelectObject(hMemoryDC, GetStockObject(WHITE_BRUSH));
    Rectangle(hMemoryDC, 0, 0, sizeRect.cx, sizeRect.cy);
    SelectObject(hMemoryDC, GetStockObject(NULL_BRUSH));

    // Set bitmap background mode to transparent mode
    oldBkMode = SetBkMode(hMemoryDC, TRANSPARENT);

    // Get a Graphics object from the memory device context
    Graphics graphics(hMemoryDC);

    // draw a black rectangle on the bitmap
    s = graphics.FillRectangle(pBlackBrush, 0, 0, 400, 400);
    if (s != Ok)
    {
        cout << "FillRectangle failed" << endl;
        return false;
    }

    // draw white text on the black rectangle using the font we created
    s = graphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
    if (s != Ok)
    {
        cout << "SetTextRenderingHint failed" << endl;
        return false;
    }

    // Create a font object and draw the text on the bitmap
    pFontFamily = new FontFamily(L"Arial");
    if (pFontFamily == NULL)
    {
        cout << "new FontFamily failed" << endl;
        return false;
    }
    pFont = new Font(pFontFamily, fontSize, fs);
    if (pFont == NULL)
    {
        cout << "new Font failed" << endl;
        if (pFontFamily != NULL)
            delete pFontFamily;
        return false;
    }

    s = graphics.DrawString(L"TEST STRING", 11, pFont, origin, pWhiteBrush);
    if (s == Ok)
    {   // Save the bitmap to a file
        bRet = SaveBitmapToFile(hMemoryDC, hMemoryBitmap, bitmapInfo, sfileName);
    }

    // Clean up
    SetBkMode(hMemoryDC, oldBkMode);
    if (pFont != NULL)
        delete pFont;
    if (pFontFamily != NULL)
        delete pFontFamily;
    if (hDefaultBitmap != NULL && hMemoryBitmap != NULL)
        SelectObject(hMemoryDC, hDefaultBitmap);
    if (hMemoryBitmap != NULL)
        DeleteObject(hMemoryBitmap);
    if (hMemoryDC != NULL)
        DeleteDC(hMemoryDC);
    return bRet;
}

bool SaveBitmapToFile(HDC hMemoryDC, HBITMAP hMemoryBitmap, BITMAPINFO& bitmapInfo, string sFileName)
{
    bool bRet(false);
    DWORD dwBmpSize(0);
    BYTE* pBytes(NULL);
    ULONG ulcb(0);
    WCHAR wchBuff[BUFFER_SIZE];
    PSTR pTemp = (PSTR)wchBuff;

    for (unsigned int nChar = 0; nChar < sFileName.length(); nChar++)
    {
        *pTemp++ = sFileName[nChar];
        *pTemp++ = 0;
    }
    *pTemp++ = 0;
    *pTemp = 0;

    dwBmpSize = ((bitmapInfo.bmiHeader.biWidth * bitmapInfo.bmiHeader.biBitCount + 31) / 32) * 4 * bitmapInfo.bmiHeader.biHeight;
    pBytes = new BYTE[dwBmpSize];
    bRet = (GetDIBits(hMemoryDC, hMemoryBitmap, 0, bitmapInfo.bmiHeader.biHeight, &pBytes[0], &bitmapInfo, DIB_RGB_COLORS) != 0);
    if (bRet)
        bRet = WriteBitmapFile(pBytes, bitmapInfo.bmiHeader, wchBuff);
    if (pBytes != NULL)
    {
        delete[] pBytes;
        pBytes = NULL;
    }
    return bRet;
}

bool WriteBitmapFile(BYTE* pBitmapBits, BITMAPINFOHEADER& bmpInfoHeader, LPCTSTR lpszFileName)
{
    BITMAPFILEHEADER bfh = {0};
    bool bRet(false);
    DWORD dwcb2Write(0), dwcbWritten(0);

    // This value should be values of BM letters i.e 0×4D42
    // 0x4D = M 0x42 = B storing in reverse order to match with endian
    bfh.bfType=0x4D42;
    // or...
    // bfh.bfType = ‘B’+(’M’ << 8);
    // <<8 used to shift ‘M’ to end

    // Offset to the RGBQUAD
    bfh.bfOffBits = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER);
    bfh.bfSize = bfh.bfOffBits + bmpInfoHeader.biSizeImage;

    HANDLE hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        cout << "CreateFile failed" << endl;
        ShowError();
        return false;
    }
    dwcb2Write = sizeof(bfh);
    bRet = (WriteFile(hFile, &bfh, dwcb2Write, &dwcbWritten, NULL) != 0);
    if (!bRet || (dwcbWritten != dwcb2Write))
    {
        cout << "WriteFile failed" << endl;
        ShowError();
        CloseHandle(hFile);
        return false;
    }
    dwcb2Write = sizeof(bmpInfoHeader);
    bRet = (WriteFile(hFile, &bmpInfoHeader, dwcb2Write, &dwcbWritten, NULL) != 0);
    if (!bRet || (dwcbWritten != dwcb2Write))
    {
        cout << "WriteFile failed" << endl;
        ShowError();
        CloseHandle(hFile);
        return false;
    }
    dwcb2Write = bmpInfoHeader.biSizeImage;
    bRet = (WriteFile(hFile, pBitmapBits, dwcb2Write, &dwcbWritten, NULL) != 0);
    if (!bRet || (dwcbWritten != dwcb2Write))
    {
        cout << "WriteFile failed" << endl;
        ShowError();
        CloseHandle(hFile);
        return false;
    }
    CloseHandle(hFile);
    return true;
}

void ShowError()
{   // Retrieve the system error message for the last-error code
    char szBuff[BUFFER_SIZE];
    LPTSTR lpMsgBuf;
    size_t retVal;
    DWORD dw = GetLastError(), dwRet(0);

    dwRet = FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );

    wcstombs_s(&retVal, szBuff, (size_t)BUFFER_SIZE, lpMsgBuf, (size_t)BUFFER_SIZE );

    // Display the error message
    cout << szBuff << endl;

    LocalFree(lpMsgBuf);
}

I'm trying to find a way to use the Gdiplus::Graphics.DrawString method to create a bitmap having white text on a black background.

(Why? Because I need to be able to rotate the text, and the Gdiplus::Graphics object has RotateTransform available for that.)

I've written a sample console app that demonstrates what I see when trying to draw white text on a black rectangle. (The code is pasted below.)

Anyone have ideas what I'm doing wrong? Thanks in advance for any help.

////////////////////////

// DrawTextTest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#pragma comment (lib,"Gdiplus.lib")
#include <Windows.h>
#include <GdiPlusEnums.h>
#include <GdiPlusTypes.h>
#include <GdiPlus.h>
#include <iostream>
#include <string>

using namespace Gdiplus;
using namespace std;

bool RunTheTest(string);
bool SaveBitmapToFile(HDC, HBITMAP, BITMAPINFO&, string);
bool WriteBitmapFile(BYTE*, BITMAPINFOHEADER&, LPCTSTR);
void ShowError();

#define BUFFER_SIZE 1024

int _tmain(int argc, _TCHAR* argv[])
{
    bool bRet(false);
    char szBuff[BUFFER_SIZE];
    size_t retVal;
    string sFileName, sUserInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartupInput gdiplusStartupInput;

    if (argc > 1)
    {
        for (int nArg = 1; nArg < argc; nArg++)
        {
            wcstombs_s(&retVal, szBuff, (size_t)BUFFER_SIZE,  argv[nArg], (size_t)BUFFER_SIZE );
            if (nArg > 1)
                sFileName += " ";
            sFileName += szBuff;
        }
    }
    else
    {
        cout << "Input the full path filename (do not enclose in quotes)" << endl;
        cin >> sUserInput;
        sFileName += sUserInput;
    }
    Status s = GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    bRet = RunTheTest(sFileName);
    GdiplusShutdown(gdiplusToken);
    if (bRet)
        return 0;
    return 1;
}

bool RunTheTest(string sfileName)
{
    BITMAPINFO bitmapInfo;
    bool bRet(false);
    Brush* pBlackBrush = new SolidBrush(Gdiplus::Color::Black);
    Brush* pWhiteBrush = new SolidBrush(Gdiplus::Color::White);
    double inchesPerMeter(39.3700787);
    float fontSize(30);
    Font* pFont(NULL);
    FontFamily* pFontFamily(NULL);
    FontStyle fs(FontStyleRegular);
    HBITMAP hMemoryBitmap(NULL);
    HDC hMemoryDC(NULL);
    HGDIOBJ hDefaultBitmap(NULL);
    int pelsPerMeter(0), resolution(240);
    int oldBkMode(0);
    LPTSTR pFileName(NULL);
    PointF origin(0, 0);
    SIZE sizeRect;
    Status s;

    // Initialize a memory device context compatible with the screen
    hMemoryDC = CreateCompatibleDC(NULL);

    // Prepare some values for creating a memory bitmap
    pelsPerMeter = (int)(resolution * inchesPerMeter);
    sizeRect.cx = sizeRect.cy = 400;

    // Create the memory bitmap
    bitmapInfo.bmiHeader.biBitCount = 24;
    bitmapInfo.bmiHeader.biCompression = BI_RGB;
    bitmapInfo.bmiHeader.biPlanes = 1;
    bitmapInfo.bmiHeader.biXPelsPerMeter = pelsPerMeter;
    bitmapInfo.bmiHeader.biYPelsPerMeter = pelsPerMeter;
    bitmapInfo.bmiHeader.biSize = sizeof(bitmapInfo.bmiHeader);
    bitmapInfo.bmiHeader.biWidth = sizeRect.cx;
    bitmapInfo.bmiHeader.biHeight = sizeRect.cy;
    hMemoryBitmap = CreateCompatibleBitmap(hMemoryDC, sizeRect.cx, sizeRect.cy);
    hDefaultBitmap = SelectObject(hMemoryDC, hMemoryBitmap);

    // Draw a white rectangle on the bitmap
    SelectObject(hMemoryDC, GetStockObject(WHITE_BRUSH));
    Rectangle(hMemoryDC, 0, 0, sizeRect.cx, sizeRect.cy);
    SelectObject(hMemoryDC, GetStockObject(NULL_BRUSH));

    // Set bitmap background mode to transparent mode
    oldBkMode = SetBkMode(hMemoryDC, TRANSPARENT);

    // Get a Graphics object from the memory device context
    Graphics graphics(hMemoryDC);

    // draw a black rectangle on the bitmap
    s = graphics.FillRectangle(pBlackBrush, 0, 0, 400, 400);
    if (s != Ok)
    {
        cout << "FillRectangle failed" << endl;
        return false;
    }

    // draw white text on the black rectangle using the font we created
    s = graphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
    if (s != Ok)
    {
        cout << "SetTextRenderingHint failed" << endl;
        return false;
    }

    // Create a font object and draw the text on the bitmap
    pFontFamily = new FontFamily(L"Arial");
    if (pFontFamily == NULL)
    {
        cout << "new FontFamily failed" << endl;
        return false;
    }
    pFont = new Font(pFontFamily, fontSize, fs);
    if (pFont == NULL)
    {
        cout << "new Font failed" << endl;
        if (pFontFamily != NULL)
            delete pFontFamily;
        return false;
    }

    s = graphics.DrawString(L"TEST STRING", 11, pFont, origin, pWhiteBrush);
    if (s == Ok)
    {   // Save the bitmap to a file
        bRet = SaveBitmapToFile(hMemoryDC, hMemoryBitmap, bitmapInfo, sfileName);
    }

    // Clean up
    SetBkMode(hMemoryDC, oldBkMode);
    if (pFont != NULL)
        delete pFont;
    if (pFontFamily != NULL)
        delete pFontFamily;
    if (hDefaultBitmap != NULL && hMemoryBitmap != NULL)
        SelectObject(hMemoryDC, hDefaultBitmap);
    if (hMemoryBitmap != NULL)
        DeleteObject(hMemoryBitmap);
    if (hMemoryDC != NULL)
        DeleteDC(hMemoryDC);
    return bRet;
}

bool SaveBitmapToFile(HDC hMemoryDC, HBITMAP hMemoryBitmap, BITMAPINFO& bitmapInfo, string sFileName)
{
    bool bRet(false);
    DWORD dwBmpSize(0);
    BYTE* pBytes(NULL);
    ULONG ulcb(0);
    WCHAR wchBuff[BUFFER_SIZE];
    PSTR pTemp = (PSTR)wchBuff;

    for (unsigned int nChar = 0; nChar < sFileName.length(); nChar++)
    {
        *pTemp++ = sFileName[nChar];
        *pTemp++ = 0;
    }
    *pTemp++ = 0;
    *pTemp = 0;

    dwBmpSize = ((bitmapInfo.bmiHeader.biWidth * bitmapInfo.bmiHeader.biBitCount + 31) / 32) * 4 * bitmapInfo.bmiHeader.biHeight;
    pBytes = new BYTE[dwBmpSize];
    bRet = (GetDIBits(hMemoryDC, hMemoryBitmap, 0, bitmapInfo.bmiHeader.biHeight, &pBytes[0], &bitmapInfo, DIB_RGB_COLORS) != 0);
    if (bRet)
        bRet = WriteBitmapFile(pBytes, bitmapInfo.bmiHeader, wchBuff);
    if (pBytes != NULL)
    {
        delete[] pBytes;
        pBytes = NULL;
    }
    return bRet;
}

bool WriteBitmapFile(BYTE* pBitmapBits, BITMAPINFOHEADER& bmpInfoHeader, LPCTSTR lpszFileName)
{
    BITMAPFILEHEADER bfh = {0};
    bool bRet(false);
    DWORD dwcb2Write(0), dwcbWritten(0);

    // This value should be values of BM letters i.e 0×4D42
    // 0x4D = M 0x42 = B storing in reverse order to match with endian
    bfh.bfType=0x4D42;
    // or...
    // bfh.bfType = ‘B’+(’M’ << 8);
    // <<8 used to shift ‘M’ to end

    // Offset to the RGBQUAD
    bfh.bfOffBits = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER);
    bfh.bfSize = bfh.bfOffBits + bmpInfoHeader.biSizeImage;

    HANDLE hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        cout << "CreateFile failed" << endl;
        ShowError();
        return false;
    }
    dwcb2Write = sizeof(bfh);
    bRet = (WriteFile(hFile, &bfh, dwcb2Write, &dwcbWritten, NULL) != 0);
    if (!bRet || (dwcbWritten != dwcb2Write))
    {
        cout << "WriteFile failed" << endl;
        ShowError();
        CloseHandle(hFile);
        return false;
    }
    dwcb2Write = sizeof(bmpInfoHeader);
    bRet = (WriteFile(hFile, &bmpInfoHeader, dwcb2Write, &dwcbWritten, NULL) != 0);
    if (!bRet || (dwcbWritten != dwcb2Write))
    {
        cout << "WriteFile failed" << endl;
        ShowError();
        CloseHandle(hFile);
        return false;
    }
    dwcb2Write = bmpInfoHeader.biSizeImage;
    bRet = (WriteFile(hFile, pBitmapBits, dwcb2Write, &dwcbWritten, NULL) != 0);
    if (!bRet || (dwcbWritten != dwcb2Write))
    {
        cout << "WriteFile failed" << endl;
        ShowError();
        CloseHandle(hFile);
        return false;
    }
    CloseHandle(hFile);
    return true;
}

void ShowError()
{   // Retrieve the system error message for the last-error code
    char szBuff[BUFFER_SIZE];
    LPTSTR lpMsgBuf;
    size_t retVal;
    DWORD dw = GetLastError(), dwRet(0);

    dwRet = FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );

    wcstombs_s(&retVal, szBuff, (size_t)BUFFER_SIZE, lpMsgBuf, (size_t)BUFFER_SIZE );

    // Display the error message
    cout << szBuff << endl;

    LocalFree(lpMsgBuf);
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

地狱即天堂 2024-11-04 12:09:12

只需首先使用 Graphics::FillRectangle() 绘制矩形即可。从 Graphics::MeasureString() 获取所需的矩形大小。

Simply draw the rectangle first with Graphics::FillRectangle(). Get the rectangle size you need from Graphics::MeasureString().

无敌元气妹 2024-11-04 12:09:12

不幸的是,你没有描述你的结果是什么,但我可以从我自己的经验中看出,有时将 Win32-Calls 与 GDI-Plus 混合是一个问题。我有相反的效果:使用 GDI-Plus Win32 DrawText 绘制填充区域后有时没有效果。
所以我的建议是对所有调用使用 GDIPlus,而不是混合在 Win32 调用中。

GDIPLus 一片混乱。它的 C++ 头文件完全不完整,并且缺少一些有用的东西。

我刚刚发现这篇文章描述了混合 GDI 和 GDI+ 的一些限制:
https://support.microsoft. com/en-gb/help/311221/info-interoperability- Between-gdi-and-gdi

Unfortunately you dont describe what the result of your is, but i can tell from my own experience that it is sometimes a problem to mix Win32-Calls with GDI-Plus. I have the opposite effect: After drawing Filled areas with GDI-Plus Win32 DrawText has sometimes no effect.
So my suggestion would be to using GDIPlus for all calls rather than mixing in Win32 calls.

GDIPLus is a mess. Its C++ Headers are totally incomplete and lacks qite a few usefull things.

I just found this article describing some limitations of mixing GDI and GDI+:
https://support.microsoft.com/en-gb/help/311221/info-interoperability-between-gdi-and-gdi

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文