0

A program I am modifying converts EMF files to a different format (SVG). It does this with EnumEnhMetaFile() which calls the program's parsing routine for each record of the EMF. That works great for the vector stuff. However, it also needs to be able to pull out a bitmap when it hits an EMR_STRETCHDIBITS. (The final target is actually a base64 encoded PNG, but the sticking point is grabbing the Bitmap.) Does Microsoft provide a function somewhere for pulling out an image at this point? The inverse operation, adding a bitmap to the EMF, is abitmap.Draw, what I need is some sort of abitmap.Read that can operate from within the EnumEnhMetaFile data processing.

Is there a function that converts from the data offset fields that EMR_STRETCHDIBITS provides to a Windows Bitmap? Note, I do not want to render the EMF into a bitmap, I want the original bitmap that is stored in the EMF.

Thanks.

mathog
  • 345
  • 3
  • 11

3 Answers3

0

No.

But here's a link to the code that Wine's PlayEnhMetaFile uses to draw the record: http://source.winehq.org/source/dlls/gdi32/enhmetafile.c?v=wine-1.5.2#L1111

It's surprisingly simple, and the StretchDIBits call has the bits, BITMAPINFO, and iUsage - all the information you would need to plug into CreateDIBSection and SetDIBits to get an HBITMAP. (If you used CreateDIBitmap, you could lose information in the process depending on the current display mode. Ugh. And you can't just copy the bits into the DIB directly because the bits might be RLE-compressed. SetDIBits will handle that properly.)

OK, so you do need to get the height to pass to SetDIBits. Pulling it out of BITMAPINFO is problematic because it may actually be a BITMAPCOREINFO which has a different structure. Probably easiest to create the HBITMAP first and then check its height.

I think that if DIB_PAL_COLORS is used, the palette will not be in BITMAPINFO but selected into the HDC by a previous record, so you'd better be playing those records that manipulate the DC and using the HDC you're given.

So, putting it all together, something like this (untested, lacking error checks) should work:

HBITMAP bitmap_from_stretchdibits(HDC hdc, const ENHMETARECORD *lpEMFR)
{
    const EMRSTRETCHDIBITS *pStretchDIBits = (const EMRSTRETCHDIBITS *)lpEMFR;
    BITMAP bm;
    HBITMAP hbm;

    hbm = CreateDIBitmap(
        hdc,
        (const BITMAPINFO *)((const BYTE *)lpEMFR + pStretchDIBits->offBmiSrc),
        pStretchDIBits->iUsageSrc,
        NULL,
        NULL,
        0);

    if (hbm)
    {
        GetObjectA(hbm, sizeof(bm), &bm);

        SetDIBits(
            hdc,
            hbm,
            1,
            abs(bm.bmHeight),
            (const BYTE *)lpEMFR + pStretchDIBits->offBitsSrc,
            (const BITMAPINFO *)((const BYTE *)lpEMFR + pStretchDIBits->offBmiSrc),
            pStretchDIBits->iUsageSrc);
    }

    return hbm;
}
Esme Povirk
  • 3,004
  • 16
  • 24
  • Ah, I see. Thanks. For my purposes then save the Bitmap after the GetObjects to a png file, like here: http://msdn.microsoft.com/en-us/library/ms533837%28v=vs.85%29.aspx then read it back in again and convert to base64 like here: http://msdn.microsoft.com/en-us/library/dhx0d524.aspx#Y570 Probably that can be done without going through an intermediate file, through a stream object. (Can't test anything on this computer.) – mathog Apr 16 '12 at 04:13
0

This works - but the PNG it saves is compressed.

        BITMAPINFO *pbitmapinfo = (BITMAPINFO *)((char *)lpEMFR + pEmr->offBmiSrc);
        void *pBitsInMem = (char *)lpEMFR + pEmr->offBitsSrc;
        HBITMAP hbm;
        HDC tmpDC = CreateDC("DISPLAY", "", NULL, NULL);
        hbm = CreateDIBitmap(
            tmpDC,
            &(pbitmapinfo->bmiHeader),
            CBM_INIT,
            pBitsInMem,
            pbitmapinfo,
            DIB_RGB_COLORS);
        if(hbm){
          Gdiplus::Bitmap *pbmp = NULL;
          pbmp = Gdiplus::Bitmap::FromHBITMAP(hbm,NULL);
          CLSID pngClsid;
          GetEncoderClsid(L"image/png", &pngClsid);

          pbmp->Save(L"C:\\Temp\\scratch.png",&pngClsid, NULL);
          delete pbmp;
       }
       (void) DeleteObject(hbm);
       (void) DeleteDC(tmpDC);
ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
mathog
  • 345
  • 3
  • 11
  • The DeleteObject(hbm) should be moved above the preceding bracket. – mathog Apr 16 '12 at 18:09
  • Added a BMP save with:
    
                  GetEncoderClsid(L"image/bmp", &pngClsid);
                  pbmp->Save(L"C:\\Temp\\scratch.bmp",&pngClsid, NULL);
        
    
    Sorry about the code mess, apparently code won't format in a comment on this site with either angle or square bracket tags.
    and it was high resolution again.  There was a bit of a dither effect, but that may have had something to do with converting back from 5 bits/channel to 8 bits/channel.
    – mathog Apr 16 '12 at 18:18
0

One thing I just discovered, GetObjectA will return a NULL bmbits pointer if the HBM was created with CreateDIBitmap. In order for bm.bmbits to point to data then one must use instead CreateDIBSection. The problem being that the second function apparently has no way to read the data out of the EMR_STRETCHDIBITS record! So your choices are:

  1. data read from file into HBM, but no pointer to that data via this mechanism
  2. HBM created correct size, but with no data read from file. Mechanism returns a pointer but it points to a buffer full of zero values!

I was reexamining this because GDI+ save to file functions are not preserving all available data in either JPEG quality=100 mode or for PNG, which are my only two file type options. Trying to get the data into a GDK::Pixbuf. Presumably there is a pointer to it from somewhere within the Bitmap or HBitmap objects.

mathog
  • 345
  • 3
  • 11