/*
*********************************************************************
* 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: <km>
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