-1

I cannot convert a GDI+ bitmap to base 64 in C++

I have so far:

  1. Downloaded and included this library: https://github.com/ReneNyffenegger/cpp-base64

  2. Written the function that you can see below.

  3. Called it, passing in a bitmap that I know has data. I know that the bitmap has data because I am using in another function called directly after this one.

The problem is that the charPixels array is always full of zeros.

Secondly can you please explain to me what the safe way of unlocking the bits is. I fear that if I just do it in the finally, if the bits aren't actually locked at that point I will get an exception.

Stride is a positive number it is: 1280.

Also I have access to 'finally' because it is a MS extension to C++.

[Note: Code is updated because I pasted the wrong code in by mistake]

 std::string GdiBitmapToBase64(Gdiplus::Bitmap* gdiBitmap, int width, int height)
{
    unsigned char* charPixels = nullptr;

    try
    {

        Gdiplus::Rect rect = Gdiplus::Rect(0, 0, width, height);

        Gdiplus::BitmapData gdiBitmapData;
        gdiBitmap->LockBits(&rect, Gdiplus::ImageLockMode::ImageLockModeRead, PixelFormat32bppARGB, &gdiBitmapData);

        auto stride = gdiBitmapData.Stride;
        if (stride < 0) stride = -stride;

        charPixels = new unsigned char[height * stride];

        memset(charPixels, 0, height * stride);

        memcpy(charPixels, gdiBitmapData.Scan0, stride);

        std::string ret = base64_encode(charPixels, stride);
        gdiBitmap->UnlockBits(&gdiBitmapData);
        return ret;
    }
    finally
    {
        if(charPixels != nullptr)
        {
            delete[] charPixels;
        }
    }
}

Here is the code which calls this method. This may help:

void CLIScumm::Wrapper::ScreenUpdated(const void* buf, int pitch, int x, int y, int w, int h, PalletteColor* color)
{
    const unsigned char* bufCounter = static_cast<const unsigned char*>(buf);
    for (int hightCounter = 0; hightCounter < h; hightCounter++, bufCounter = bufCounter + pitch)
    {
        for (int widthCounter = 0; widthCounter < w; widthCounter++)
        {
            PalletteColor currentColor = *(color + *(bufCounter + widthCounter));
            gdiBitmap->SetPixel(x + widthCounter, y + hightCounter, Gdiplus::Color(currentColor.r, currentColor.g, currentColor.b));
        }
    }
    _screenUpdated->Invoke(gcnew System::String(GdiBitmapToBase64(gdiBitmap, DISPLAY_DEFAULT_WIDTH, DISPLAY_DEFAULT_HEIGHT).c_str()));
}

And the declarations:

namespace CLIScumm {
    public ref class Wrapper {
    ...
    private:
         ...
        Gdiplus::Graphics* gdiGraphics;
        Gdiplus::Bitmap* gdiBitmap;
         ...
    };


And the initialization:
void CLIScumm::Wrapper::init()
{
    if (!hasStarted)
    {
        try
        {
            if (!hasStarted)
            {
                ...

                Gdiplus::GdiplusStartupInput gdiplusStartupInput;
                Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);

(malloc(sizeof(100000) * DISPLAY_DEFAULT_HEIGHT * DISPLAY_DEFAULT_WIDTH));
                gdiBitmap = new Gdiplus::Bitmap(DISPLAY_DEFAULT_WIDTH, DISPLAY_DEFAULT_HEIGHT, PixelFormat32bppARGB);
                gdiGraphics = new Gdiplus::Graphics(gdiBitmap);
                InitImage();
                            ...
            }
        }
        ...
    }
}
  • It is not true that gdiPlusStartupInput is uninitialised. It is initialised in 'gdiplusinit.h' by the constructor with the following signature: GdiplusStartupInput( DebugEventProc debugEventCallback = NULL, BOOL suppressBackgroundThread = FALSE, BOOL suppressExternalCodecs = FALSE) – user2732707 Feb 28 '20 at 23:14
  • `sizeof(100000)` is size of integer, which is 4 or 8 – Barmak Shemirani Feb 29 '20 at 01:55

1 Answers1

0

Thank you all for you help, but I solved my own problem.

I was confusing bitmap pixel data with the actual bytes of a bitmap, and Scan0 is the former. The reason I was getting only zero's is because the first few frames were black.

I followed the example from C++ gdi::Bitmap to PNG Image in memory to get the actual bitmap bytes.

I modified the example function to:

bool SavePngMemory(Gdiplus::Bitmap* gdiBitmap, std::vector<BYTE>& data)
{
    //write to IStream
    IStream* istream = nullptr;
    CreateStreamOnHGlobal(NULL, TRUE, &istream);

    CLSID clsid_bmp;
    CLSIDFromString(L"{557cf400-1a04-11d3-9a73-0000f81ef32e}", &clsid_bmp);
    Gdiplus::Status status = gdiBitmap->Save(istream, &clsid_bmp);
    if (status != Gdiplus::Status::Ok)
        return false;

    //get memory handle associated with istream
    HGLOBAL hg = NULL;
    GetHGlobalFromStream(istream, &hg);

    //copy IStream to buffer
    int bufsize = GlobalSize(hg);
    data.resize(bufsize);

    //lock & unlock memory
    LPVOID pimage = GlobalLock(hg);
    memcpy(&data[0], pimage, bufsize);
    GlobalUnlock(hg);

    istream->Release();
    return true;
}

I was then able to convert data to base64 by calling:

base64_encode(&data[0], data.size());

I can't vouch for the quality of the code, because I don't understand how everything works.