views:

433

answers:

0

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