2
votes

In C++, I want to create a simple transparent image with Gdiplus and save it as a png. I have the following code:

    // These variables are class members and got initialized and are used elsewhere
    BITMAPINFO bmi;
    HDC hdc;
    void* pvBits;

    ZeroMemory(&bmi, sizeof(BITMAPINFO));
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = some_width;
    bmi.bmiHeader.biHeight = some_height;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;
    bmi.bmiHeader.biSizeImage = ((((bmi.bmiHeader.biWidth * bmi.bmiHeader.biBitCount) + 31) & ~31) >> 3) * bmi.bmiHeader.biHeight;

    HBITMAP hBM = CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS, &pvBits, NULL, 0x0);
    FillMemory(pvBits, bmi.bmiHeader.biSizeImage, 255);

    HGDIOBJ oldObj = SelectObject(hDC, hBM);
    ReleaseDC(NULL, hDC);

    GdiFlush();

    GdiPlusBitmap* bitmap = new Gdiplus::Bitmap(&bmi, pvBits);

When I save the image to png, I can see that there is an alpha channel, but its set to 0, so nothing transparent (RGB is all set). I also tried changing the 255 to 0, but that only gives me a black image with no transpirancy. Why is the Fillmemory call not filling the alpha channel, or am I missing something else?

3
Where/how do you write the PNG? Can you share the PNG it creates? Have you tried looking at the memory you are trying to fill with the debugger? - Mark Setchell
We can't see how you saved the bitmap. A very common mistake is to save the file with the wrong encoding but the right filename extension. This doesn't confuse other programs, they look at the file header instead of the extension. - Hans Passant
Why do you create a DIB section in the first place, instead of constructing a Gdiplus::Bitmap from scratch, specifying the desired PixelFormat? - zett42
@zett42 It's old code, hdc, bmi and so on are member variables used thorughout the code, I tried to shortened it. - Kackao
Saving the png works fine, when I create a bitmap from scratch and specify the right pixel format, I can see the transparincy in the saved png. - Kackao

3 Answers

2
votes

FillMemory(pvBits, bmi.bmiHeader.biSizeImage, 255) will fill the memory with solid white color. If you write over the image with GDI functions, the alpha values remain unchanged. You won't see any transparency even if the file supports it.

To create 32-bit image you only need Gdiplus::Bitmap(w, h, PixelFormat32bppARGB). There is no need for BITMAPINFO and CreateDIBSection

If you mix GDI+ with GDI functions, you may want to reset alpha after writing with GDI functions. For example:

void test()
{
    int w = 100;
    int h = 100;
    int bitcount = 32;

    int size = ((((w * bitcount) + 31) & ~31) >> 3) * h;

    BITMAPINFO bmi = { 0 };
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = w;
    bmi.bmiHeader.biHeight = -h;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = bitcount;
    bmi.bmiHeader.biCompression = BI_RGB;
    bmi.bmiHeader.biSizeImage = size;

    HDC hdc = GetDC(0);
    BYTE* pvBits = NULL;
    HBITMAP hbitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS,
             (void**)&pvBits, NULL, 0x0);
    FillMemory(pvBits, size, 0);

    auto memdc = CreateCompatibleDC(hdc);
    auto oldbmp = SelectObject(memdc, hbitmap);
    SetBkColor(memdc, RGB(255, 0, 0));
    TextOut(memdc, 0, 0, L"123", 3);

    //GDI cleanup, don't delete hbitmap yet
    SelectObject(memdc, oldbmp);
    DeleteDC(memdc);
    ReleaseDC(0, hdc);

    //make the non-zero colors transparent:
    for(int i = 0; i < size; i += 4)
    {
        int n = *(int*)(pvBits + i);
        if (n != 0)
            pvBits[i + 3] = 255;
    }

    CLSID clsid_png;
    CLSIDFromString(L"{557cf406-1a04-11d3-9a73-0000f81ef32e}", &clsid_png);

    Gdiplus::Bitmap* bitmap = new Gdiplus::Bitmap(w, h, PixelFormat32bppARGB);
    Gdiplus::BitmapData data;
    bitmap->LockBits(&Gdiplus::Rect(0, 0, w, h), 
        Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &data);
    memcpy(data.Scan0, pvBits, size);
    bitmap->UnlockBits(&data);

    //safe to delete hbitmap
    DeleteObject(hbitmap);

    bitmap->Save(L"test.png", &clsid_png);

    delete bitmap;
}
2
votes

Your code suffers from the same hdc problems as in previous question. A filling image with 0 should give a completely transparent black image as expected.

But I think it would be simpler to create bitmap directly specifying dimensions and pixel format and set content later:

Gdiplus::Bitmap* bitmap = new Gdiplus::Bitmap(width, height, PixelFormat32bppARGB);
-1
votes

In transparent images, the value 0 means "full transparent" and 255 means "full opaque".