什么是PlaceableWMF?为了保证wmf矢量图片在不同设备上播放的效果(缩放比、大小等)一致,微软提供了一个名为Placeable WMF的文件格式。他实际上就是在普通的WMF文件之前加了一个额外的文件头来记录WMF在不同设备上播放时的映射方式和缩放信息。微软称之为WmfPlaceableFileHeader:
#include <pshpack2.h> // set structure packing to 2
typedef struct
{
INT16 Left;
INT16 Top;
INT16 Right;
INT16 Bottom;
} PWMFRect16;
struct WmfPlaceableFileHeader
{
UINT32 Key; // GDIP_WMF_PLACEABLEKEY
INT16 Hmf; // Metafile HANDLE number (always 0)
PWMFRect16 BoundingBox; // Coordinates in metafile units
INT16 Inch; // Number of metafile units per inch
UINT32 Reserved; // Reserved (always 0)
INT16 Checksum; // Checksum value for previous 10 WORDs
};
#include <poppack.h>
一般我们使用这个文件头来判断该文件是否为一个Placeable WMF文件:
#ifndef GDIP_WMF_PLACEABLEKEY
# define GDIP_WMF_PLACEABLEKEY 0x9ac6cdd7L
#endif
bool IsPlaceableWMFheader(const WmfPlaceableFileHeader *metafileheader)
{
WORD *pw;
WORD cs;
INT i;
// check magic number.
if (metafileheader->Key != GDIP_WMF_PLACEABLEKEY)
{
return false;
}
// test checksum of header.
pw = (WORD *)metafileheader;
cs = *pw;
++pw;
for (i = 0; i < 9; i++)
{
cs ^= *pw;
++pw;
}
if (cs != metafileheader->Checksum)
{
assert(0 && L"校验和错误,WMF文件可能被破坏,但是微软允许播放!");
}
// check resolution.
if ((metafileheader->Inch <= 0) ||
(metafileheader->Inch > 2540))
{
return false;
}
return true;
}
但是由于windows 2000之后就不再提供对16位gdi函数的支持。所以播放WMF文件都是将其转换为EMF文件来播放的。普通的WMF文件只需要调用SetWinMetaFileBits方法即可顺利转换为EMF播放。但是由于Placeable WMF包含了映射和缩放信息,我们必须在转换的时候要将这些信息保存到EMF中去。这就要关注下SetWinMetaFileBits方法的最后一个参数了。
WmfPlaceableFileHeader里的BoundingBox和Inch一起指定了该文件播放时的实际大小。我们在转换到EMF的时候必须利用这两个信息来生成转换后的EMF的实际长度和宽度。Inch指出了每英寸(inch)有多少个点(dot)。而BoundingBox则是以点(dot)为单位指出实际长度和宽度。那么可以由BoundingBox和Inch来算出以英寸(inch)为单位的长和宽:
WmfPlaceableFileHeader header;
double xInInch = double(header.BoundingBox.Right - header.BoundingBox.Left) /
(double)header.Inch;
double yInInch = double(header.BoundingBox.Bottom - header.BoundingBox.Top) /
(double)header.Inch;
得到实际长宽之后,就可以利用SetWinMetaFileBits来进行转换了。
HENHMETAFILE SetWinMetaFileBits(
UINT cbBuffer, // size of buffer
CONST BYTE *lpbBuffer, // metafile data buffer
HDC hdcRef, // handle to reference DC
CONST METAFILEPICT *lpmfp // size of metafile picture
);
其中最后一个METAFILEPICT类型参数记录转换后的长宽信息。
typedef struct tagMETAFILEPICT {
LONG mm;
LONG xExt;
LONG yExt;
HMETAFILE hMF;
} METAFILEPICT, *LPMETAFILEPICT;
METAFILEPICT中有一个mm成员记录的是映射模式,一般我们选择为MM_ANISOTROPIC。他的映射单位为MM_HIMETRIC模式下的0.01mm。他使用窗口当前的ViewPort。xExt和yExt则分别代表着长和宽。其他模式下的含义请查阅msdn。由于MM_ANISOTROPIC模式下的逻辑单位为0.01毫米,所以我们必须将以英寸为单位的长宽转化为以0.01毫米为单位。
1英寸 == 2.539999918 厘米(公分) == 2539.999918 (0.01毫米)
所以可以设置一个METAFILEPICT提供给SetWinMetaFileBits进行转换:
METAFILEPICT mfp;
METAFILEPICT mfp;
mfp.mm = MM_ANISOTROPIC;
mfp.xExt = unsigned(xInInch * 2539.999918 + 0.5);
mfp.yExt = unsigned(yInInch * 2539.999918 + 0.5);
mfp.hMF = NULL;
HENHMETAFILE hEnhMetafile = ::SetWinMetaFileBits(size - sizeof
(WmfPlaceableFileHeader), header + sizeof(WmfPlaceableFileHeader),NULL, &mfp);
最终的转换函数代码如下:
HENHMETAFILE CreateEnhMetafileFromBuff(BYTE *buff, unsigned size)
{
// Placeable Metafile
if (IsPlaceableWMFheader((WmfPlaceableFileHeader*)buff))
{
const WmfPlaceableFileHeader *placeable =
(WmfPlaceableFileHeader*)buff;
// 以twip为单位
const unsigned& w = placeable->BoundingBox.Right - placeable-
>BoundingBox.Left;
const unsigned& h = placeable->BoundingBox.Bottom - placeable-
>BoundingBox.Top;
const double& scale = 2539.999918f / (double)placeable->Inch;
const unsigned& nw = unsigned((double)w * scale + 0.5);
const unsigned& nh = unsigned((double)h * scale + 0.5);
METAFILEPICT mfp;
mfp.mm = MM_ANISOTROPIC;
mfp.xExt = nw;
mfp.yExt = nh;
mfp.hMF = NULL;
return ::SetWinMetaFileBits(
size - sizeof(WmfPlaceableFileHeader),
buff + sizeof(WmfPlaceableFileHeader),
NULL, &mfp);
}
// EnhMetafile or Metafile
HENHMETAFILE hEnhMetafile = ::SetEnhMetaFileBits(size, buff);
if (hEnhMetafile == NULL) // WMF
{
HDC hDC = GetDC(NULL);
hEnhMetafile = ::SetWinMetaFileBits(
size, buff, hDC, NULL);
ReleaseDC(NULL, hDC);
}
return hEnhMetafile;
}