1

I wrote a small function to convert a Bitmap in ARGB to Grayscale. The conversion itself works fantastic, but the results are upside down. I cannot find the mistake.

Code: #include #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);
}

Here is the Picture:

Basic picture

The picture after conversion, without setting A - only RGB: Conversion without setting Alpha-Value

The picture after conversion, as shown in the code (with setting Alpha-Value: Conversion with setting Alpha-Value

Well, I don't know, why he is setting "Transparent" to black...

Jan021981
  • 521
  • 3
  • 28
  • Hint: you should use `bm.bmWidthBytes` instead of `iWidthBytes` in case of bitmap data padding (not sure if will happen for ARGB bitmap though, but generally might) – R2RT Sep 08 '17 at 14:02
  • Question on the side: Why are you setting the gray value to the A channel, too? Writing them to all color channels I understand, that is what makes grey. But I would not want the picture to be as opaque as it is dark (or light, depending on the model). – Yunnosch Sep 08 '17 at 14:04
  • What happens if you disable (temporarily, just as debugging experiment) the grey-making and only copy the values unchanged? I wonder whether input and output fileformat simply are different in what the y-coordinate means. – Yunnosch Sep 08 '17 at 14:06
  • 2
    I think the upside down mistake is more likely to be outside of your greying function, e.g. a rendering setup or projection matrix mistake. So pleae show a little more of the surrounding code. Ideally make a [mcve]. Or do you convert image file to image file? Please describe. – Yunnosch Sep 08 '17 at 14:09
  • 2
    https://stackoverflow.com/questions/26144955/after-writing-bmp-file-image-is-flipped-upside-down – stark Sep 08 '17 at 14:12
  • Hi, in my first version, I also didn't set the Alpha-Value - but the result was a little bit strange. With setting the Alpha-Value, I get the results as expected - but upside down. – Jan021981 Sep 08 '17 at 16:08
  • Thank you "stark" for this link. But in the end, I don't understand it. I'm just overwriting the pixels, as they are. Or is the flipp performed in the line `return CreateBitmapIndirect(&bm);`? – Jan021981 Sep 08 '17 at 16:10

2 Answers2

0

An HBITMAP can reference bitmaps stored in many different formats. It could be a DIBSECTION or a device-dependent bitmap. Either of which may represent the pixel values in a variety of ways. Note that the GetObject documentation lists two different ways to get information about an HBITMAP.

My guess is that your input is a DIBSECTION with the scanlines stored top-to-bottom. When you ask for the BITMAP (with GetObject), you lose some of that format information, specifically whether the image is bottom-up or top-down, whether it has an alpha channel, and the order of the channels (e.g., BGRA V. ARGB). A BITMAP cannot represent as much format detail as a DIBSECTION.

After manipulating the pixel data, you're creating a new bitmap with CreateBitmapIndirect. Since the BITMAP structure doesn't contain information about whether the data is bottom-up or top-down, it uses the default, which is bottom-up. Since you (apparently) started with top-down pixel data you've effectively flipped the image upside down.

Your difficulty with the alpha channel can also be explained by the fact that you lost information when you tried to describe the format with just a BITMAP. If the color channels are in a different order than the default that CreateBitmapIndirect assumes, then you would see exactly this problem. By setting alpha to the same gray value as all the other channels, you've effectively hidden the fact that you've scrambled the order of the color channels.

A Solution

There are several different ways to go about this. Here's one possible solution:

You can ask Windows to give you a pointer to the pixel data in the format you want to work in regardless of its native format using GetDIBits. You can then have Windows convert your modified pixel values back into the bitmap's format with SetDIBits.

You'll have to fill out a BITMAPINFO (there are several variants) to describe the in-memory format you're expecting. You'd pass this BITMAPINFO to both GetDIBits and SetDIBits.

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
  • Hi Adrian, thank you very much for this additional information, I didn't know until now. I'll have a look on that. That also explains, why the size of the bitmap in grayscal is different to the one in true color, as the grayscale also uses the same pixelformat. – Jan021981 Sep 08 '17 at 20:51
  • Over the weekend, I read about DIB's in the book Win32 programming from Petzold. He writes, that the down-upper problem is historical, because the mathematicians from IBM wanted the coordinate frame in the lower, left corner and not like the computer scientists in the left upper corner. You can define a upper-down line setting by defining a negative value for bmHeight. – Jan021981 Sep 11 '17 at 12:12
  • Right, but that's part of the information you lose when you ask for a BITMAP structure rather then a BITMAPINFO. The BITMAP will always give a positive value for height, even if the bitmap is top-down. – Adrian McCarthy Sep 11 '17 at 20:11
0

Adrian McCarthy showed us the better solution, but I'll need some days to read about DIB's. I needed a fast solution and got it by flipping the bitmap upside down again.

As the question, how to convert a HBITMAP from TrueColor to GrayScale is found very often in the internet and the answers are not really satisfying, I'd like to poste my code now:

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

Results:

Screenshot in TrueColor:

ScreenShot in TrueColor

Screenshot after conversion:

enter image description here

Jan021981
  • 521
  • 3
  • 28