You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WinAPI下将32位真彩色HBITMAP转换为1位单色位图的问题

HBITMAP转1位单色位图的问题修复

我正尝试在WinAPI中将HBITMAP转换为1位单色位图,但转换后的图像并非原位图的单色版本。不想使用任何额外库,以下是我的实现代码:

HBITMAP ConvertToMonochromeBitmap(HBITMAP hBmp) {
    BITMAP bmp;
    GetObject(hBmp, sizeof(BITMAP), &bmp);

    // Truecolor bmi
    BITMAPINFO bmiTrueColor = { 0 };
    bmiTrueColor.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmiTrueColor.bmiHeader.biWidth = bmp.bmWidth;
    bmiTrueColor.bmiHeader.biHeight = -bmp.bmHeight;
    bmiTrueColor.bmiHeader.biPlanes = 1;
    bmiTrueColor.bmiHeader.biBitCount = 32;
    bmiTrueColor.bmiHeader.biCompression = BI_RGB;

    // Getting TrueColor bitmap bits
    BYTE* pBitmapBits = new BYTE[bmp.bmWidth * bmp.bmHeight * 4];
    GetDIBits(GetDC(NULL), hBmp, 0, bmp.bmHeight, pBitmapBits, &bmiTrueColor, DIB_RGB_COLORS);

    // Create BYTE* array for Monochrome bitmap
    BYTE* pMonochromeData = new BYTE[bmp.bmWidth * bmp.bmHeight / 8];
    memset(pMonochromeData, 0, bmp.bmWidth * bmp.bmHeight / 8);

    for (int i = 0; i < bmp.bmWidth * bmp.bmHeight; i++) {
        int blue = pBitmapBits[i * 4];
        int green = pBitmapBits[i * 4 + 1];
        int red = pBitmapBits[i * 4 + 2];
        int grayscale = (red + green + blue) / 3;

        // Calculate the index in the monochrome bitmap.
        int monochromeIndex = i / 8;

        // Calculate the bit position within the byte.
        int bitOffset = 7 - (i % 8);

        // Set the corresponding bit in the monochrome bitmap.
        if (grayscale < 128) {
            // Set bit to 1 if color is black
            pMonochromeData[monochromeIndex] |= (1 << bitOffset);
        }
        else {
            // Set bit to 0 if color is not black
            pMonochromeData[monochromeIndex] &= ~(1 << bitOffset);
        }
    }

    BITMAPINFO bmi = { 0 };
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = bmp.bmWidth;
    bmi.bmiHeader.biHeight = -bmp.bmHeight;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 1;
    bmi.bmiHeader.biCompression = BI_RGB;
    bmi.bmiHeader.biSizeImage = bmp.bmWidth * bmp.bmHeight / 8;

    HBITMAP newBitmap = CreateDIBitmap(GetDC(NULL), &bmi.bmiHeader, CBM_INIT, pMonochromeData, &bmi, DIB_RGB_COLORS);

    delete[] pMonochromeData;
    delete[] pBitmapBits;

    return newBitmap;
}

问题分析与修复点

你的代码存在几个关键问题,导致转换后的单色位图显示异常:

  1. 行字节对齐错误
    WinAPI要求DIB的每行字节数必须是4的倍数,直接用bmp.bmWidth / 8计算会导致宽度非8倍数时行数据错位,正确计算方式为:
int monochromeWidthBytes = ((bmp.bmWidth + 7) / 8 + 3) & ~3;
  1. 缺失1位位图颜色表
    1位单色位图必须提供包含黑白两个颜色项的颜色表,否则系统会用默认颜色映射导致显示异常,需要初始化bmi.bmiColors数组。

  2. GetDC(NULL)的资源泄漏
    直接调用GetDC(NULL)获取屏幕DC后未释放,会造成GDI资源泄漏,应创建临时兼容DC并在使用后释放。

  3. 灰度计算精度不足
    简单的(R+G+B)/3平均法不如加权灰度公式(0.299*R + 0.587*G + 0.114*B)准确,后者更符合人眼对颜色的感知,能减少色差。

修复后的完整代码

HBITMAP ConvertToMonochromeBitmap(HBITMAP hBmp) {
    BITMAP bmp;
    GetObject(hBmp, sizeof(BITMAP), &bmp);

    // 创建临时兼容DC,避免使用屏幕DC的问题
    HDC hTempDC = CreateCompatibleDC(NULL);
    HBITMAP hOldBmp = (HBITMAP)SelectObject(hTempDC, hBmp);

    // Truecolor bmi
    BITMAPINFO bmiTrueColor = { 0 };
    bmiTrueColor.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmiTrueColor.bmiHeader.biWidth = bmp.bmWidth;
    bmiTrueColor.bmiHeader.biHeight = -bmp.bmHeight;
    bmiTrueColor.bmiHeader.biPlanes = 1;
    bmiTrueColor.bmiHeader.biBitCount = 32;
    bmiTrueColor.bmiHeader.biCompression = BI_RGB;

    // 分配32位位图数据缓冲区
    DWORD dwTrueColorSize = bmp.bmWidth * bmp.bmHeight * 4;
    BYTE* pBitmapBits = new BYTE[dwTrueColorSize];
    GetDIBits(hTempDC, hBmp, 0, bmp.bmHeight, pBitmapBits, &bmiTrueColor, DIB_RGB_COLORS);

    // 计算单色位图每行字节数(4字节对齐)
    int monochromeWidthBytes = ((bmp.bmWidth + 7) / 8 + 3) & ~3;
    DWORD dwMonochromeSize = monochromeWidthBytes * bmp.bmHeight;
    BYTE* pMonochromeData = new BYTE[dwMonochromeSize];
    memset(pMonochromeData, 0xFF, dwMonochromeSize); // 默认填充白色(bit 0)

    for (int y = 0; y < bmp.bmHeight; y++) {
        for (int x = 0; x < bmp.bmWidth; x++) {
            int pixelIndex = y * bmp.bmWidth + x;
            BYTE blue = pBitmapBits[pixelIndex * 4];
            BYTE green = pBitmapBits[pixelIndex * 4 + 1];
            BYTE red = pBitmapBits[pixelIndex * 4 + 2];

            // 加权灰度计算,更符合人眼感知
            int grayscale = (int)(0.299 * red + 0.587 * green + 0.114 * blue);

            // 计算当前像素在单色缓冲区中的位置
            int byteOffset = y * monochromeWidthBytes + (x / 8);
            int bitPos = 7 - (x % 8);

            // 灰度低于128则设为黑色(bit 1),否则保持白色(bit 0)
            if (grayscale < 128) {
                pMonochromeData[byteOffset] &= ~(1 << bitPos);
            }
        }
    }

    // 初始化单色位图的BITMAPINFO,包含颜色表
    BITMAPINFO bmi = { 0 };
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = bmp.bmWidth;
    bmi.bmiHeader.biHeight = -bmp.bmHeight;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 1;
    bmi.bmiHeader.biCompression = BI_RGB;
    bmi.bmiHeader.biSizeImage = dwMonochromeSize;

    // 设置颜色表:索引0=白色,索引1=黑色
    bmi.bmiColors[0].rgbRed = 255;
    bmi.bmiColors[0].rgbGreen = 255;
    bmi.bmiColors[0].rgbBlue = 255;
    bmi.bmiColors[1].rgbRed = 0;
    bmi.bmiColors[1].rgbGreen = 0;
    bmi.bmiColors[1].rgbBlue = 0;

    // 创建单色位图
    HBITMAP newBitmap = CreateDIBitmap(hTempDC, &bmi.bmiHeader, CBM_INIT, pMonochromeData, &bmi, DIB_RGB_COLORS);

    // 释放资源
    SelectObject(hTempDC, hOldBmp);
    DeleteDC(hTempDC);
    delete[] pMonochromeData;
    delete[] pBitmapBits;

    return newBitmap;
}

内容的提问来源于stack exchange,提问作者Anwdy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 03:23:10