标题：Metafiles
关于元文件
为微软windows操作系统编写的应用程序可以使用两种图像工具来存储图像：元文件和位图。本节将描述元文件的信息。
在内部，元文件是一个可变长结构数组，称为：元文件记录（metafile records）。元文件中的第一个记录描述了一些公共的信息，比如：创建图像的设备的分辨率、图像的尺寸等等。剩下的记录则是用于描述图像的GDI函数操作记录。当元文件设备描述表（DC）被创建之后，GDI的操作记录就会被存储到元文件中。这个DC是被在创建图像期间所有的绘图操作所需要的。当windows处理一个与元文件DC相关联的GDI函数时，它将转换这个函数到适当的格式并以记录的形式追加（存储）到相关的元文件中。
当图像建立完毕，最后一个记录也加到元文件中之后，这个元文件就可以采用剪贴板来传递给其它的应用程序，或是嵌入到其它的文件中、存储到磁盘文件中，也可以用于以后重复显示之用。如果元文件的记录已经转换为设备命令并且已被适当的设备处理了，那么该元文件已经被显示了（played）。
有两种类型的元文件：增强型元文件和Windows型元文件。增强型元文件一般被用于用Win32 API编写的应用程序中。它的格式包括：元文件头、GDI目标的句柄表、私有调色板和元文件记录数组。增强型元文件提供真正的设备无关性。
Windows元文件一般用于用Windows version 3.X API编写的应用程序中。这种格式的元文件是由一个元文件头和元文件记录数组组成。Windows元文件在技术上有一定的限制，现在已经很少被使用了—它之所以现在还被支持，只是为了系统的兼容性。
增强型元文件
增强型元文件格式
================
程序员可以使用增强型元文件来存储用Win32 GDI函数创建的图像（包括新的路径和转换函数）。因为增强型元文件对于Win32 API是一种标准，以这种格式存储的图像可以从一个Win32应用程序拷贝到另一个应用程序。而且，因为这种格式是真正与设备无关的，所以保证图像的尺寸与形状在其它设备中也保持不变。
增强型元文件
============
增强型元文件是一个记录的数组。而元文件记录是一个可变长的ENHMETARECORD结构。这个结构标识了记录的类型、记录的长度和包含的附加数据。
在增强型元文件中的第一个记录总是增强型元文件头。这个文件头描述了以下信息：
* 元文件的尺寸 （以字节为单位）
* 图像的边框尺寸 （设备单位）
* 图像的边框尺寸 （0.01 mm 为单位）
* 在元文件中记录的个数
* 到可选的文本描述的偏移
* 可选调色板的尺寸
* 原始设备分辩率 （像素单位）
* 原始设备分辩率 （mm单位）
可选的文本描述将用于描述图像以及作者的一些信息，它一般放在元文件头的后面。可选的调色板指出了创建增强型元文件所使用的颜色。剩下的记录标识了用于创建图像的GDI函数。下面的范例描述了系统是怎样将GDI函数转换为一个元文件记录的：
假设用户调用了SetMapMode(4)这个GDI函数，经系统转换后，它
将变为一个记录存入元文件，记录的内容如下：
00000011 0000000C 00000004
其中值00000011指明了记录的类型（11表示GDI函数SetMapMode函数），0C则是这个记录的长度
（以字节为单位），04标识了入口参数4。
增强型元文件操作
================
程序员可以通过提供适当的参数调用CreateEnhMetaFile()函数来创建增强型元文件。Win32 API使用这些参数来维护图像的尺寸，判断元文件是存储于磁盘上还是存储于内存中，等等。
要想在输出设备上维护图像的尺寸，Win32 API需要参考设备的分辩率（参考设备是指第一次显示这个元
文件时所在的设备），以及参考DC（就是关联到参考设备上的DC）。当调用CreateEnhMetaFile()函数的
时候，应用程序必需提供一个DC句柄（参考DC），应用程序可以使用GetDC()或CreateDC()函数来获得一
个参考DC，也可以将参考DC指定为NULL，此时系统将当前的显示设备作为参考设备。
大多数应用程序创建元文件图像最后都会存储到磁盘中，但这并不是必需的。如果应用程序想将图像存储到磁盘中，则它在调用CreateEnhMetaFile()函数时必需提供一个有效的文件名。如果开发者不提供文件名，则系统将自动的为其创建一个临时文件，并将元文件存储于内存中。
程序员也可以加一些描述本图像及作者的一些信息到增强型元文件中。应用程序可以在File Open dialog box（文件打开对话框）中显示这些信息，以给用户一些有益提示。如果应用程序想向增强型元文件中加入一个这样的描述串，则他必需在调用CreateEnhMetaFile()函数时提供一个指向该描述串的指针。
当调用CreateEnhMetaFile()函数成功之后，该函数将返回一个标识元文件的DC句柄。这个DC在与之关联
的文件（或者说是输出设备）上是唯一的。当Windows系统在处理一个GDI函数时，如果它收到的是一个元文件DC句柄，则系统将转换GDI函数为一个增强型元文件记录，并将其追加到增强型元文件的尾部。
当最后一个记录追加到增强型元文件之后，图像就算绘制完毕。此时，应用程序可以调用
CloseEnhMetaFile()函数来关闭并删除该元文件DC，并返回一个标识增强型元文件的句柄。应用程序可以使用该句柄来完成以下任务：
* 显示存储在增强型元文件中的图像（回放）
* 创建增强型元文件的拷贝
* 枚举、编辑、或拷贝增强型元文件中的单个记录
* 获取增强型元文件中可选的描述文本
* 获得增强型元文件头的拷贝
* 获取增强型元文件的二进制版
* 枚举可选调色板中的颜色
* 转换增强型元文件格式为Windows元文件格式
应用程序可以使用CopyEnhMetaFile()函数来拷贝增强型元文件，它支持标识增强型元文件的句柄，也支持指向新文件名的指针。
很多绘图软件或CAD软件都需要编辑存储在增强型元文件中图像的手段。虽然编辑增强型元文件是一项比较复杂的任务，但系统仍提供这种方法。开发者可以使用EnumEnhMetaFile()函数，再结合其它的一些Win32函数就可以对单个的增强型元文件记录进行编辑（该函数有一个回调函数很有用）。
一些应用程序可能需要在FileOpen对话框里显示增强型元文件的文本描述串，此时，程序员可以使用
GetEnhMetaFileHeader()函数来获取增强型元文件头，该文件头中的一个成员变量表明了该元文件中是否存在文本描述串。如果该增强型元文件中存在文本描述串，则应用程序可通过调用
GetEnhMetaFileDescription()函数来获取该文本串。
一些应用程序使用GetEnhMetaFileBits()函数来获取元文件的内容，但是，在获取内容之前，应用程序必需提供文件的尺寸。获取这个尺寸值，可调用GetEnhMetaFileHeader()函数先取得文件头，该文件头中的一个成员变量标识了文件的尺寸。
要想使增强型元文件在不同的输出设备上保持颜色一致，应用程序可以调用CreatePalette()函数来创建调色板，并将其存储到增强型元文件中。其它的程序在显示这个增强型元文件之前，可获取该调色板，并调用RealizePalette()函数来实现该调色板。应用程序可用增强型元文件头中的某个成员的值来判断是否有调色板。如果有，可调用GetEnhMetaFilePaletteEntries()函数来获取这个逻辑调色板。
Windows元文件格式
=================
Win32 API支持Windows元文件主要是为了保持系统向下兼容。以下内容是这种格式的一些技术限制：
* Windows元文件是与应用程序、特定设备相关的。修改应用程序的映射模式或设备的分辩率，将影响
Windows元文件的回放效果（图像的颜色、尺寸可能会改变）。
* Windows元文件头包含的信息不足。它不包含诸如图像的尺寸、创建图像的设备的分辩率、描述文本串、调色板等等信息。
* Windows元文件不支持新的曲线、通道、转换函数。
* 一些Windows元文件记录不能被scaled。
* 与Windows元文件相关联的元文件DC不能被查询（也就是说，应用程序不能获取该设备的分辩率、字体等等信息）。
* 要想转换一个Windows元文件到增强型元文件，应用程序可以调用GetMetaFileBitsEx()函数来获取
Windows元文件数据，然后调用SetWinMetaFileBits()函数将数据转换为增强型元文件。
* 程序员在编写Win32应用程序时，应尽量避免使用Windows元文件格式，而应使用增强型元文件格式。
创建一个增强型元文件
====================
下面这节将描述一个范例，用以说明存放到磁盘上的增强型元文件的创建方法。文件名由用户指定。
范例中使用了应用程序的窗口DC作为元文件的参考DC（Windows将该设备的分辩率数据存储于增强型元文
件头中）。应用程序通过调用GetDC()函数来获取这个DC.
范例使用了应用程序客户区的大小作为图像的框架大小（通过调用GetClientRect()函数来获得）。然后
应用程序转换设备单位为0.01mm单位，并将该值代入CreateEnhMetaFile()函数中。
范例显示另存为对话框，用户可指定新文件的名字。系统将向文件名子串尾部追加三个字符：.EMF作为文件的扩展名。然后将该名字发送给CreateEnhMetaFile()函数。
范例也向增强型元文件中加入了文本描述串，这个描述串是作为资源存放在串表中的。
以下是范例代码：
/* Obtain a handle to a reference DC. */ 
hdcRef = GetDC(hWnd); 
/* 
* Determine the picture frame dimensions. 
* iWidthMM is the display width in millimeters. 
* iHeightMM is the display height in millimeters. 
* iWidthPels is the display width in pixels. 
* iHeightPels is the display height in pixels 
*/ 
iWidthMM = GetDeviceCaps(hdcRef, HORZSIZE); 
iHeightMM = GetDeviceCaps(hdcRef, VERTSIZE); 
iWidthPels = GetDeviceCaps(hdcRef, HORZRES); 
iHeightPels = GetDeviceCaps(hdcRef, VERTRES); 
/* 
* Retrieve the coordinates of the client 
* rectangle, in pixels. 
*/ 
GetClientRect(hWnd, &rect); 
/* 
* Convert client coordinates to .01-mm units. 
* Use iWidthMM, iWidthPels, iHeightMM, and 
* iHeightPels to determine the number of 
* .01-millimeter units per pixel in the x- 
* and y-directions. 
*/ 
rect.left = (rect.left * iWidthMM * 100)/iWidthPels; 
rect.top = (rect.top * iHeightMM * 100)/iHeightPels; 
rect.right = (rect.right * iiWidthMM * 100)/iWidthPels; 
rect.bottom = (rect.bottom * iHeightMM * 100)/iHeightPels; 
/* Load the filename filter from the string table. */ 
LoadString(hInst, IDS_FILTERSTRING, 
(LPSTR)szFilter, sizeof(szFilter)); 
/* 
* Replace the '%' separators that are embedded 
* between the strings in the string-table entry 
* with '\0'. 
*/ 
for (i=0; szFilter[i]!='\0'; i++) 
if (szFilter[i] == '%') 
szFilter[i] = '\0'; 
/* Load the dialog title string from the table. */ 
LoadString(hInst, IDS_TITLESTRING, 
(LPSTR)szTitle, sizeof(szTitle)); 
/* Initialize the OPENFILENAME members. */ 
szFile[0] = '\0'; 
Ofn.lStructSize = sizeof(OPENFILENAME); 
Ofn.hwndOwner = hWnd; 
Ofn.lpstrFilter = szFilter; 
Ofn.lpstrFile= szFile; 
Ofn.nMaxFile = sizeof(szFile); 
Ofn.lpstrFileTitle = szFileTitle; 
Ofn.nMaxFileTitle = sizeof(szFileTitle); 
Ofn.lpstrInitialDir = (LPSTR)NULL; 
Ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT; 
Ofn.lpstrTitle = szTitle; 
/* 
* Display the Filename common dialog box. The 
* filename specified by the user is passed 
* to the CreateEnhMetaFile function and used to 
* store the metafile on disk. 
*/ 
GetSaveFileName(&Ofn); 
/* Load the description from the string table. */ 
LoadString(hInst, IDS_DESCRIPTIONSTRING, 
(LPSTR)szDescription, sizeof(szDescription)); 
/* 
* Replace the '%' string separators that are 
* embedded between strings in the string-table 
* entry with '\0'. 
*/ 
for (i=0; szDescription[i]!='\0'; i++) 
if (szDescription[i] == '%') 
szDescription[i] = '\0'; 
/* Create the metafile DC. */ 
hdcMeta = CreateEnhMetaFile(hdcRef, 
(LPTSTR) Ofn.lpstrFile, 
&rect, (LPSTR)szDescription); 
if (!hdcMeta) 
errhandler("CreateEnhMetaFile", hWnd); 
/* Release the reference DC. */ 
ReleaseDC(hWnd, hdcRef); 
显示图像并将其存入增强型元文件中
================================
这一节描述元文件图像的创建及存储。范例绘制一个图像到屏幕或元文件。如果给出了显示DC,则函数将
图像绘制到屏幕上，如果给出了元文件DC，则函数将图像绘制到元文件中。
以下是范例代码：
void DrawOrStore(HWND hwnd, HDC hdcMeta, HDC hdcDisplay) 
{ 
RECT rect; 
HDC hDC; 
int fnMapModeOld; 
HBRUSH hbrOld; 
/* Draw it to the display DC or store it in the metafile DC. */ 
if (hdcMeta) 
hDC = hdcMeta; 
else 
hDC = hdcDisplay; 
/* Set the mapping mode in the DC. */ 
fnMapModeOld = SetMapMode(hDC, MM_LOENGLISH); 
/* Find the midpoint of the client area. */ 
GetClientRect(hwnd, (LPRECT)&rect); 
DPtoLP(hDC, (LPPOINT)&rect, 2); 
/* Select a gray brush. */ 
hbrOld = SelectObject(hDC, GetStockObject(GRAY_BRUSH)); 
/* Draw a circle with a one inch raduis. */ 
Ellipse(hDC, (rect.right/2 - 100), (rect.bottom/2 + 100), 
(rect.right/2 + 100), (rect.bottom/2 - 100)); 
/* Perform additional drawing here. */ 
/* Set the device context back to its original state. */ 
SetMapMode(hDC, fnMapModeOld); 
SelectObject(hDC, hbrOld); 
} 
打开一个增强型元文件并显示它的内容
==================================
这一节描述了应用程序怎样打开一个存放于磁盘上的增强型元文件，并将元文件图像显示在窗口客户区的方法。
范例通过使用OpenFile对话框来让用户选择一个增强型元文件，并将选择的文件名发给GetEnhMetaFile()函数，该函数返回一个标识该文件的句柄。这个句柄就可以传给PlayEnhMetaFile()函数来显示元文件图像。
以下是范例代码：
LoadString(hInst, IDS_FILTERSTRING, 
(LPSTR)szFilter, sizeof(szFilter)); 
/* 
* Replace occurrences of '%' string separator 
* with '\0'. 
*/ 
for (i=0; szFilter[i]!='\0'; i++) 
if (szFilter[i] == '%') 
szFilter[i] = '\0'; 
LoadString(hInst, IDS_DEFEXTSTRING, 
(LPSTR)szDefExt, sizeof(szFilter)); 
/* 
* Use the OpenFilename common dialog box 
* to obtain the desired filename. 
*/ 
szFile[0] = '\0'; 
Ofn.lStructSize = sizeof(OPENFILENAME); 
Ofn.hwndOwner = hWnd; 
Ofn.lpstrFilter = szFilter; 
Ofn.lpstrCustomFilter = (LPSTR)NULL; 
Ofn.nMaxCustFilter = 0L; 
Ofn.nFilterIndex = 1L; 
Ofn.lpstrFile = szFile; 
Ofn.nMaxFile = sizeof(szFile); 
Ofn.lpstrFileTitle = szFileTitle; 
Ofn.nMaxFileTitle = sizeof(szFileTitle); 
Ofn.lpstrInitialDir = (LPSTR) NULL; 
Ofn.lpstrTitle = (LPSTR)NULL; 
Ofn.Flags = OFN_SHOWHELP | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; 
Ofn.nFileOffset = 0; 
Ofn.nFileExtension = 0; 
Ofn.lpstrDefExt = szDefExt; 
GetOpenFileName(&Ofn); 
/* Open the metafile. */ 
hemf = GetEnhMetaFile(Ofn.lpstrFile); 
/* Retrieve a handle to a window DC. */ 
hDC = GetDC(hWnd); 
/* Retrieve the client rectangle dimensions. */ 
GetClientRect(hWnd, &rct); 
/* Draw the picture. */ 
PlayEnhMetaFile(hDC, hemf, &rct); 
/* Release the metafile handle. */ 
DeleteEnhMetaFile(hemf); 
/* Release the window DC. */ 
ReleaseDC(hWnd, hDC); 
编辑一个增强型元文件
====================
要想编辑存储于增强型元文件中的图像，应用程序必需执行下面的一些任务：
1 使用HIT-TESTING去捕捉光标坐标并获取用户想编辑的目标的位置（line, arc, rectangle, ellipse etc.）
2 转换这些坐标为逻辑单位（或全局单位）
3 调用EnumEnhMetaFile()函数，并且检查每一个元文件记录
4 判断给定的记录是否与GDI绘制函数相符合
5 如果符合，则判断元文件中该记录的坐标是否与截获的用户坐标相同。
6 如果坐标也相同，则说明找到了用户想编辑的记录，可在屏幕上清除该目标
7 从元文件中删除这个记录，并保存该记录的地址到一个指针变量中
8 允许用户重画该目标或替换目标
9 转换新绘制的GDI函数为一个或多个增强型元文件的记录
10 保存这些记录到增强型元文件中
元文件函数
==========
下面的函数用于增强型元文件：
CloseEnhMetaFile 
CopyEnhMetaFile 
CreateEnhMetaFile 
DeleteEnhMetaFile 
EnhMetaFileProc 
EnumEnhMetaFile 
GdiComment 
GetEnhMetaFile 
GetEnhMetaFileBits 
GetEnhMetaFileDescription 
GetEnhMetaFileHeader 
GetEnhMetaFilePaletteEntries 
GetWinMetaFileBits 
PlayEnhMetaFile 
PlayEnhMetaFileRecord 
SetEnhMetaFileBits 
SetWinMetaFileBits 
下面的函数为提供系统兼容而被保留：
CloseMetaFile 
CopyMetaFile 
CreateMetaFile 
DeleteMetaFile 
EnumMetaFile 
EnumMetaFileProc 
GetMetaFile 
GetMetaFileBitsEx 
PlayMetaFile 
PlayMetaFileRecord 
SetMetaFileBitsEx 
增强型元文件所用到的结构
========================
结构：ENHMETAHEADER
typedef struct tagENHMETAHEADER { // enmh
DWORD iType;
DWORD nSize;
RECTL rclBounds;
RECTL rclFrame;
DWORD dSignature;
DWORD nVersion;
DWORD nBytes;
DWORD nRecords;
WORD nHandles;
WORD sReserved;
DWORD nDescription;
DWORD offDescription;
DWORD nPalEntries;
SIZEL szlDevice;
SIZEL szlMillimeters;
DWORD cbPixelFormat;
DWORD offPixelFormat;
DWORD bOpenGL;
} ENHMETAHEADER; 
结构：ENHMETARECORD
typedef struct tagENHMETARECORD { // enmr
DWORD iType;
DWORD nSize;
DWORD dParm[1]; 
} ENHMETARECORD; 
结构：HANDLETABLE
typedef struct tagHANDLETABLE { // ht
HGDIOBJ objectHandle[1]; 
} HANDLETABLE; 
结构：METAHEADER
typedef struct tagMETAHEADER { // mh
WORD mtType;
WORD mtHeaderSize;
WORD mtVersion;
DWORD mtSize;
WORD mtNoObjects;
DWORD mtMaxRecord;
WORD mtNoParameters; 
} METAHEADER; 
结构：METARECORD
typedef struct tagMETARECORD { // mr
DWORD rdSize;
WORD rdFunction;
WORD rdParm[1]; 
} METARECORD;