484 lines
14 KiB
C++
484 lines
14 KiB
C++
/*
|
|
*********************************************************************
|
|
* 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
|
|
|