Creating 24-bit BITMAP in Winapi

716 views Asked by At

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?

1

There are 1 answers

7
Anders On

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(...)
}