0

I'm using the following code in order to convert my ImageMagick image to 32-bit HBITMAP:

BITMAP bitmap;
std::memset(&bitmap, 0, sizeof(bitmap));

bitmap.bmType = 0;
bitmap.bmWidth = image->image()->columns;
bitmap.bmHeight = image->image()->rows;
bitmap.bmWidthBytes = 4 * bitmap.bmWidth;
bitmap.bmPlanes = 1;
bitmap.bmBitsPixel = 32;
bitmap.bmBits = NULL;

const size_t size = bitmap.bmWidthBytes * bitmap.bmHeight;
auto buffer = (HANDLE)GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, size);

RGBQUAD *bitmap_bits = (RGBQUAD *) GlobalLock((HGLOBAL) buffer);
register RGBQUAD *q = bitmap_bits;

for (size_t y = 0; y < image->image()->rows; y++)
{
    register auto p = GetVirtualPixels(image->image(), 0, y, image->image()->columns, 1, exception);
    if (!p) break;

    for (size_t x = 0; x < image->image()->columns; x++)
    {
        q->rgbRed = ScaleQuantumToChar(GetPixelRed(image->image(), p));
        q->rgbGreen = ScaleQuantumToChar(GetPixelGreen(image->image(), p));
        q->rgbBlue = ScaleQuantumToChar(GetPixelBlue(image->image(), p));
        q->rgbReserved = 0;

        p += GetPixelChannels(image->image());
        q++;
    }
}

bitmap.bmBits = bitmap_bits;
HBITMAP hbmp = CreateBitmapIndirect(&bitmap);

It works well, but I'd like to save some memory by using images with lower depth. Unfortunately I'm not even able to make it work with 24-bit images. I modified my code to look like this:

BITMAP bitmap;
std::memset(&bitmap, 0, sizeof(bitmap));

bitmap.bmType = 0;
bitmap.bmWidth = image->image()->columns;
bitmap.bmHeight = image->image()->rows;
bitmap.bmWidthBytes = ((bitmap.bmWidth * 24 + 31) / 32) * 4;
bitmap.bmPlanes = 1;
bitmap.bmBitsPixel = 24;
bitmap.bmBits = NULL;

const size_t length = bitmap.bmWidthBytes * bitmap.bmHeight;
auto buffer = (HANDLE)GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, length);

RGBTRIPLE *bitmap_bits = (RGBTRIPLE *) GlobalLock((HGLOBAL) buffer);
register RGBTRIPLE *q = bitmap_bits;

for (size_t y = 0; y < image->image()->rows; y++)
{
    register auto p = GetVirtualPixels(image->image(), 0, y, image->image()->columns, 1, exception);
    if (!p) break;

    for (size_t x = 0; x < image->image()->columns; x++)
    {
        q->rgbtRed = ScaleQuantumToChar(GetPixelRed(image->image(), p));
        q->rgbtGreen = ScaleQuantumToChar(GetPixelGreen(image->image(), p));
        q->rgbtBlue = ScaleQuantumToChar(GetPixelBlue(image->image(), p));

        p += GetPixelChannels(image->image());
        q++;
    }
}

bitmap.bmBits = bitmap_bits;
HBITMAP hbmp = CreateBitmapIndirect(&bitmap);

But it seems that this code cannot produce valid bitmap. What am I doing wrong?

liew
  • 69
  • 1
  • 9

1 Answers1

1

You are not taking the stride/alignment into account. Each row needs to be DWORD aligned.

Calculating Surface Stride

In an uncompressed bitmap, the stride is the number of bytes needed to go from the start of one row of pixels to the start of the next row. The image format defines a minimum stride for an image. In addition, the graphics hardware might require a larger stride for the surface that contains the image. For uncompressed RGB formats, the minimum stride is always the image width in bytes, rounded up to the nearest DWORD. You can use the following formula to calculate the stride:

stride = ((((biWidth * biBitCount) + 31) & ~31) >> 3)

You need to fix the way you access the RGBTRIPLEs in the buffer.

Before the "x loop" you should do something like q = (RGBTRIPLE*) (((char*)bitmap_bits) + (y * bitmap.bmWidthBytes));

CreateBitmapIndirect creates a DDB which is perhaps not the best choice, create a DIB instead:

#define CalcStride(w, bpp) ( ((((w) * (bpp)) + 31) & ~31) >> 3 )
static void SetPixel24(UINT w, void*bits, UINT x, UINT y, COLORREF cr)
{
    RGBTRIPLE*p = ((RGBTRIPLE*) ( ((char*)bits) + (y * CalcStride(w, 24)) )) + x;
    p->rgbtRed = GetRValue(cr);
    p->rgbtGreen = GetGValue(cr);
    p->rgbtBlue = GetBValue(cr);
}
void Silly24BPPExample()
{
    HWND hWnd = CreateWindowEx(WS_EX_APPWINDOW, WC_STATIC, 0, WS_VISIBLE|WS_CAPTION|WS_SYSMENU|WS_OVERLAPPEDWINDOW|SS_BITMAP|SS_REALSIZECONTROL, 0, 0, 99, 99, 0, 0, 0, 0);
    const INT w = 4, h = 4, bpp = 24;
    BITMAPINFO bi;
    ZeroMemory(&bi, sizeof(bi));
    BITMAPINFOHEADER&bih = bi.bmiHeader;
    bih.biSize = sizeof(BITMAPINFOHEADER);
    bih.biWidth = w, bih.biHeight = -h;
    bih.biPlanes = 1, bih.biBitCount = bpp;
    bih.biCompression = BI_RGB;
    void*bits;
    HBITMAP hBmp = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, &bits, NULL, 0);
    for (UINT x = 0; x < w; ++x)
        for (UINT y = 0; y < h; ++y)
            SetPixel24(w, bits, x, y, RGB(255, 0, 0)); // All red
    SetPixel24(w, bits, 0, 0, RGB(0, 0, 255)); // except one blue
    SendMessage(hWnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hBmp);
    for (MSG msg; IsWindow(hWnd) && GetMessage(&msg, 0, 0, 0); ) DispatchMessage(&msg);
    // DeleteObject(...)
}
Anders
  • 97,548
  • 12
  • 110
  • 164
  • Thank you! I've added that code below this line `if (!p) break;` but I'm afraid that this didn't help. Does the rest of the code look OK to you? – liew Jun 05 '19 at 15:22
  • Does CreateBitmapIndirect return NULL or a bitmap with incorrect pixels? You should verify that bmWidthBytes is correct and maybe switch to the algorithm from MSDN. – Anders Jun 05 '19 at 15:54
  • It returns bitmap with incorrect pixels (when I load them onto canvas provided by wxWidgets I can't see anything, when I save it as a bitmap it's displayed as black surface in my image viewer). HBITMAP itself seems to have proper structure, I'm able to get correct bmi.bmiHeader.biBitCount from it. – liew Jun 05 '19 at 16:36
  • @liew Try setting every pixel to a known color (e.g. `q->rgbtRed = 255; q->rgbtGreen = 0; q->rgbtBlue = 255;` for pink) and then see if the resultant bitmap is that color. If it is you know the problem is in the input data not the output conversion. – Jonathan Potter Jun 05 '19 at 18:22
  • @JonathanPotter sorry, I forgot to mention I've already tried that. It seems that it's not related to the conversion – liew Jun 05 '19 at 20:18
  • @Anders I just noticed you've added some extra code, it seems to work with DIB approach, thank you. Is there an easy way to do the same for 8-bit DIB bitmap? – liew Jun 06 '19 at 16:42
  • For < 16 bpp you need a color table and the "pixels" are just a index into the color table. A SetPixel8 function that takes a RGB color would have to try to find the best match in the color table OR change the color table. But it is better to use SetDIBits or something like that a let GDI do the work for you. – Anders Jun 06 '19 at 17:01