什么是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;
}
