/* ********************************************************************* * File: ximawmf.cpp * Purpose: Windows Metafile Class Loader and Writer * Author: Volker Horch - vhorch@gmx.de * created: 13-Jun-2002 * * Note: If the code below works, i wrote it. * If it doesn't work, i don't know who wrote it. ********************************************************************* */ /* ********************************************************************* Note by Author: ********************************************************************* Metafile Formats: ================= There are 2 kinds of Windows Metafiles: - Standard Windows Metafile - Placeable Windows Metafile A StandardWindows Metafile looks like: - Metafile Header (MEATAHEADER) - Metafile Records A Placeable Metafile looks like: - Aldus Header (METAFILEHEADER) - Metafile Header (METAHEADER) - Metafile Records The "Metafile Header" and the "Metafile Records" are the same for both formats. However, the Standard Metafile does not contain any information about the original dimensions or x/y ratio of the Metafile. I decided, to allow only placeable Metafiles here. If you also want to enable Standard Metafiles, you will have to guess the dimensions of the image. ********************************************************************* Limitations: see ximawmf.h you may configure some stuff there ********************************************************************* */ #include "ximawmf.h" #if CXIMAGE_SUPPORT_WMF && CXIMAGE_SUPPORT_WINDOWS //////////////////////////////////////////////////////////////////////////////// #if CXIMAGE_SUPPORT_DECODE //////////////////////////////////////////////////////////////////////////////// bool CxImageWMF::Decode(CxFile *hFile, int32_t nForceWidth, int32_t nForceHeight) { if (hFile == NULL) return false; HENHMETAFILE hMeta; HDC hDC; int32_t cx,cy; //save the current position of the file int32_t pos = hFile->Tell(); // Read the Metafile and convert to an Enhanced Metafile METAFILEHEADER mfh; hMeta = ConvertWmfFiletoEmf(hFile, &mfh); if (hMeta) { // ok, it's a WMF ///////////////////////////////////////////////////////////////////// // We use the original WMF size information, because conversion to // EMF adjusts the Metafile to Full Screen or does not set rclBounds at all // ENHMETAHEADER emh; // uint32_t uRet; // uRet = GetEnhMetaFileHeader(hMeta, // handle of enhanced metafile // sizeof(ENHMETAHEADER), // size of buffer, in bytes // &emh); // address of buffer to receive data // if (!uRet){ // DeleteEnhMetaFile(hMeta); // return false; // } // // calculate size // cx = emh.rclBounds.right - emh.rclBounds.left; // cy = emh.rclBounds.bottom - emh.rclBounds.top; ///////////////////////////////////////////////////////////////////// // calculate size // scale the metafile (pixels/inch of metafile => pixels/inch of display) // mfh.inch already checked to be <> 0 hDC = ::GetDC(0); int32_t cx1 = ::GetDeviceCaps(hDC, LOGPIXELSX); int32_t cy1 = ::GetDeviceCaps(hDC, LOGPIXELSY); ::ReleaseDC(0, hDC); cx = (mfh.inch/2 + (mfh.bbox.right - mfh.bbox.left) * cx1) / mfh.inch; cy = (mfh.inch/2 + (mfh.bbox.bottom - mfh.bbox.top) * cy1) / mfh.inch; } else { // maybe it's an EMF... hFile->Seek(pos,SEEK_SET); ENHMETAHEADER emh; hMeta = ConvertEmfFiletoEmf(hFile, &emh); if (!hMeta){ strcpy(info.szLastError,"corrupted WMF"); return false; // definitively give up } // ok, it's an EMF; calculate canvas size cx = emh.rclBounds.right - emh.rclBounds.left; cy = emh.rclBounds.bottom - emh.rclBounds.top; // alternative methods, sometime not so reliable... [DP] //cx = emh.szlDevice.cx; //cy = emh.szlDevice.cy; // //hDC = ::GetDC(0); //float hscale = (float)GetDeviceCaps(hDC, HORZRES)/(100.0f * GetDeviceCaps(hDC, HORZSIZE)); //float vscale = (float)GetDeviceCaps(hDC, VERTRES)/(100.0f * GetDeviceCaps(hDC, VERTSIZE)); //::ReleaseDC(0, hDC); //cx = (int32_t)((emh.rclFrame.right - emh.rclFrame.left) * hscale); //cy = (int32_t)((emh.rclFrame.bottom - emh.rclFrame.top) * vscale); } if (info.nEscape == -1) { // Check if cancelled head.biWidth = cx; head.biHeight= cy; info.dwType = CXIMAGE_FORMAT_WMF; DeleteEnhMetaFile(hMeta); strcpy(info.szLastError,"output dimensions returned"); return true; } if (!cx || !cy) { DeleteEnhMetaFile(hMeta); strcpy(info.szLastError,"empty WMF"); return false; } if (nForceWidth) cx=nForceWidth; if (nForceHeight) cy=nForceHeight; ShrinkMetafile(cx, cy); // !! Otherwise Bitmap may have bombastic size HDC hDC0 = ::GetDC(0); // DC of screen HBITMAP hBitmap = CreateCompatibleBitmap(hDC0, cx, cy); // has # colors of display hDC = CreateCompatibleDC(hDC0); // memory dc compatible with screen ::ReleaseDC(0, hDC0); // don't need anymore. get rid of it. if (hDC){ if (hBitmap){ RECT rc = {0,0,cx,cy}; int32_t bpp = ::GetDeviceCaps(hDC, BITSPIXEL); HBITMAP hBitmapOld = (HBITMAP)SelectObject(hDC, hBitmap); // clear out the entire bitmap with windows background // because the MetaFile may not contain background information uint32_t dwBack = XMF_COLOR_BACK; #if XMF_SUPPORT_TRANSPARENCY if (bpp == 24) dwBack = XMF_COLOR_TRANSPARENT; #endif uint32_t OldColor = SetBkColor(hDC, dwBack); ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL); SetBkColor(hDC, OldColor); //retrieves optional palette entries from the specified enhanced metafile PLOGPALETTE plogPal; PBYTE pjTmp; HPALETTE hPal; int32_t iEntries = GetEnhMetaFilePaletteEntries(hMeta, 0, NULL); if (iEntries) { if ((plogPal = (PLOGPALETTE)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(uint32_t) + sizeof(PALETTEENTRY)*iEntries )) == NULL) { DeleteObject(hBitmap); DeleteDC(hDC); DeleteEnhMetaFile(hMeta); strcpy(info.szLastError,"Cancelled"); return false; } plogPal->palVersion = 0x300; plogPal->palNumEntries = (uint16_t) iEntries; pjTmp = (PBYTE) plogPal; pjTmp += 4; GetEnhMetaFilePaletteEntries(hMeta, iEntries, (PPALETTEENTRY)pjTmp); hPal = CreatePalette(plogPal); GlobalFree(plogPal); SelectPalette(hDC, hPal, FALSE); RealizePalette(hDC); } // Play the Metafile into Memory DC BOOL bRet = PlayEnhMetaFile(hDC, // handle to a device context hMeta, // handle to an enhanced metafile &rc); // pointer to bounding rectangle SelectObject(hDC, hBitmapOld); DeleteEnhMetaFile(hMeta); // we are done with this one if (info.nEscape) { // Check if cancelled DeleteObject(hBitmap); DeleteDC(hDC); strcpy(info.szLastError,"Cancelled"); return false; } // the Bitmap now has the image. // Create our DIB and convert the DDB into DIB if (!Create(cx, cy, bpp, CXIMAGE_FORMAT_WMF)) { DeleteObject(hBitmap); DeleteDC(hDC); return false; } #if XMF_SUPPORT_TRANSPARENCY if (bpp == 24) { RGBQUAD rgbTrans = { XMF_RGBQUAD_TRANSPARENT }; SetTransColor(rgbTrans); } #endif // We're finally ready to get the DIB. Call the driver and let // it party on our bitmap. It will fill in the color table, // and bitmap bits of our global memory block. bRet = GetDIBits(hDC, hBitmap, 0, (uint32_t)cy, GetBits(), (LPBITMAPINFO)pDib, DIB_RGB_COLORS); DeleteObject(hBitmap); DeleteDC(hDC); return (bRet!=0); } else { DeleteDC(hDC); } } else { if (hBitmap) DeleteObject(hBitmap); } DeleteEnhMetaFile(hMeta); return false; } /********************************************************************** Function: CheckMetafileHeader Purpose: Check if the Metafileheader of a file is valid **********************************************************************/ BOOL CxImageWMF::CheckMetafileHeader(METAFILEHEADER *metafileheader) { uint16_t *pw; uint16_t cs; int32_t i; // check magic # if (metafileheader->key != 0x9ac6cdd7L) return false; // test checksum of header pw = (uint16_t *)metafileheader; cs = *pw; pw++; for (i = 0; i < 9; i++) { cs ^= *pw; pw++; } if (cs != metafileheader->checksum) return false; // check resolution if ((metafileheader->inch <= 0) || (metafileheader->inch > 2540)) return false; return true; } /********************************************************************** Function: ConvertWmfFiletoEmf Purpose: Converts a Windows Metafile into an Enhanced Metafile **********************************************************************/ HENHMETAFILE CxImageWMF::ConvertWmfFiletoEmf(CxFile *fp, METAFILEHEADER *metafileheader) { HENHMETAFILE hMeta; uint32_t lenFile; uint32_t len; uint8_t *p; METAHEADER mfHeader; uint32_t seekpos; hMeta = 0; // get length of the file lenFile = fp->Size(); // a placeable metafile starts with a METAFILEHEADER // read it and check metafileheader len = fp->Read(metafileheader, 1, sizeof(METAFILEHEADER)); if (len < sizeof(METAFILEHEADER)) return (hMeta); if (CheckMetafileHeader(metafileheader)) { // This is a placeable metafile // Convert the placeable format into something that can // be used with GDI metafile functions seekpos = sizeof(METAFILEHEADER); } else { // Not a placeable wmf. A windows metafile? // at least not scaleable. // we could try to convert, but would loose ratio. don't allow this return (hMeta); //metafileheader->bbox.right = ?; //metafileheader->bbox.left = ?; //metafileheader->bbox.bottom = ?; //metafileheader->bbox.top = ?; //metafileheader->inch = ?; // //seekpos = 0; // fp->Seek(0, SEEK_SET); // rewind } // At this point we have a metaheader regardless of whether // the metafile was a windows metafile or a placeable metafile // so check to see if it is valid. There is really no good // way to do this so just make sure that the mtType is either // 1 or 2 (memory or disk file) // in addition we compare the length of the METAHEADER against // the length of the file. if filelength < len => no Metafile len = fp->Read(&mfHeader, 1, sizeof(METAHEADER)); if (len < sizeof(METAHEADER)) return (hMeta); if ((mfHeader.mtType != 1) && (mfHeader.mtType != 2)) return (hMeta); // Length in Bytes from METAHEADER len = mfHeader.mtSize * 2; if (len > lenFile) return (hMeta); // Allocate memory for the metafile bits p = (uint8_t *)malloc(len); if (!p) return (hMeta); // seek back to METAHEADER and read all the stuff at once fp->Seek(seekpos, SEEK_SET); lenFile = fp->Read(p, 1, len); if (lenFile != len) { free(p); return (hMeta); } // the following (commented code) works, but adjusts rclBound of the // Enhanced Metafile to full screen. // the METAFILEHEADER from above is needed to scale the image // hMeta = SetWinMetaFileBits(len, p, NULL, NULL); // scale the metafile (pixels/inch of metafile => pixels/inch of display) METAFILEPICT mfp; int32_t cx1, cy1; HDC hDC; hDC = ::GetDC(0); cx1 = ::GetDeviceCaps(hDC, LOGPIXELSX); cy1 = ::GetDeviceCaps(hDC, LOGPIXELSY); memset(&mfp, 0, sizeof(mfp)); mfp.mm = MM_ANISOTROPIC; mfp.xExt = 10000; //(metafileheader->bbox.right - metafileheader->bbox.left) * cx1 / metafileheader->inch; mfp.yExt = 10000; //(metafileheader->bbox.bottom - metafileheader->bbox.top) * cy1 / metafileheader->inch; mfp.hMF = 0; // in MM_ANISOTROPIC mode xExt and yExt are in MM_HIENGLISH // MM_HIENGLISH means: Each logical unit is converted to 0.001 inch //mfp.xExt *= 1000; //mfp.yExt *= 1000; // ???? //int32_t k = 332800 / ::GetSystemMetrics(SM_CXSCREEN); //mfp.xExt *= k; mfp.yExt *= k; // fix for Win9x while ((mfp.xExt < 6554) && (mfp.yExt < 6554)) { mfp.xExt *= 10; mfp.yExt *= 10; } hMeta = SetWinMetaFileBits(len, p, hDC, &mfp); if (!hMeta){ //try 2nd conversion using a different mapping mfp.mm = MM_TEXT; hMeta = SetWinMetaFileBits(len, p, hDC, &mfp); } ::ReleaseDC(0, hDC); // Free Memory free(p); return (hMeta); } ///////////////////////////////////////////////////////////////////// HENHMETAFILE CxImageWMF::ConvertEmfFiletoEmf(CxFile *pFile, ENHMETAHEADER *pemfh) { HENHMETAFILE hMeta; int32_t iLen = pFile->Size(); // Check the header first: int32_t pos = pFile->Tell(); int32_t iLenRead = pFile->Read(pemfh, 1, sizeof(ENHMETAHEADER)); if (iLenRead < sizeof(ENHMETAHEADER)) return NULL; if (pemfh->iType != EMR_HEADER) return NULL; if (pemfh->dSignature != ENHMETA_SIGNATURE) return NULL; //if (pemfh->nBytes != (uint32_t)iLen) return NULL; pFile->Seek(pos,SEEK_SET); uint8_t* pBuff = (uint8_t *)malloc(iLen); if (!pBuff) return (FALSE); // Read the Enhanced Metafile iLenRead = pFile->Read(pBuff, 1, iLen); if (iLenRead != iLen) { free(pBuff); return NULL; } // Make it a Memory Metafile hMeta = SetEnhMetaFileBits(iLen, pBuff); free(pBuff); // finished with this one if (!hMeta) return NULL; // oops. // Get the Enhanced Metafile Header uint32_t uRet = GetEnhMetaFileHeader(hMeta, // handle of enhanced metafile sizeof(ENHMETAHEADER), // size of buffer, in bytes pemfh); // address of buffer to receive data if (!uRet) { DeleteEnhMetaFile(hMeta); return NULL; } return (hMeta); } //////////////////////////////////////////////////////////////////////////////// #endif //CXIMAGE_SUPPORT_DECODE //////////////////////////////////////////////////////////////////////////////// #if CXIMAGE_SUPPORT_ENCODE ///////////////////////////////////////////////////////////////////// bool CxImageWMF::Encode(CxFile * hFile) { if (hFile == NULL) return false; strcpy(info.szLastError, "Save WMF not supported"); return false; } #endif // CXIMAGE_SUPPORT_ENCODE ///////////////////////////////////////////////////////////////////// /********************************************************************** Function: ShrinkMetafile Purpose: Shrink the size of a metafile to be not larger than the definition **********************************************************************/ void CxImageWMF::ShrinkMetafile(int32_t &cx, int32_t &cy) { int32_t xScreen = XMF_MAXSIZE_CX; int32_t yScreen = XMF_MAXSIZE_CY; if (cx > xScreen){ cy = cy * xScreen / cx; cx = xScreen; } if (cy > yScreen){ cx = cx * yScreen / cy; cy = yScreen; } } #endif // CIMAGE_SUPPORT_WMF