C++ Gdi::Bitmap to Png Image in Memory

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;
}

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

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.

C++ GDI+ how to get and load image from resource?

TLDR: FindResource, LoadResource, and LockResource to get a pointer to the image bytes. Then make an IStream from it. The IStream can be used to initialize a Gdi+ Image or Bitmap object. (Gdiplus::Bitmap derives from Gdiplus::Image)

Stick a new line into your .rc file:

IDI_MY_IMAGE_FILE    PNG      "foo.png"

And make sure IDI_MY_IMAGE_FILE is defined as an integer in your resource.h header file.

#define IDI_MY_IMAGE_FILE               131

Then to load the image at runtime:

Gdiplus::Bitmap* pBmp = LoadImageFromResource(hInstance, MAKEINTRESOURCE(IDI_MY_IMAGE_FILE), L"PNG");

Where LoadImageFromResource is a helper function that does all the heavy work of loading a PNG from your application resources.

Gdiplus::Bitmap* LoadImageFromResource(HMODULE hMod, const wchar_t* resid, const wchar_t* restype)
{
IStream* pStream = nullptr;
Gdiplus::Bitmap* pBmp = nullptr;
HGLOBAL hGlobal = nullptr;

HRSRC hrsrc = FindResourceW(hInst, resid, restype); // get the handle to the resource
if (hrsrc)
{
DWORD dwResourceSize = SizeofResource(hMod, hrsrc);
if (dwResourceSize > 0)
{
HGLOBAL hGlobalResource = LoadResource(hMod, hrsrc); // load it
if (hGlobalResource)
{
void* imagebytes = LockResource(hGlobalResource); // get a pointer to the file bytes

// copy image bytes into a real hglobal memory handle
hGlobal = ::GlobalAlloc(GHND, dwResourceSize);
if (hGlobal)
{
void* pBuffer = ::GlobalLock(hGlobal);
if (pBuffer)
{
memcpy(pBuffer, imagebytes, dwResourceSize);
HRESULT hr = CreateStreamOnHGlobal(hGlobal, TRUE, &pStream);
if (SUCCEEDED(hr))
{
// pStream now owns the global handle and will invoke GlobalFree on release
hGlobal = nullptr;
pBmp = new Gdiplus::Bitmap(pStream);
}
}
}
}
}
}

if (pStream)
{
pStream->Release();
pStream = nullptr;
}

if (hGlobal)
{
GlobalFree(hGlobal);
hGlobal = nullptr;
}

return pBmp;
}

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);
}
}

Can we use png instead bitmap in GDI for bitblt?

No, you can't BitBlt with a PNG. BitBlt (which stands for "bit block transfer", by the way) is very fast, but it is basically just a simple memory copying routine. So the only way to BitBlt any image format other than an uncompressed bitmap is to first convert that format to a bitmap.

By the way, applications typically use bitmaps for animation because you don't want to increase your overall frame rendering time by having to uncompress your sprite images each time. Bitmaps are just an example of generally caching things in memory to improve performance.

How to convert a image file loaded in memory to a ID2D1Bitmap in C++

if you have the HBITMAP handle, you can do this:

  1. The the size of your image using: ::GetObject(hBmp, sizeof(BITMAP), &bmpSizeInfo);
  2. fill a BITMAPINFO like this:

    memset(&bmpData, 0, sizeof(BITMAPINFO));
    bmpData.bmiHeader.biSize = sizeof(bmpData.bmiHeader);
    bmpData.bmiHeader.biHeight = -bmpSizeInfo.bmHeight;
    bmpData.bmiHeader.biWidth = bmpSizeInfo.bmWidth;
    bmpData.bmiHeader.biPlanes = bmpSizeInfo.bmPlanes;
    bmpData.bmiHeader.biBitCount = bmpSizeInfo.bmBitsPixel;

  3. create enough heap memory to hold the data for your bitmap:

    pBuff = new char[bmpSizeInfo.bmWidth * bmpSizeInfo.bmHeight * 4];

  4. Get the bitmap data like this:

    ::GetDIBits(hDc, hBmp, 0, bmpSizeInfo.bmHeight, (void*)pBuff, &bmpData, DIB_RGB_COLORS);

  5. Create a D2D1_BITMAP_PROPERTIES and fill it like this:

    bmpPorp.dpiX = 0.0f;
    bmpPorp.dpiY = 0.0f;
    bmpPorp.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
    bmpPorp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE;

  6. Using your render target turn the data into ID2D1Bitmap

    pRT->CreateBitmap(bmpSize, pBuff, 4 * bmpSizeInfo.bmWidth, bmpPorp, &pBmpFromH);

Hope this can help.

Sam

Create transparent bitmap with Gdiplus in C++

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;
}


Related Topics



Leave a reply



Submit