I'm attempting to draw an image using StretchDIBits with an Enhanced Meta File handle as the HDC to my OnDraw function using C++ and Visual Studio 2008. I load the .bmp source file using GDIplus and using GetHBITMAP.
If I use the HDC returned by BeginPaint there are no problems. If I draw the bitmap upright, flipped horizontally, or flipped both ways, no problems.
HOWEVER, when I attempt to draw the bitmap upside down, to the EMF file I get an unhandled exception, a scrambled stack, and I can't find any evidence of memory corruption. If I comment out the call to StretchDIBits which crashes, then again everything runs fine regardless of HDC pointing to the display or the EMF.
I have verified that it does not matter if the EMF file is output on the first or second call to OnDraw. I have verified that I am extracting the GDI bitmap data and header from the GDIPlus bitmap correctly. I can draw 3 of the 4 orientations. I cannot even draw subsets of the image upside down unless I also flip the image horizontally.
I've looked for memory corruption in my data structures, I've checked that the bitmap bits is aligned on a DWORD. No GDI resource leaks.
Suggestions?
-Jason
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <assert.h>
#include <vector>
#include <gdiplus.h>
class KDIB
{
void * m_pBits;
Gdiplus::Bitmap * m_bitmap;
HBITMAP m_hbitmap;
BITMAPINFO * m_bitmapInfo;
public:
~KDIB()
{
delete [] m_pBits;
}
KDIB()
{
m_bitmap = NULL;
m_pBits = NULL;
m_bitmapInfo = NULL;
}
void Load(HDC hDC, WCHAR *filename )
{
if( m_pBits )
return;
m_bitmap = new Gdiplus::Bitmap(filename);
assert( m_bitmap );
Gdiplus::Status status;
status = m_bitmap->GetHBITMAP(Gdiplus::Color(), &m_hbitmap );
assert(status == Gdiplus::Ok);
// get the size of the bitmap info
BITMAPINFO tmpBitmapInfo;
tmpBitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
tmpBitmapInfo.bmiHeader.biBitCount = 0;
tmpBitmapInfo.bmiHeader.biClrImportant = 0;
tmpBitmapInfo.bmiHeader.biClrUsed = 0;
tmpBitmapInfo.bmiHeader.biCompression = 0;
tmpBitmapInfo.bmiHeader.biHeight = 0;
tmpBitmapInfo.bmiHeader.biPlanes = 0;
tmpBitmapInfo.bmiHeader.biSizeImage = 0;
tmpBitmapInfo.bmiHeader.biWidth = 0;
tmpBitmapInfo.bmiHeader.biXPelsPerMeter = 0;
tmpBitmapInfo.bmiHeader.biYPelsPerMeter = 0;
int result = GetDIBits(hDC, m_hbitmap, 0, 0, NULL, &tmpBitmapInfo, DIB_RGB_COLORS );
assert(result);
assert(tmpBitmapInfo.bmiHeader.biSizeImage);
assert(tmpBitmapInfo.bmiHeader.biHeight);
assert(tmpBitmapInfo.bmiHeader.biHeight);
int nColors;
if( tmpBitmapInfo.bmiHeader.biClrUsed )
{
nColors = tmpBitmapInfo.bmiHeader.biClrUsed;
}
else
{
if( tmpBitmapInfo.bmiHeader.biBitCount == 0 )
nColors = 0;
else if( tmpBitmapInfo.bmiHeader.biBitCount == 1 )
nColors = 2;
else if( tmpBitmapInfo.bmiHeader.biBitCount == 4 )
nColors = 16;
else if( tmpBitmapInfo.bmiHeader.biBitCount == 16
|| tmpBitmapInfo.bmiHeader.biBitCount == 32)
{
if( tmpBitmapInfo.bmiHeader.biCompression == BI_RGB )
nColors = 0;
if( tmpBitmapInfo.bmiHeader.biCompression == BI_BITFIELDS )
nColors = 3;
else
assert(false);
}
else if( tmpBitmapInfo.bmiHeader.biBitCount == 24 )
nColors = 0;
else
assert(false);
}
// now allocate the correct amount of storage
m_bitmapInfo = reinterpret_cast<BITMAPINFO *>(new char[tmpBitmapInfo.bmiHeader.biSize + sizeof(RGBQUAD) * nColors]);
m_bitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
m_bitmapInfo->bmiHeader.biBitCount = 0;
m_bitmapInfo->bmiHeader.biClrImportant = 0;
m_bitmapInfo->bmiHeader.biClrUsed = 0;
m_bitmapInfo->bmiHeader.biCompression = 0;
m_bitmapInfo->bmiHeader.biHeight = 0;
m_bitmapInfo->bmiHeader.biPlanes = 0;
m_bitmapInfo->bmiHeader.biSizeImage = 0;
m_bitmapInfo->bmiHeader.biWidth = 0;
m_bitmapInfo->bmiHeader.biXPelsPerMeter = 0;
m_bitmapInfo->bmiHeader.biYPelsPerMeter = 0;
// fill the bitmapinfo structure
result = GetDIBits(hDC, m_hbitmap, 0, 0, NULL, m_bitmapInfo, DIB_RGB_COLORS );
// allocate and get the bitmap data
m_pBits = new char[(tmpBitmapInfo.bmiHeader.biSizeImage + 3) & ~3];
assert(m_pBits);
int numscanlines;
numscanlines = GetDIBits(hDC, m_hbitmap, 0, tmpBitmapInfo.bmiHeader.biHeight, m_pBits, m_bitmapInfo, DIB_RGB_COLORS );
assert(numscanlines);
}
// wrapper that uses internal data
int StretchDIBits(HDC hDC, int dx, int dy, int dw, int dh, int sx, int sy, int sw, int sh, int coloruse, DWORD rop)
{
int result = ::StretchDIBits(hDC, dx, dy, dw, dh, sx, sy, sw, sh,
m_pBits, m_bitmapInfo, coloruse, rop);
return result;
}
int GetHeight() {return m_bitmapInfo->bmiHeader.biHeight;}
int GetWidth() {return m_bitmapInfo->bmiHeader.biWidth;}
};
void OnDraw(HDC hDC)
{
LPCWSTR label;
HGDIOBJ hFont = NULL;
LONG xMin = 0;
LONG yMin = 0;
LONG xMax = 1000;
LONG yMax = 500;
SetGraphicsMode(hDC, GM_ADVANCED);
SetMapMode(hDC, MM_ANISOTROPIC);
SetViewportExtEx(hDC, xMax, yMax, NULL);
SetWindowOrgEx(hDC, 0, 0, NULL);
SetWindowExtEx(hDC, xMax, yMax, NULL);
SetTextAlign(hDC, TA_NOUPDATECP | TA_CENTER | TA_BASELINE);
SetBkMode(hDC, TRANSPARENT);
hFont = CreateFont(30,0,0,0,400,0,0,0,0,4,64,0,34,L"Calibri");
SelectObject(hDC, hFont);
SetTextColor(hDC, 0x2000000);
label = L"Test of flipping with StretchDIBits";
ExtTextOutW(hDC, xMax / 2, 40,ETO_CLIPPED,NULL,label, _tcslen(label),NULL);
static KDIB Dib_1;
Dib_1.Load(hDC, L"kde41.bmp");
LONG yOrg = 80;
LONG xOrg = 80;
LONG spacer = 16;
LONG w = Dib_1.GetWidth();
LONG h = Dib_1.GetHeight();
// These work fine:
Dib_1.StretchDIBits(hDC, xOrg + spacer, yOrg + spacer, w, h, 0, 0, w, h, DIB_RGB_COLORS, SRCCOPY);
//Dib_1.StretchDIBits(hDC, xOrg + spacer*2 + w, yOrg + spacer, w, h, w, 0, -w, h, DIB_RGB_COLORS, SRCCOPY);
//Dib_1.StretchDIBits(hDC, xOrg + spacer*2 + w, yOrg + spacer*2 + h, w, h, w, h, -w, -h, DIB_RGB_COLORS, SRCCOPY);
// CRASH when drawing bitmap upside down to EMF file
Dib_1.StretchDIBits(hDC, xOrg + spacer, yOrg + spacer*2 + h, w, h, 0, h, w, -h, DIB_RGB_COLORS, SRCCOPY);
DeleteObject(hFont);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch ( uMsg )
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hDC;
HDC metaFileHDC;
hDC = BeginPaint(hWnd, & ps);
static bool firstTime = true;
static bool secondTime = false;
if( firstTime )
{
metaFileHDC = hDC;
}
else
{
metaFileHDC = CreateEnhMetaFile( hDC, L"EmfStretchDIBits-flipping.emf", NULL, L"Test of flipping via StretchDIBits\0\0");
}
OnDraw(metaFileHDC);
if( secondTime )
{
CloseEnhMetaFile( metaFileHDC );
OnDraw(hDC);
secondTime = false;
}
if( firstTime )
{
secondTime = true;
firstTime = false;
}
EndPaint(hWnd, & ps);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int nCmdShow)
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
WNDCLASSEX wc;
memset(& wc, 0, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.lpfnWndProc = WndProc;
wc.hInstance = hInst;
wc.hbrBackground = (HBRUSH) GetStockObject(LTGRAY_BRUSH);
wc.lpszClassName = L"EMFDC";
if ( ! RegisterClassEx(& wc) )
return -1;
HWND hWnd = CreateWindowEx(0, L"EMFDC", L"StretchDIBits to EMF", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInst, NULL);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg;
while ( GetMessage(&msg, NULL, 0, 0) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Gdiplus::GdiplusShutdown(gdiplusToken);
return msg.wParam;
}