Creating GDI+ bitmaps in memory and then saving as png - c++

I am new to C++ and been having trouble with writing a function using the GDI+ library to create a new bitmap in memory ( so not opening/reading an existing bitmap); then drawing on the bitmap; before saving it to png. In particular, I am having problems with the bitmap creation and saving code. I am constrained to using codeblocks and I can't use visual studios, even if I wanted to. The code is as follows:
#include "drawImage.h"
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
#include <stdio.h>
#include <iostream>
using namespace std;
using namespace Gdiplus;
drawImage::drawImage(){}
void drawImage::DrawBitmap(int width, int height){
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
{
//Create a bitmap
Bitmap myBitmap(width, height, PixelFormatCanonical);
Graphics g(&myBitmap);
Pen blackpen(Color(255,0,0,0), 3);
//draw on bitmap
int x1 = 1;
int x2 = 200;
int y1 = 1;
int y2 = 200;
g.DrawLine(&blackpen, x1,y1,x2,y2);
// Save bitmap (as a png)
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
myBitmap.Save(L"C:\\test\\test.png", &pngClsid, NULL);
}
GdiplusShutdown(gdiplusToken);
}
The issues I am having are as follows:
The 'saving' code does not compile and gives the error message "'GetEncoderClsid' was not declared in this scope". However, I got this direct from the Microsoft website here. I don't think this is the proper way of converting to png but I dont know an alternative way?
When the code is compiled and run (by commenting out the saving code), it then crashes on the line "Bitmap *myBitmap = new Bitmap(width, height, PixelFormatCanonical);" and gives an error message saying my executable has stopped working.
I have added the 'gdi32' linker library and also '-lgdiplus' as a linker option. Also, I have used this website to help with the gdi stuff although the section on bitmaps only deals with loading existing bitmaps (not creating new ones in memory)
I am totally lost on what to do, so any help or advice on this matter is much appreciated.

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.

For 1.: GetEncoderClsid is not a library function, it is an example helper defined here. If you want to use it copy the code from the site. (Btw your link explicitly states this.)
For 2.: You need to call GdiplusStartup before creating any GDI+ object or calling any GDI+ function. See its documentation here.
To be more precise about GdiplusShutdown because that seems to be making you new problems: It is required that all GDI+ objects are destroyed before GdiplusShutdown is called. That means that objects with static storage (for example pen) must go out of scope before GdiplusShutdown is called. That is not the case if you call it in DrawBitmap in the way you do now. Either call GdiplusStartup and GdiplusShutdown in main or add additional brackets {} around your code between GdiplusStartup and GdiplusShutdown, because these introduce a new scope and pen would be destroyed when } is reached.
Edit: Nr. 2 was fixed in the question by edit. For the remaining problem and improvement of the code see #IInspectable's answer.

Related

C++ Memory management failing when deleting Bitmap and CLSID objects using GDI+

I am unable to manage memory for Bitmap and CLSID objects I have created in a screenshot object class. Both of these are from the GDI+ library. The header lists the following private variables in Screenshot.h
#include <gdiplus.h>
#include <iostream>
#include <fstream>
#include <string>
#include "windows.h"
#pragma once
#pragma comment(lib, "gdiplus.lib")
using namespace std;
using namespace Gdiplus;
class Screenshot
{
private:
HDC dc, memdc, fontdc;
HBITMAP membit;
Bitmap* bmpPtr;
CLSID clsid;
ULONG_PTR gdiplusToken;
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid);
public:
Screenshot();
~Screenshot();
void TakeScreenshot(string userAction, string winName, long xMousePos, long yMousePos, long long tStamp);
void SaveScreenshot(string filename);
void memoryManagement();
};
Then when my main program takes a screenshot, the values are filled in with TakeScreenshot(), but not yet saved to disk
void Screenshot::TakeScreenshot(//redacted for readibility) {
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
HWND hwnd = GetDesktopWindow();
dc = ::GetDC(0);
int scaleHeight, scaleWidth = 0;
int Height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int Width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
scaleHeight = Height + (0.1 * Height);
memdc = CreateCompatibleDC(dc);
membit = CreateCompatibleBitmap(dc, Width, scaleHeight);
HBITMAP bmpContainer = (HBITMAP)SelectObject(memdc, membit);
BitBlt(memdc, 0, 0, Width, Height, dc, 0, 0, SRCCOPY);
//Other code that adds fonts, etc. Does not invoke bmpPtr
bmpPtr = new Bitmap(membit, NULL);
GetEncoderClsid(L"image/jpeg", &clsid);
If the screenshot is saved, another function SaveScreenshot() uses bmpPtr->Save() and Gdiplus shutdown is called inside of it. However, some of the screenshots get popped off from a queue (STL queue) and out of memory instead of saved, as follows:
void ManageQueue(Screenshot& ssObj)
{
//If queue contains 30 screenshots, pop off first element and push new object
//Else just push new object
if (screenshotQueue.size() == MAX_SCREENSHOTS)
{
screenshotQueue.front().memoryManagement();
screenshotQueue.pop();
screenshotQueue.push(ssObj);
}
else
{
screenshotQueue.push(ssObj);
}
}
I wrote a MemoryManagement() function to perform the necessary releases and deletes before the Screenshot is popped off. This function is not called if the screenshot has been saved:
void Screenshot::memoryManagement()
{
delete bmpPtr;
delete &clsid;
ReleaseDC(NULL, memdc);
DeleteObject(fontdc);
DeleteObject(memdc);
DeleteObject(membit);
}
When either the delete on bmpPtr or clsid is called, whether it is from this function or in the deconstructor, the program is crashing. I am experiencing significant memory leaks with the program now and without running a windows equivalent of Valgrind I'm assuming it's coming from here. How can I successfully delete these objects? I will credit any answer in my source code as a contributing programmer. Please leave any suggestions for improving my question if needed.
scaleHeight = Height + (0.1 * Height);
This seems to be an attempt to fix the problem with DPI scaling. It will work if DPI settings is at 10%, but that's usually not the case. You have to make your program DPI aware through the manifest file. Use SetProcessDPIAware for a quick fix.
Don't declare dc, memdc, etc. as class members. These are GDI handles (not GDI+) which you can hold for a short time, usually during the duration of the function. You have to release them as soon as possible.
Also other variables like clsid don't need to be declared as class members. You can declare them as class member if you wish, but there is nothing to gain.
If you have a multi-monitor setup you also need SM_XVIRTUALSCREEN/Y to get the top-left corner of the monitor setup.
//call this once on start up
SetProcessDPIAware();
HDC dc = ::GetDC(0);
int x = GetSystemMetrics(SM_XVIRTUALSCREEN);
int y = GetSystemMetrics(SM_YVIRTUALSCREEN);
int Height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int Width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
HDC memdc = CreateCompatibleDC(dc);
HBITMAP membit = CreateCompatibleBitmap(dc, Width, Height);
HBITMAP bmpContainer = (HBITMAP)SelectObject(memdc, membit);
BitBlt(memdc, 0, 0, Width, Height, dc, x, y, SRCCOPY);
Bitmap* bmpPtr = new Bitmap(membit, NULL);
// or just Bitmap bmp(membit, NULL);
CLSID clsid;
GetEncoderClsid(L"image/jpeg", &clsid);
bmpPtr->Save(L"output.jpg", &clsid);
//cleanup:
delete bmpPtr;
SelectObject(memdc, bmpContainer);
DeleteObject(membit);
DeleteDC(memdc);
ReleaseDC(0, dc);
The solution for this problem was to use the namespace delete instead of regular delete. Switching to this prevented the break point trigger during debug and has sealed the memory leak.
void Screenshot::memoryManagement()
{
::delete bmpPtr;
ReleaseDC(NULL, memdc);
DeleteObject(fontdc);
DeleteObject(memdc);
DeleteObject(membit);
GdiplusShutdown(gdiplusToken);
}

save jpeg from hBitmap to BYTE array [duplicate]

I've got a HBITMAP that I want to save into a JPEG/PNG stream or array of bytes. The problem is that I'm using mingw as my compiler so I can't use CImage.. which would have made my life easier.
I can get the pixels from the bitmap without any problems, but I have no idea how to get access to them in JPEG/PNG-format.
Where do I start?
If you have access DirectX library you may use IStream to convert your image to JPEG
http://msdn.microsoft.com/en-us/library/windows/desktop/aa380034(v=vs.85).aspx
or if you have GDI+ something like this might work
Gdiplus::Bitmap bmp(hbmpImage,(HPALETTE)0);
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
bmp.Save(L"D:\image.png",&pngClsid,NULL);
where GetEncoderLCLsid looks like this:
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
}
don't forget to initialize GDI+
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
if don't have access to either you may use libjpeg but you need to put all the dependencies packages from the GnuWin32 site. Much faster the code in this page should work, just forget about the boost
libjpeg dying without message
Another option is to use the WIC API (Windows Imaging Component), which gives direct access to image encoders and decoders. I believe GDI+ may use this under the covers.

HBITMAP to JPEG /PNG without CImage in C++

I've got a HBITMAP that I want to save into a JPEG/PNG stream or array of bytes. The problem is that I'm using mingw as my compiler so I can't use CImage.. which would have made my life easier.
I can get the pixels from the bitmap without any problems, but I have no idea how to get access to them in JPEG/PNG-format.
Where do I start?
If you have access DirectX library you may use IStream to convert your image to JPEG
http://msdn.microsoft.com/en-us/library/windows/desktop/aa380034(v=vs.85).aspx
or if you have GDI+ something like this might work
Gdiplus::Bitmap bmp(hbmpImage,(HPALETTE)0);
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
bmp.Save(L"D:\image.png",&pngClsid,NULL);
where GetEncoderLCLsid looks like this:
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
}
don't forget to initialize GDI+
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
if don't have access to either you may use libjpeg but you need to put all the dependencies packages from the GnuWin32 site. Much faster the code in this page should work, just forget about the boost
libjpeg dying without message
Another option is to use the WIC API (Windows Imaging Component), which gives direct access to image encoders and decoders. I believe GDI+ may use this under the covers.

WIC Direct2D CreateBitmapFromMemory: limitations on width and height?

CreateBitmapFromMemory executes successfully when _nWidth is equal to or less than 644.
If the value exceeds this value, the HRESULT value is -2003292276
Do limits exist on the width and height?
#include <d2d1.h>
#include <d2d1helper.h>
#include <wincodecsdk.h> // Use this for WIC Direct2D functions
void test()
{
IWICImagingFactory *m_pIWICFactory;
ID2D1Factory *m_pD2DFactory;
IWICBitmap *m_pEmbeddedBitmap;
ID2D1Bitmap *m_pD2DBitmap;
unsigned char *pImageBuffer = new unsigned char[1024*1024];
HRESULT hr = S_OK;
int _nHeight = 300;
int _nWidth = 644;
If nWidth exceeds 644, CreateBitmapFromMemory returns an Error.
//_nWidth = 648;
if (m_pIWICFactory == 0 )
{
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
// Create WIC factory
hr = CoCreateInstance(
CLSID_WICImagingFactory,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&m_pIWICFactory)
);
if (SUCCEEDED(hr))
{
// Create D2D factory
hr = D2D1CreateFactory( D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory );
}
}
hr = m_pIWICFactory->CreateBitmapFromMemory(
_nHeight, // height
_nWidth, // width
GUID_WICPixelFormat24bppRGB, // pixel format of the NEW bitmap
_nWidth*3, // calculated from width and bpp information
1024*1024, // height x width
pImageBuffer, // name of the .c array
&m_pEmbeddedBitmap // pointer to pointer to whatever an IWICBitmap is.
);
if (!SUCCEEDED(hr)) {
char *buffer = "Error in CreateBitmapFromMemory\n";
}
}
Error code is 0x88982F8C WINCODEC_ERR_INSUFFICIENTBUFFER and the reason is now obvious?
The first parameter is width, and the second is height. You have them in wrong order. All in all you provide incorrect arguments resulting in bad buffer.
Are you sure you passed in the correct pixelFormat for function CreateBitmapFromMemory? you hard code it to GUID_WICPixelFormat24bppRGB, I think this is the root cause, you should make sure this format same as the format with the source bitmap which you are copy the data from. try use the GetPixelFormat function to get the correct format instead of hard code.
There is an upper limit on the dimensions of images on the GPU.
Call GetMaximumBitmapSize on the render target.
http://msdn.microsoft.com/query/dev11.query?appId=Dev11IDEF1&l=EN-US&k=k(GetMaximumBitmapSize);k(DevLang-C%2B%2B);k(TargetOS-Windows)&rd=true
What you get back is the max pixels of either vertical or horiz.
For larger images you'd have to load them into a software render target such as a bitmap render target and then render what you want from that.

Shrink the bitmap to required dimension

I have a bitmap of large dimension (2000 x 2000) i need to shrink that bitmap to a small dimension (150 x 150). i have written a code for it, but its not working. Can anybody help in finding the problem? The problem is the destination bitmap is just blank. I am selecting wrong DC's? I have made sure that both the source and destinations are correct. After doing bitblt do i need to do some more thing to destination bitmap?
BOOL ReSizeBitmap(CBitmap *pBitmap, CBitmap *pNewBitmap)
{
// Get new bitmap size
BITMAP bmOld;
if( !pBitmap->GetBitmap(&bmOld) )
{
return FALSE;
}
CRect rcPrev(0, 0, bmOld.bmWidth, bmOld.bmHeight);
int newWidth = 150;
int newHeight = 150;
if( newWidth < 1 || newHeight < 1 )
{
::SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
BOOL bResult = FALSE;
try
{
CDC dcDest;
CDC dcSource;
dcSource.CreateCompatibleDC(NULL);
dcDest.CreateCompatibleDC(NULL);
CBitmap* pSourceOld = dcSource.SelectObject(pBitmap);
CBitmap* pDestold = dcDest.SelectObject(pNewBitmap);
if( !pNewBitmap->CreateCompatibleBitmap(
&dcDest, newWidth, newHeight) )
{
return FALSE;
}
int oldStretchMode = dcDest.SetStretchBltMode(HALFTONE);
bResult = dcDest.StretchBlt(
0, 0, 150, 150,
&dcSource, 0, 0, bmOld.bmWidth, bmOld.bmHeight,
SRCCOPY);
dcDest.SetStretchBltMode(oldStretchMode);
dcSource.SelectObject(pSourceOld);
dcDest.SelectObject(pDestold);
bResult = TRUE;
}
catch(CResourceException* /*e*/)
{
}
return bResult;
}
Well even if the code works there is some cleaniing up to do.
RAII is one of the idiom you realy need when you are working in MFC!
if( !pNewBitmap->CreateCompatibleBitmap(&dcDest, newWidth, newHeight) )
{
return FALSE;
}
When you return FALSE or there is an exception you haven't called
cSource.SelectObject(pSourceOld);
dcDest.SelectObject(pDestold);
to cleanup before you left the function.
Create a small helper class to cleanup all the time, you don't have to worry about return or throw statements.
class SelectObjectAndCleanUp
{
CDC& deviceContext;
CBitmap *const oldSource;
public:
SelectObjectCleanUp( CDC& deviceContext, CBitmap* source )
: deviceContext(deviceContext),
oldSource( deviceContext.SelectObject(source) ) {
}
~SelectObjectCleanUp() {
deviceContext.SelectObject(oldSource)
}
};
// use of the helper
SelectObjectCleanUp sourceSelectionAndCleanup(dcSource, pBitmap );
SelectObjectCleanUp destionationSelectionAndCleanup(dcDest, pNewBitmap );
I'm not too familiar with C++, but are you selecting the new bitmap into your new DC before it's created? Also, when you call CreateCompatibleBitmap, I think you want to use your screen DC (the one you used to create the destination DC), not your compatible memory DC. So, get the screen DC with GetDC, and pass that into both CreateCompatibleDC AND CreateCompatibleBitmap.
There is a great free C++ image library called CxImage that is open source under the zlib license.
No need to reinvent the wheel.
Download http://www.gdiwatch.com/, it may show you where the error is (it can be made to work with vs 2008, too - just copy the registry keys manually from the vs2005 to vs2008 directories)
Have you tried doing the CreateCompatibleBitmap() before the SelectObject() call?
Does GetBitmap() of the new bitmap return the correct size, i.e. is the new bitmap valid?
The rest seem ok, it should work like this I believe. Does it work with other StretchBltModes?
Thanks to all of the guys who peeped and suggested solutions, any how after some debugging i found the problem. Here is the solution!!!
CBitmap *SrcBmp;
HBITMAP hBmp;
hBmp= (HBITMAP)LoadImage( NULL, L"c:\\source.bmp", IMAGE_BITMAP, 0,0, LR_LOADFROMFILE );
SrcBmp = CBitmap::FromHandle(hBmp);
BITMAP BmpInfo;
SrcBmp->GetBitmap(&BmpInfo);
CDC SrcDC;
SrcDC.CreateCompatibleDC(NULL);
CBitmap DestBmp;
DestBmp.CreateCompatibleBitmap(&SrcDC,150,150);
CDC DestDC;
DestDC.CreateCompatibleDC(NULL);
CBitmap *pOldBmp1 = SrcDC.SelectObject(SrcBmp);
CBitmap *pOldBmp2 = DestDC.SelectObject(&DestBmp);
DestDC.StretchBlt(0,0,150,150,&SrcDC,0,0,BmpInfo.bmWidth,BmpInfo.bmHeight,SRCCOPY);
CImage image;
image.Attach(DestBmp);
image.Save(_T("C:\\test.bmp"), Gdiplus::ImageFormatBMP);
SrcDC.SelectObject(pOldBmp1);
DestDC.SelectObject(pOldBmp2);