c++ - 从 ARGB 到灰度的转换 - 结果很好,但颠倒了

c++ - Conversion from ARGB to Grayscale - Results are well, but upside down

我写了一个小函数来将 ARGB 中的位图转换为灰度。转换本身效果很好,但结果是颠倒的。 我找不到错误。

代码: #包括 #include

inline BYTE GrayScaleValue(BYTE* r, BYTE* g, BYTE* b) { return /*ceil*/(0.21f * (*r) + 0.72f * (*g) + 0.07f * (*b)); }

extern "C" __declspec(dllexport) HBITMAP ConvertToMonocrom(HBITMAP bmp) {
    INT x = 0, y = 0;
    char Gray;
    BITMAP bm;
    GetObject(bmp, sizeof(BITMAP), (LPSTR)&bm);
    BYTE * pImgByte = (BYTE *)bm.bmBits;
    INT iWidthBytes = bm.bmWidth * 4;
    for (y = 0; y < bm.bmHeight; y++) {
        for (x = 0; x < bm.bmWidth; x++) {
            Gray = GrayScaleValue(&pImgByte[y * iWidthBytes + x * 4 + 3], &pImgByte[y * iWidthBytes + x * 4 + 2], &pImgByte[y * iWidthBytes + x * 4 + 1]);
            pImgByte[y * iWidthBytes + x * 4] = Gray;
            pImgByte[y * iWidthBytes + x * 4 + 1] = Gray;
            pImgByte[y * iWidthBytes + x * 4 + 2] = Gray;
            pImgByte[y * iWidthBytes + x * 4 + 3] = Gray;
        }
    }
    return CreateBitmapIndirect(&bm);
}

这是图片:

转换后的图片,未设置 A - 仅 RGB:

转换后的图片,如代码所示(设置Alpha-Value:

嗯,我不知道,他为什么要将 "Transparent" 设置为黑色...

HBITMAP 可以引用以多种 种不同格式存储的位图。它可以是 DIBSECTION 或设备相关的位图。其中任何一个都可以以多种方式表示像素值。请注意,GetObject documentation 列出了两种获取有关 HBITMAP 信息的不同方法。

我的猜测是您的输入是一个 DIBSECTION,扫描线从上到下存储。当您请求 BITMAP(使用 GetObject)时,您会丢失一些格式信息,特别是图像是自下而上还是自上而下,它是否具有 alpha 通道以及通道的顺序(例如,BGRA V .ARGB)。 BITMAP 不能像 DIBSECTION 那样表示详细的格式。

处理像素数据后,您将使用 CreateBitmapIndirect 创建新的位图。由于 BITMAP 结构不包含有关数据是自下而上还是自上而下的信息,因此它使用默认值,即自下而上。由于您(显然)从自上而下的像素数据开始,因此您有效地将图像上下颠倒了。

您在使用 alpha 通道时遇到的困难也可以解释为当您尝试仅使用 BITMAP 描述格式时丢失了信息。如果颜色通道的顺序与 CreateBitmapIndirect 假定的默认顺序不同,那么您会确切地看到这个问题。通过将 alpha 设置为与所有其他通道相同的灰度值,您已经有效地隐藏了您打乱了颜色通道顺序的事实。

一个解决方案

有几种不同的方法可以解决这个问题。这是一种可能的解决方案:

您可以使用 GetDIBits 要求 Windows 为您提供指向您想要使用的格式的像素数据的指针,而不管其原始格式如何。然后,您可以 Windows 使用 SetDIBits 将修改后的像素值转换回位图格式。

您必须填写一个 BITMAPINFO(有多种变体)来描述您期望的内存格式。您会将此 BITMAPINFO 传递给 GetDIBits 和 SetDIBits。

Adrian McCarthy 向我们展示了更好的解决方案,但我需要几天时间来阅读有关 DIB 的信息。我需要一个快速的解决方案并通过再次翻转位图得到它。

关于如何将 HBITMAP 从 TrueColor 转换为 GrayScale 的问题在 Internet 上经常找到并且答案并不令人满意,我现在想发布我的代码:

#include <stdio.h>
#include <Windows.h>
#include <math.h>

inline BYTE GrayScaleValue(BYTE* r, BYTE* g, BYTE* b) { return /*ceil*/(0.21f * (*r) + 0.72f * (*g) + 0.07f * (*b)); }

extern "C" __declspec(dllexport) HBITMAP ConvertToMonocrom(HBITMAP bmp) {
    #pragma region Local variables
    INT x = 0, y = 0;
    char Gray;
    BITMAP bm;
    #pragma endregion

    #pragma region Activating HBITMAP
    GetObject(bmp, sizeof(BITMAP), (LPSTR)&bm);
    #pragma endregion

    #pragma region Assigning pointer
    BYTE * pImgByte = (BYTE *)bm.bmBits;
    BYTE* fpImgByte = (BYTE*)malloc(bm.bmHeight * bm.bmWidth * sizeof(BYTE));
    if (fpImgByte == nullptr) { printf("ERROR: Unable to allocate memory for buffer array!"); return nullptr; }
    INT iWidthBytes = bm.bmWidth * 4;
    #pragma endregion

   #pragma region TrueColor to GrayScale
    for (y = 0; y < bm.bmHeight; y++) {
        for (x = 0; x < bm.bmWidth; x++) {
            Gray = GrayScaleValue(&pImgByte[y * iWidthBytes + x * 4 + 3], &pImgByte[y * iWidthBytes + x * 4 + 2], &pImgByte[y * iWidthBytes + x * 4 + 1]);
            fpImgByte[y * bm.bmWidth + x] = Gray;
        }
    }
    #pragma endregion

    #pragma region Flipping bitmap
    for (y = 0; y < bm.bmHeight; y++) {
        for (x = 0; x < bm.bmWidth; x++) {
            Gray = fpImgByte[(bm.bmHeight - y - 1)*bm.bmWidth + x];
            pImgByte[y * iWidthBytes + x * 4] = Gray;
            pImgByte[y * iWidthBytes + x * 4 + 1] = Gray;
            pImgByte[y * iWidthBytes + x * 4 + 2] = Gray;
            pImgByte[y * iWidthBytes + x * 4 + 3] = Gray;
        }
    }
    #pragma endregion 

    #pragma region Releasing memory
    free(fpImgByte);
    #pragma endregion

    #pragma region Returning converted bitmap
    return CreateBitmapIndirect(&bm);
    #pragma endregion
}

结果:

真彩色屏幕截图:

转换后的截图: