📄 scemf.cpp
字号:
/*
* This file is part of the EMFexplorer projet.
* Copyright (C) 2004 Smith Charles.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* Extension: for commercial use, apply the Equity Public License, which
* adds to the normal terms of the GLPL a condition of donation to the author.
* If you are interested in support for this source code,
* contact Smith Charles <smith.charles@free.fr> for more information.
*/
#include "stdafx.h"
#include "SCEMF.h"
#include "SCGenInclude.h"
#include SC_INC_WINLIB(SCBitmap.h)
#include SC_INC_WINLIB(SCMemDC.h)
#include SC_INC_WINLIB(SCRichEdit.h)
#include SC_INC_WINLIB(SCGDIUtils.h)
#include <wingdi.h>
// #ifdef _DEBUG
// #define new DEBUG_NEW
// #undef THIS_FILE
// static char THIS_FILE[] = __FILE__;
// #endif
// 1 inch => 2540 0.01mm
#define L001MMPERINCH 2540L
// #define SC_ADJUST_EMFDPI
#define SC_PRIVILEGE_EMFDPI
// Get DPI of EMF
BOOL SCGetEMFDPIs(HENHMETAFILE hEMF, long &lDPIX, long &lDPIY)
{
long lEmfPaperCx;
long lEmfPaperCy;
return SCGetEMFInfos(hEMF, lDPIX, lDPIY, lEmfPaperCx, lEmfPaperCy);
}
// Get DPI and paper size of EMF
BOOL SCGetEMFInfos(HENHMETAFILE hEMF, long &lDPIX, long &lDPIY, long &lPaperCx, long &lPaperCy)
{
ENHMETAHEADER EmfHeader;
if (hEMF && ::GetEnhMetaFileHeader(hEMF, sizeof(ENHMETAHEADER), &EmfHeader))
{
// this paper size is not reliable (there are a lot of 1024x768 around there)
lPaperCx = EmfHeader.szlDevice.cx;
lPaperCy = EmfHeader.szlDevice.cy;
// or this paper size (in mm) is not reliable (there are a lot of 320x240 around there,
// yielding 81x81 DPI device)
lDPIX = MulDiv(lPaperCx, 254, EmfHeader.szlMillimeters.cx*10L);
lDPIY = MulDiv(lPaperCy, 254, EmfHeader.szlMillimeters.cy*10L);
#ifdef SC_ADJUST_EMFDPI
// TODO: fix this or undef SC_ADJUST_EMFDPI
// Ask Microsoft how to get 96 for EMF taylored on a 96 DPI device
if (81==lDPIX)
lDPIX = 96;
if (81==lDPIY)
lDPIY = 96;
#endif
return TRUE;
}
return FALSE;
}
//------------------------------------
#define SC_KEEP_XY 0
#define SC_KEEP_Y 1
#define SC_KEEP_X 2
// #define SC_USE_BOUNDS
///
/// Get size of the given EMF ( doesn't take page rotations into account).
/// (This function attempts to guess the best size to use in order to display an EMF
/// at actual size)
///
BOOL SCGetEMFPlaySize(HENHMETAFILE hEMF, CSize& sizeEMF, HDC hDC/*=NULL*/, float* pfScale/*=NULL*/)
{
ASSERT(hEMF);
// Collect information about the EMF
ENHMETAHEADER EmfHeader;
if (!::GetEnhMetaFileHeader(hEMF, sizeof(ENHMETAHEADER), &EmfHeader))
return FALSE;
#ifdef SC_USE_BOUNDS
sizeEMF.cx = EmfHeader.rclBounds.right - EmfHeader.rclBounds.left + 1;
sizeEMF.cy = EmfHeader.rclBounds.bottom - EmfHeader.rclBounds.top + 1;
if (sizeEMF.cx<=0)
sizeEMF.cx = 1;
if (sizeEMF.cy<=0)
sizeEMF.cy = 1;
#else
long lEmfDPIX = 0;
long lEmfDPIY = 0;
long lEmfPaperCx = 0;
long lEmfPaperCy = 0;
if (!SCGetEMFInfos(hEMF, lEmfDPIX, lEmfDPIY, lEmfPaperCx, lEmfPaperCy))
return FALSE;
int bKeep = SC_KEEP_XY;
int iWidth, iHeight;
{// Scale dimensions to ensure correct aspect ratio
BOOL bTmpDC = (NULL==hDC);
if (bTmpDC)
{
hDC = ::GetDC(NULL);
}
ASSERT(hDC);
if (!hDC)
return FALSE;
// adjust paper dimensions for device play
// (for DPI independence, in case EMF DPIs and DPIs are different)
if (!SCScaleImgSurface(hDC, lEmfDPIX, lEmfDPIY, lEmfPaperCx, lEmfPaperCy))
{
ASSERT(0);
return FALSE;
}
// compute which paper dimension to keep, while preserving aspect ratio
// (always keep the smaller dimension)
#ifdef SC_PRIVILEGE_EMFDPI
// Using supposed EMF DPI instead of device DPI
iWidth = MulDiv(EmfHeader.rclFrame.right - EmfHeader.rclFrame.left + 1, lEmfDPIX, L001MMPERINCH);
iHeight = MulDiv(EmfHeader.rclFrame.bottom - EmfHeader.rclFrame.top + 1, lEmfDPIY, L001MMPERINCH);
#else
GetEMFDimension(hDC, EmfHeader, iWidth, iHeight);
#endif
if (!SCScaleImgSurface(hDC, lEmfDPIX, lEmfDPIY, (long&)iWidth, (long&)iHeight))
{
ASSERT(0);
return FALSE;
}
if (iWidth<=0)
iWidth = 1;
if (iHeight<=0)
iHeight = 1;
if ((iWidth>lEmfPaperCx) || (iHeight>lEmfPaperCy))
{// EMF was taylored with different resolution
if (lEmfPaperCx>=lEmfPaperCy)
{// keep y, and reduce x
bKeep = SC_KEEP_Y;
} else
{// keep x, and reduce y
bKeep = SC_KEEP_X;
}
} else
{
lEmfPaperCx = iWidth;
lEmfPaperCy = iHeight;
}
// done with DC
if (bTmpDC)
::ReleaseDC(NULL,hDC);
}
// Final dimensions
if (SC_KEEP_XY==bKeep)
{
sizeEMF.cx = lEmfPaperCx;
sizeEMF.cy = lEmfPaperCy;
if (pfScale)
*pfScale = 1;
} else
if (SC_KEEP_Y==bKeep)
{
sizeEMF.cy = min(lEmfPaperCy, iHeight);
sizeEMF.cx = ::MulDiv(sizeEMF.cy, iWidth, iHeight);
if (sizeEMF.cx<=0)
sizeEMF.cx = 1;
if (pfScale)
*pfScale = float(sizeEMF.cx)/float(lEmfPaperCx);
} else
{
sizeEMF.cx = min(lEmfPaperCx, iWidth);
sizeEMF.cy = ::MulDiv(sizeEMF.cx, iHeight, iWidth);
if (sizeEMF.cy<=0)
sizeEMF.cy =1;
if (pfScale)
*pfScale = float(sizeEMF.cy)/float(lEmfPaperCy);
}
// Note: at this point sizeEMF may be equal to rclBounds, meaning it may exceed
// paper size (most likely for thin, long, images that require floating points
// calculation for proper scaling).
#endif
return TRUE;
}
#undef SC_KEEP_XY
#undef SC_KEEP_Y
#undef SC_KEEP_X
//-------------------------------
///
/// Smallest rectangle surrounding the image.
///
BOOL SCGetEMFElemsRect(HENHMETAFILE hEMF, CRect& rcElems, fnSCPlayEnhMetaFile fnPlayEMF/*=NULL*/)
{
ENHMETAHEADER EmfHeader;
if (hEMF && ::GetEnhMetaFileHeader(hEMF, sizeof(ENHMETAHEADER), &EmfHeader))
{
#if 0
// the 81 DPI problem would cumulate
// compute bounds in pixels
HDC hDC = ::GetDC(NULL);
ASSERT(hDC);
if (!hDC)
return FALSE;
// compute actual EMF DPIs, based on the dimensions of its creation device
long lEmfDPIX = MulDiv(EmfHeader.szlDevice.cx, 254, EmfHeader.szlMillimeters.cx*10L);
long lEmfDPIY = MulDiv(EmfHeader.szlDevice.cy, 254, EmfHeader.szlMillimeters.cy*10L);
#ifdef SC_ADJUST_EMFDPI
// see SCGetEMFInfos for documentation
if (81==lEmfDPIX)
lEmfDPIX = 96;
if (81==lEmfDPIY)
lEmfDPIY = 96;
//
#endif
// scale the surface for DPI independence
long lDevDPIX = ::GetDeviceCaps(hDC, LOGPIXELSX);
long lDevDPIY = ::GetDeviceCaps(hDC, LOGPIXELSY);
::ReleaseDC(NULL,hDC);
ASSERT(lDevDPIX);
ASSERT(lDevDPIY);
rcElems.left = MulDiv(EmfHeader.rclBounds.left, lDevDPIX, lEmfDPIX);
rcElems.top = MulDiv(EmfHeader.rclBounds.top, lDevDPIY, lEmfDPIY);
rcElems.right = MulDiv(EmfHeader.rclBounds.right, lDevDPIX, lEmfDPIX);
rcElems.bottom = MulDiv(EmfHeader.rclBounds.bottom, lDevDPIY, lEmfDPIY);
// Ckeck bounds
int iWidth = rcElems.Width();
int iHeight = rcElems.Height();
if (iWidth && iHeight)
{
// Ok! This is my bug: I won't allow an EMF whose dimensions exceed its device
// to display at actual size.
if (iWidth<=EmfHeader.szlDevice.cx && iHeight<=EmfHeader.szlDevice.cy)
{
// We take whatever is given
return TRUE;
} // else big EMFs will be sized down
}
#endif
SCComputeEMFBlackBox(hEMF, rcElems, fnPlayEMF);
return TRUE;
}
return FALSE;
}
///
/// Compute EMF frame dimension in pixels.
///
void GetEMFDimension(HDC hDC, ENHMETAHEADER &emfh, int & width, int & height)
{
// Calculate the rect in which to draw the metafile based on the
// intended size and the current output device resolution.
// Remember that the intended size is given in 0.01 mm units, so
// convert those to device units on the target device.
#ifdef SC_PRIVILEGE_EMFDPI
// Compute actual EMF DPIs, based on the dimensions of its creation device,
// and prefer these DPIs over output device DPI
int iDpiX = MulDiv(emfh.szlDevice.cx, 254, emfh.szlMillimeters.cx*10L);
int iDpiY = MulDiv(emfh.szlDevice.cy, 254, emfh.szlMillimeters.cy*10L);
#else
// Get the characteristics of the output device.
int iDpiX = GetDeviceCaps(hDC, LOGPIXELSX);
int iDpiY = GetDeviceCaps(hDC, LOGPIXELSY);
#endif
// Convert from 0.01 mm to pixels.
RECTL& rRect = emfh.rclFrame;
width = MulDiv(rRect.right - rRect.left, iDpiX, L001MMPERINCH);
height = MulDiv(rRect.bottom - rRect.top, iDpiY, L001MMPERINCH);
}
// Scale an image surface according to a destination device resolution
BOOL SCScaleImgSurface(HDC hDC, long lImgDPIX, long lImgDPIY, long &lImgCx, long &lImgCy)
{
ASSERT(hDC);
if (!hDC)
return FALSE;
long lDevDPIX = ::GetDeviceCaps(hDC, LOGPIXELSX);
long lDevDPIY = ::GetDeviceCaps(hDC, LOGPIXELSY);
ASSERT(lDevDPIX);
ASSERT(lDevDPIY);
if (lDevDPIX && lDevDPIY)
{
lImgCx = MulDiv(lImgCx, lDevDPIX, lImgDPIX);
lImgCy = MulDiv(lImgCy, lDevDPIY, lImgDPIY);
return TRUE;
}
return FALSE;
}
//
// Compute the dimensions, in device units, of the smallest rectangle that can be drawn
// around the picture stored in the metafile.
// Note: this is the job of Windows. But rclBounds is not always accurate. So we are
// taking an actual snapshot of the black box on a memory DC compatible with the screen.
//
// Note: Beware that this function clips any white-on-white element laying on the EMF's
// boundaries. That's the reason why ElemsRect may be smaller than EmfHeader.rclBounds.
// Empty lines are good examples of white-on-white elements.
//
BOOL SCComputeEMFBlackBox(HENHMETAFILE hEmf, CRect& ElemsRect, fnSCPlayEnhMetaFile fnPlayEMF/*=NULL*/)
{
// We try to create the emf handle
ASSERT(hEmf);
ENHMETAHEADER EmfHeader;
if (!hEmf || !::GetEnhMetaFileHeader(hEmf, sizeof(ENHMETAHEADER), &EmfHeader))
return FALSE;
#ifdef SC_USE_BOUNDS
// If we could rely on rclBounds
CopyRect(&ElemsRect, (LPCRECT)&EmfHeader.rclBounds);
#else
// Collect information from the EMF
CSize sizeEMF;
SCGetEMFPlaySize(hEmf, sizeEMF);
CRect rcPlay(0, 0, sizeEMF.cx, sizeEMF.cy);
// Draw to extract bounds (use a large surface)
ElemsRect.CopyRect((LPRECT)&rcPlay);
CSCMemDC MemDC;
MemDC.SCPrepareSurface(sizeEMF.cx, sizeEMF.cy, hEmf);
//MemDC.PatBlt(0, 0, sizeEMF.cx, sizeEMF.cy, WHITENESS); // background is white
// Play in rcPlay to conform to aspect ratio
BOOL bOk;
if (NULL==fnPlayEMF)
bOk = ::PlayEnhMetaFile(MemDC.m_hDC, hEmf, (LPRECT)&rcPlay);
else
bOk = fnPlayEMF(MemDC.m_hDC, hEmf, (LPRECT)&rcPlay);
// Extract bounding box (start out with the whole bitmap surface)
// final ElemsRect should be smaller or equal to this rcPlay
CRect& R = ElemsRect;
PBITMAPINFOHEADER pbih; // bitmap info-header
HBITMAP hDib = MemDC.STGetDCDIB();
ASSERT(hDib);
// Retrieve the information describing the DIB.
DIBSECTION ds;
::GetObject(hDib, sizeof(DIBSECTION), &ds);
pbih = (PBITMAPINFOHEADER)&ds.dsBmih;
ASSERT(ds.dsBm.bmBits); // must be a DIB
// Compute image size in pixels
DWORD dwBitsSize = (pbih->biSizeImage!=0) ?
pbih->biSizeImage : abs(pbih->biHeight)*(((pbih->biWidth*pbih->biBitCount + 31) & ~31) >> 3);
BYTE *pBottom = SCExtractMemBoundingBox((LPBYTE)ds.dsBm.bmBits, &R, dwBitsSize,
ds.dsBm.bmWidthBytes, (short)ds.dsBm.bmBitsPixel);
ASSERT(pBottom);
if (!::IsRectEmpty(&R))
{
// adjustment for bottom-up image
if (pbih->biHeight>0)
{
long t = rcPlay.bottom - R.bottom - 1; // new top = old height - pos of bottom pixel
long b = t + (R.bottom - R.top); // new top + new height
// security
ASSERT(t>=-1);
if (t<0)
t = 0;
ASSERT(b>=-1);
if (b<0)
b = 0;
// adjust rect
R.top = t; // number of blank pixels at the top
R.bottom = b+1; // below the bottom pixel
R.right++; // at right of the rightmost pixel
// R.left = number of blank pixels on the left
} else
{ // adjust rect, for pixels on the left column and at the bottom line
R.bottom++;
R.right++;
}
ASSERT(R.left>=0);
ASSERT(R.top>=0);
ASSERT(R.right<=rcPlay.right);
ASSERT(R.bottom<=rcPlay.bottom);
}
#endif
return TRUE;
}
///
/// Metafile to Enhanced Metafile format
///
HENHMETAFILE SCConvertWMFtoEMF(LPCTSTR lpszFname)
{
// Open the file for reading.
CFile sourceFile;
CFileException ex;
if (!sourceFile.Open(lpszFname, CFile::modeRead, &ex))
return NULL;
UINT uiSize = sourceFile.GetLength();
LPBYTE pBuffer = new BYTE[uiSize];
if (!pBuffer)
{
sourceFile.Close();
return NULL;
}
DWORD dwNbRead = sourceFile.Read(pBuffer, uiSize);
sourceFile.Close();
HENHMETAFILE hEMF = SCConvertWMFtoEMF(dwNbRead, pBuffer);
// Cleanup objects.
delete [] pBuffer;
return hEMF;
}
HENHMETAFILE SCConvertWMFtoEMF(DWORD dwSize, LPBYTE pBuffer)
{
LPBYTE pData = pBuffer;
UINT uiSize = dwSize;
APMFILEHEADER* pAPMHeader = (APMFILEHEADER*)pBuffer;
APMFILEHEADER winWMFHeader;
if (WMFMETA_PLACEABLEKEY==pAPMHeader->key)
{// aldus
pData += sizeof(APMFILEHEADER);
uiSize -= sizeof(APMFILEHEADER);
} else
{// windows
// Oh! You didn't place your metafile? Good for you!
pAPMHeader = &winWMFHeader;
memset(pAPMHeader, 0, sizeof(winWMFHeader));
}
HWND ghwndDrawSurf = NULL;
HDC hDCDrawSurf = GetDC(ghwndDrawSurf);
METAFILEPICT mfPict;
mfPict.hMF = 0;
mfPict.mm = MM_ANISOTROPIC;
METAFILEPICT *lpmfp = NULL;
if (pAPMHeader->inch)
{
lpmfp = &mfPict;
SMALL_RECT* prcPict = &pAPMHeader->bbox;
#if 1
mfPict.xExt = MulDiv((long)(prcPict->Right - prcPict->Left), 2540, pAPMHeader->inch);
mfPict.yExt = MulDiv((long)(prcPict->Bottom - prcPict->Top), 2540, pAPMHeader->inch);
#else
mfPict.xExt = 0;
mfPict.yExt = 0;
// Code from Microsoft's mfedit.c ==> BAD RESULT
switch (pAPMHeader->inch) {
// !!! End up in an upside down image
//
case 1440:
mfPict.mm = MM_TWIPS;
break;
case 2540:
mfPict.mm = MM_HIMETRIC;
break;
case 254:
mfPict.mm = MM_LOMETRIC;
break;
case 1000:
mfPict.mm = MM_HIENGLISH;
break;
case 100:
mfPict.mm = MM_LOENGLISH;
break;
default:
// !!! In addition, text is too small
//
mfPict.mm = MM_ANISOTROPIC;
mfPict.xExt = ((long)(prcPict->Right - prcPict->Left)) * pAPMHeader->inch * 2560;
mfPict.yExt = ((long)(prcPict->Bottom - prcPict->Top)) * pAPMHeader->inch * 2560;
break;
}
#endif
}
// Generate EMF
HENHMETAFILE hEMF = SetWinMetaFileBits(uiSize, pData, hDCDrawSurf, lpmfp);
// Cleanup
ReleaseDC(ghwndDrawSurf ,hDCDrawSurf);
return hEMF;
}
///
/// Enhanced Metafile to Windows Metafile format on disk
///
void SCConvertEMFtoWMF(HENHMETAFILE hEMF, LPCTSTR lpszFileName)
{
LPBYTE lpEMFBits;
UINT uiSizeBuf;
HDC hrefDC = GetDC(NULL);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -