Convert Bitmap to Png In-Memory in C++ (Win32)

Convert bitmap to PNG in-memory in C++ (win32)

I read and write PNGs using libpng and it seems to deal with everthing I throw at it (I've used it in unit-tests with things like 257x255 images and they cause no trouble). I believe the API is flexible enough to not be tied to file I/O (or at least you can override its default behaviour e.g see png_set_write_fn in section on customization)

In practice I always use it via the much cleaner boost::gil PNG IO extension, but unfortunately that takes char* filenames and if you dig into it the png_writer and file_mgr classes in its implementation it seem pretty tied to FILE* (although if you were on Linux a version using fmemopen and in-memory buffers could probably be cooked up quite easily).

C++ gdi::Bitmap to PNG Image in memory

Gdiplus can save to file, or save to memory using IStream. See Gdiplus::Image::Save method

//get gdi+ bitmap
Gdiplus::Bitmap bitmap(hbitmap, nullptr);

//write to IStream
IStream* istream = nullptr;
HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, &istream);
CLSID clsid_png;
CLSIDFromString(L"{557cf406-1a04-11d3-9a73-0000f81ef32e}", &clsid_png);
bitmap.Save(istream, &clsid_png);

The memory size is small enough that you can copy from IStream to a single buffer (see "Minimum example" for more detail)

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

//copy IStream to buffer
int bufsize = GlobalSize(hg);
char *buffer = new char[bufsize];

//lock & unlock memory
LPVOID ptr = GlobalLock(hg);
memcpy(buffer, ptr, bufsize);
GlobalUnlock(hg);

//release will automatically free the memory allocated in CreateStreamOnHGlobal
istream->Release();

PNG is now available in buffer, its size is bufsize. You can work directly with the binary data, or convert to Base64 to send over the network.

Minimum example:

#include <iostream>
#include <fstream>
#include <vector>
#include <Windows.h>
#include <gdiplus.h>

bool save_png_memory(HBITMAP hbitmap, std::vector<BYTE>& data)
{
Gdiplus::Bitmap bmp(hbitmap, nullptr);

//write to IStream
IStream* istream = nullptr;
if (CreateStreamOnHGlobal(NULL, TRUE, &istream) != 0)
return false;

CLSID clsid_png;
if (CLSIDFromString(L"{557cf406-1a04-11d3-9a73-0000f81ef32e}", &clsid_png)!=0)
return false;
Gdiplus::Status status = bmp.Save(istream, &clsid_png);
if (status != Gdiplus::Status::Ok)
return false;

//get memory handle associated with istream
HGLOBAL hg = NULL;
if (GetHGlobalFromStream(istream, &hg) != S_OK)
return 0;

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

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

istream->Release();
return true;
}

int main()
{
CoInitialize(NULL);

ULONG_PTR token;
Gdiplus::GdiplusStartupInput tmp;
Gdiplus::GdiplusStartup(&token, &tmp, NULL);

//take screenshot
RECT rc;
GetClientRect(GetDesktopWindow(), &rc);
auto hdc = GetDC(0);
auto memdc = CreateCompatibleDC(hdc);
auto hbitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
auto oldbmp = SelectObject(memdc, hbitmap);
BitBlt(memdc, 0, 0, rc.right, rc.bottom, hdc, 0, 0, SRCCOPY);
SelectObject(memdc, oldbmp);
DeleteDC(memdc);
ReleaseDC(0, hdc);

//save as png
std::vector<BYTE> data;
if(save_png_memory(hbitmap, data))
{
//write from memory to file for testing:
std::ofstream fout("test.png", std::ios::binary);
fout.write((char*)data.data(), data.size());
}
DeleteObject(hbitmap);

Gdiplus::GdiplusShutdown(token);
CoUninitialize();

return 0;
}

Converting a bitmap (bmp) to png with transparency (Windows c++)

This should do:

void CImageCopy(CImage& dest, CImage& src)
{
// rescale user image
dest.Destroy();
dest.Create(src.GetWidth(), src.GetHeight(), 32, CImage::createAlphaChannel);
HDC hdc = dest.GetDC();

src.AlphaBlend(hdc, 0,0,src.GetWidth(), src.GetHeight(), 0,0,src.GetWidth(),src.GetHeight());
dest.ReleaseDC();
}

void Fix24BitTransparency(CImage& img)
{
if(img.GetBPP()<32 )
{
// alpha bits
CImage imgout;
imgout.Create(img.GetWidth(), img.GetHeight(), 32, CImage::createAlphaChannel);

for(int x=0; x<img.GetWidth(); ++x)
for(int y=0; y<img.GetHeight(); ++y)
{
COLORREF c1;
c1 = img.GetPixel(x,y); // user image
imgout.SetPixel(x,y,c1);
if( c1 == RGB(255,0,128) ) // or whatever you decide transparent...
{
imgout.SetPixel(x,y, RGB(255,255,255) );
BYTE* pAlpha = (BYTE*)imgout.GetPixelAddress(x, y)+3;
*pAlpha = 0;
}
else
{
BYTE* pAlpha = (BYTE*)imgout.GetPixelAddress(x, y)+3;
*pAlpha = 255;
}
}
CImageCopy(img, imgout);
}
}

Convert BITMAP to PNG using IImage interface on Windows Mobile

Use CreateImageFromStream to read in your BITMAP data, that gives you an IImage.

Edit:

I did a little more research on this. There are a couple paths, but I think the easiest is to:

  1. create a DIBSECTION and blit your bitmap to it.
  2. Create a BitmapData instance pointing to the DIBSECTION for the image data.
  3. Call CreateBitmapFromBuffer to generate an IBitmapImage interface
  4. Push the IBitmapImage (which is an IImage) through your encoder.

Graphic bug when writing PNG from BITMAP in memory

I figured it out:

biBitCount is indeed 24

oBitmapInfoHeader.biBitCount = 24;// bitmap.bmPlanes * bitmap.bmBitsPixel;

GetDIBits failed because it started a bit too far

(LPBYTE)poBitmapInfoHeader + (oBitmapInfoHeader.biSize + iColors * 3),//sizeof(RGBQUAD)),   // address for bitmap bits

Then a stride with 3bpp works

const uint32_t stride = (width * 3);

Read a PNG Using Win32 / C++

Or of course you could use GDI+ - The Bitmap object takes a string constructor and loads the specified file into memory. That way you don't need any external libraries, just that which comes with windows.

Creating GDI+ bitmaps in memory and then saving as png

The core issue is passing the wrong pixel format to the Bitmap constructor. PixelFormatCanonical is not one of the supported pixel formats. It's a bit mask used to determine whether a pixel format is canonical (see IsCanonicalPixelFormat). You'll have to use a real pixel format, like the default PixelFormat32bppARGB.

The following code produces the desired output:

First up, a small helper class for GDI+ initialization. This ensures, that the d'tor (i.e. the call to GdiplusShutdown) is executed after all other objects have been destroyed. With respect to order of destruction, it serves the same purpose as the additional scope in the OP. In addition, it also allows for exceptions to be thrown.

#include <windows.h>
#include <gdiplus.h>
using namespace Gdiplus;
#include <stdexcept>
using std::runtime_error;

struct GdiplusInit {
GdiplusInit() {
GdiplusStartupInput inp;
GdiplusStartupOutput outp;
if ( Ok != GdiplusStartup( &token_, &inp, &outp ) )
throw runtime_error( "GdiplusStartup" );
}
~GdiplusInit() {
GdiplusShutdown( token_ );
}
private:
ULONG_PTR token_;
};

This code was taken from the MSDN sample Retrieving the Class Identifier for an Encoder.

int GetEncoderClsid( const WCHAR* format, CLSID* pClsid )
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes

ImageCodecInfo* pImageCodecInfo = NULL;

GetImageEncodersSize( &num, &size );
if ( size == 0 )
return -1; // Failure

pImageCodecInfo = (ImageCodecInfo*)( malloc( size ) );
if ( pImageCodecInfo == NULL )
return -1; // Failure

GetImageEncoders( num, size, pImageCodecInfo );

for ( UINT j = 0; j < num; ++j )
{
if ( wcscmp( pImageCodecInfo[j].MimeType, format ) == 0 )
{
*pClsid = pImageCodecInfo[j].Clsid;
free( pImageCodecInfo );
return j; // Success
}
}

free( pImageCodecInfo );
return -1; // Failure
}

Finally, the GDI+ rendering code. It uses objects with automatic storage duration throughout, making it more compact and safer.

void drawImage( int width, int height ) {
GdiplusInit gdiplusinit;

//Create a bitmap
Bitmap myBitmap( width, height, PixelFormat32bppARGB );
Graphics g( &myBitmap );
Pen blackpen( Color( 255, 0, 0, 0 ), 3 );

//draw on bitmap
g.DrawLine( &blackpen, 1, 1, 200, 200 );

// Save bitmap (as a png)
CLSID pngClsid;
int result = GetEncoderClsid( L"image/png", &pngClsid );
if ( result == -1 )
throw runtime_error( "GetEncoderClsid" );
if ( Ok != myBitmap.Save( L"C:\\test\\test.png", &pngClsid, NULL ) )
throw runtime_error( "Bitmap::Save" );
}

int main()
{
drawImage( 200, 200 );
return 0;
}

Note: It looks like GetEncoderClsid shouldn't be required, since those are well-known constants. However, trying to pass the appropriate WIC CLSID (CLSID_WICPngEncoder) to Bitmap::Save only produced a FileNotFound error.

Converting a screenshot bitmap to jpeg in memory

So Dan pointed out that there is actually a method Save that can use a Stream. I've decided to use this approach, which I got from one of the references listed in my question

vector<BYTE> buf;
IStream *stream = NULL;
HRESULT hr = CreateStreamOnHGlobal(0, TRUE, &stream);
CImage image;
ULARGE_INTEGER liSize;

// screenshot to jpg and save to stream
image.Attach(hBitmap);
image.Save(stream, Gdiplus::ImageFormatJPEG);
IStream_Size(stream, &liSize);
DWORD len = liSize.LowPart;
IStream_Reset(stream);
buf.resize(len);
IStream_Read(stream, &buf[0], len);
stream->Release();

// just testing if the buf contains the correct data
fstream fi;
fi.open("aaashot.jpg", fstream::binary | fstream::out);
fi.write(reinterpret_cast<const char*>(&buf[0]), buf.size()*sizeof(BYTE));
fi.close();

Loading an image from resource and converting to bitmap in memory

I ended up using PicoPNG to convert the PNG to a two dimensional vector which I then manually contructed a bitmap from. My final code looked like this:

HBITMAP LoadPNGasBMP(const HMODULE hModule, const LPCTSTR lpPNGName)
{
/* First we need to get an pointer to the PNG */
HRSRC found = FindResource(hModule, lpPNGName, "PNG");
unsigned int size = SizeofResource(hModule, found);
HGLOBAL loaded = LoadResource(hModule, found);
void* resource_data = LockResource(loaded);

/* Now we decode the PNG */
vector<unsigned char> raw;
unsigned long width, height;
int err = decodePNG(raw, width, height, (const unsigned char*)resource_data, size);
if (err != 0)
{
log_debug("Error while decoding png splash: %d", err);
return NULL;
}

/* Create the bitmap */
BITMAPV5HEADER bmpheader = {0};
bmpheader.bV5Size = sizeof(BITMAPV5HEADER);
bmpheader.bV5Width = width;
bmpheader.bV5Height = height;
bmpheader.bV5Planes = 1;
bmpheader.bV5BitCount = 32;
bmpheader.bV5Compression = BI_BITFIELDS;
bmpheader.bV5SizeImage = width*height*4;
bmpheader.bV5RedMask = 0x00FF0000;
bmpheader.bV5GreenMask = 0x0000FF00;
bmpheader.bV5BlueMask = 0x000000FF;
bmpheader.bV5AlphaMask = 0xFF000000;
bmpheader.bV5CSType = LCS_WINDOWS_COLOR_SPACE;
bmpheader.bV5Intent = LCS_GM_BUSINESS;
void* converted = NULL;
HDC screen = GetDC(NULL);
HBITMAP result = CreateDIBSection(screen, reinterpret_cast<BITMAPINFO*>(&bmpheader), DIB_RGB_COLORS, &converted, NULL, 0);
ReleaseDC(NULL, screen);

/* Copy the decoded image into the bitmap in the correct order */
for (unsigned int y1 = height - 1, y2 = 0; y2 < height; y1--, y2++)
for (unsigned int x = 0; x < width; x++)
{
*((char*)converted+0+4*x+4*width*y2) = raw[2+4*x+4*width*y1]; // Blue
*((char*)converted+1+4*x+4*width*y2) = raw[1+4*x+4*width*y1]; // Green
*((char*)converted+2+4*x+4*width*y2) = raw[0+4*x+4*width*y1]; // Red
*((char*)converted+3+4*x+4*width*y2) = raw[3+4*x+4*width*y1]; // Alpha
}

/* Done! */
return result;
}


Related Topics



Leave a reply



Submit