MetaFile和向量图形的关系,就像位图和位映像图形的关系一样。位图通常来自实际的图像,而MetaFile则大多是通过计算机程序人为建立的。MetaFile由一系列与图形函数呼叫相同的二进制记录组成,这些记录一般用于绘制直线、曲线、填入的区域和文字等。
「画图(paint)」程序建立位图,而「绘图(draw)」程序建立MetaFile。在优秀的绘图程序中,能轻易地「抓住」某个独立的图形对象(例如一条直线)并将它移动到其它位置。这是因为组成图形的每个成员都是以单独的记录储存的。在画图程序中,这是不可能的-您通常都会局限于删除或插入位图矩形块。
由于MetaFile以图形绘制命令描述图像,因此可以对图像进行缩放而不会失真。位图则不然,如果以二倍大小来显示位图,您却无法得到二倍的分辨率,而只是在水平和垂直方向上重复位图的位。
MetaFile可以转换为位图,但是会丢失一些信息:组成MetaFile的图形对象将不再是独立的,而是被合并进大的图像。将位图转换为MetaFile要艰难得多,一般仅限于非常简单的图像,而且它需要大量处理来分析边界和轮廓。而MetaFile可以包含绘制位图的命令。
虽然MetaFile可以作为图片剪辑储存在磁盘上,但是它们大多用于程序通过剪贴簿共享图片的情况。由于MetaFile将图片描述为图像函数呼叫的集合,因而它们既比位图占用更少的空间,又比位图更与设备无关。
Microsoft Windows支持两种MetaFile格式和支持这些格式的两组函数。我首先讨论从Windows 1.0到目前的32位Windows版本都支持的MetaFile函数,然后讨论为32位Windows系统开发的「增强型MetaFile」。增强型MetaFile在原有MetaFile的基础上有了一些改进,应该尽可能地加以利用。
旧的 的 MetaFile MetaFile MetaFile 格式格式
格式 MetaFile既能够暂时储存在内存中,也能够以文件的形式储存在磁盘上。对应用程序来说,两者区别不大,尤其是由Windows来处理磁盘上储存和加载MetaFile资料的文件I/O时,更是如此。
内存MetaFile MetaFile的的简单简单利用利用
利用 如果呼叫CreateMetaFile函数来建立MetaFile设备内容,Windows就会以早期的格式建立一个MetaFile,然后您可以使用大部分GDI绘图函数在该MetaFile设备内容上进行绘图。这些GDI呼叫并不在任何具体的设备上绘图,相反地,它们被储存在MetaFile中。当关闭MetaFile设备内容时,会得到MetaFile的句柄。这时就可以在某个具体的设备内容上「播放」这个MetaFile,这与直接执行MetaFile中GDI函数的效果等同。
CreateMetaFile只有一个参数,它可以是NULL或文件名称。如果是NULL,则MetaFile储存在内存中。如果是文件名称(以.WMF作为「Windows MetaFile」的扩展名),则MetaFile储存在磁盘文件中。
程序18-1中的MetaFile显示了在WM_CREATE消息处理期间建立内存MetaFile的方法,并在WM_PAINT消息处理期间将图像显示100遍。 程序18-1 MetaFile
MetaFile.C
/*-------------------------------------------------------------------------
MetaFile.C --        MetaFile Demonstration Program
(c) Charles Petzold, 1998
--------------------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName [] = TEXT ("MetaFile") ;
HWND                                                        hwnd ;
MSG                                                        msg ;
WNDCLASS                                            wndclass ;
wndclass.style                                              = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc                        = WndProc ;
wndclass.cbClsExtra                          = 0 ;
wndclass.cbWndExtra                          = 0 ;
wndclass.hInstance                          = hInstance ;windows程序设计第7版pdf
MetaFile MetaFile 壹佰软件开发小组  整理编译
wndclass.hIcon                                              = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor                                    = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground              = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName                = NULL ;
wndclass.lpszClassName              = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (  NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("MetaFile Demonstration"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) {
static HMetaFile              hmf ;
static int                                  cxClient, cyClient ;
HBRUSH                                              hBrush ;
HDC                                                        hdc, hdcMeta ;
int                                                        x, y ;
PAINTSTRUCT                                  ps ;
switch (message)
case  WM_CREATE:
hdcMeta=      CreateMetaFile (NULL) ;
hBrush=      CreateSolidBrush (RGB (0, 0, 255)) ;
Rectangle    (hdcMeta, 0, 0, 100, 100) ;
MoveToEx              (hdcMeta,    0,    0,    NULL) ;
LineTo                (hdcMeta,    100,  100)  ;
MoveToEx              (hdcMeta,    0,    100,  NULL) ;
LineTo                (hdcMeta,    100,  0)    ;
SelectObject (hdcMeta, hBrush) ;
Ellipse (hdcMeta, 20, 20, 80, 80) ;
hmf = CloseMetaFile (hdcMeta) ;
DeleteObject (hBrush) ;
return 0 ;
case  WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
case  WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExtEx (hdc, 1000, 1000, NULL) ;
SetViewportExtEx (hdc, cxClient, cyClient, NULL) ;
for (x = 0 ; x < 10 ; x++)
for (y = 0 ; y < 10 ; y++)
{
SetWindowOrgEx (hdc, -100 * x, -100 * y, NULL) ;                                                  PlayMetaFile (hdc, hmf) ;
}
EndPaint (hwnd, &ps) ;
return 0 ;
case  WM_DESTROY:
DeleteMetaFile (hmf) ;
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
这个程序展示了在使用内存MetaFile时所涉及的4个MetaFile函数的用法。第一个是CreateMetaFile。在WM_CREATE消息处理期间用NULL参数呼叫该函数,并传回MetaFile设备内容的句柄。然后,MetaFile利用这个MetaFileDC来绘制两条直线和一个蓝椭圆。这些函数呼叫以二进制形式储存在MetaFile中。CloseMetaFile函数传回MetaFile的句柄。因为以后还要用到该MetaFile句柄,所以把它储存在静态变量。
该MetaFile包含GDI函数呼叫的二进制表示码,它们是两个MoveToEx呼叫、两个LineTo呼叫、一个SelectObject呼叫(指定蓝画刷)和一个Ellipse呼叫。坐标没有指定任何映像方式或转换,它们只是作为数值数据被储存在MetaFile中。
在WM_PAINT消息处理期间,MetaFile设定一种映像方式并呼叫PlayMetaFile在窗口中绘制对象100次。MetaFile中函数呼叫的坐标按照目的设备内容的目前变换方式加以解释。在呼叫PlayMetaFile时,事实上是在重复地呼叫最初在WM_CREATE消息处理期间建立MetaFile时,在CreateMetaFile和CloseMetaFile之间所做的所有呼叫。
和任何GDI对象一样,MetaFile对象也应该在程序终止前被删除。这是在WM_DESTROY消息处理期间用DeleteMetaFile函数处理的工作。
MetaFile程序的结果如图18-1所示。
将MetaFile
MetaFile储
储存在磁
存在磁盘
在上面的例子中,CreateMetaFile的NULL参数表示要建立储存在内存中的MetaFile。我们也可以建立作为文件储存在磁盘上的MetaFile,这种方法对于大的MetaFile比较合适,因为可以节省内存空间。而另一方面,每次使用磁盘上的MetaFile时,就需要存取磁盘。
要把MetaFile转换为使用MetaFile磁盘文件的程序,必须把CreateMetaFile的NULL参数替换为文件名称。在WM_CREATE处理结束时,可以用MetaFile句柄来呼叫DeleteMetaFile,这样句柄被删除,但是磁盘文件仍然被储存着。
在处理WM_PAINT消息处理期间,可以通过呼叫GetMetaFile来取得此磁盘文件的MetaFile句柄:
hmf = GetMetaFile (szFileName) ;
现在就可以像前面那样显示这个MetaFile。在WM_PAINT消息处理结束时,可以用下面的叙述删除该MetaFile句柄:
DeleteMetaFile (hmf) ;
在开始处理WM_DESTROY消息时,不必删除MetaFile,因为它已经在WM_CREATE消息和每个WM_PAINT消息结束时被删除了,但是仍然需要删除磁盘文件:
图18-1 MetaFile程序执行结果显示
DeleteFile (szFileName) ;
当然,除非您想储存该文件。
正如在第十章讨论过的,MetaFile也可以作为使用者自订资源。您可以简单地把它当作数据块加载。如果您有一块包含MetaFile内容的资料,那么您可以使用
hmf = SetMetaFileBitsEx (iSize, pData) ;
来建立MetaFile。SetMetaFileBitsEx有一个对应的函数-GetMetaFileBitsEx,此函数将MetaFile的内容复制到内存块中。
老式老式MetaFile MetaFile MetaFile与与剪贴簿
老式MetaFile有个讨厌的缺陷。如果您具有老式MetaFile的句柄,那么,当您在显示MetaFile时如何确定它的大小呢?除非您深入分析MetaFile的内部结构,否则无法得知。
此外,当程序从剪贴簿取得老式MetaFile时,如果MetaFile被定义为在MM_ISOTROPIC或MM_ANISOTROPIC映像方式下显示,则此程序在使用该MetaFile时具有最大程度的灵活性。程序收到该MetaFile后,就可以在显示它之前简单地通过设定视埠的范围来缩放图像。然而,如果MetaFile内的映像方式被设定为MM_ISOTROPIC或MM_ANISOTROPIC,则收到该MetaFile的程序将无法继续执行。程序仅能在显示MetaFile之前或之后进行GDI呼叫,不允许在显示MetaFile当中进行GDI呼叫。
为了解决这些问题,老式MetaFile句柄不直接放入剪贴簿供其它程序取得,而是作为「MetaFile图片」
(MetaFilePICT结构型态)的一部分。此结构使得从剪贴簿上取得MetaFile图片的程序能够在显示MetaFile之前设定映像方式和视埠范围。 MetaFilePICT结构的长度为16个字节,定义如下:
typedef struct tagMetaFilePICT
{
LONG mm ;                                    // mapping mode
LONG xExt ;                                  // width of the MetaFile image
LONG yExt ;                                  // height of the MetaFile image
LONG hMF ;                                  // handle to the MetaFile
}
MetaFilePICT ;
对于MM_ISOTROPIC和MM_ANISOTROPIC以外的所有映像方式,图像大小用xExt和yExt值表示,其单位是由mm给出的映像方式的单位。利用这些信息,从剪贴簿复制MetaFile图片结构的程序就能够
确定在显示MetaFile时所需的显示空间。建立该MetaFile的程序可以将这些值设定为输入MetaFile的GDI绘制函数中所使用的最大的x坐标和y坐标值。
在MM_ISOTROPIC和MM_ANISOTROPIC映射方式下,xExt和yExt字段有不同的功能。我们在第五章中曾介绍过一个程序,该程序为了在GDI函数中使用与图像实际尺寸无关的逻辑单位而采用MM_ISOTROPIC或MM_ANISOTROPIC映射方式。当程序只想保持纵横比而可以忽略图形显示平面的大小时,采用MM_ISOTROPIC模式;反之,当不需要考虑纵横比时采用MM_ANISOTROPIC模式。您也许还记得,第五章中在程序将映像方式设定为MM_ISOTROPIC或MM_ANISOTROPIC后,通常会呼叫SetWindowExtEx和SetViewportExtEx。SetWindowExtEx呼叫使用逻辑单位来指定程序在绘制时使用的单位,而SetViewportExtEx呼叫使用的设备单位大小则取决于图形显示平面(例如,窗口显示区域的大小)。
如果程序为剪贴簿建立了MM_ISOTROPIC或MM_ANISOTROPIC方式的MetaFile,则该MetaFile本身不应包含对SetViewportExtEx的呼叫,因为该呼叫中的设备单位应该依据建立MetaFile的程序的显示平面,而不是依据从剪贴簿读取并显示MetaFile的程序的显示平面。从剪贴簿取得MetaFile的程序可以利用xExt和yExt值来设定合适的视埠范围以便显示MetaFile。但是当映像方式是MM_ISOTROPIC或MM_ANISOTROPIC时,MetaFile本身包含设定窗口范围的呼叫。MetaFile内的GDI绘图函数的坐标依据这些窗口的范围。
建立MetaFile和MetaFile图片遵循以下规则:
设定MetaFilePICT结构的mm字段来指定映像方式。
对于MM_ISOTROPIC和MM_ANISOTROPIC以外的映像方式,xExt与yExt字段设定为图像的宽和高,单位与mm字段相对应。对于在MM_ISOTROPIC或MM_ANISOTROPIC方式下显示的
MetaFile,工作要复杂一些。在MM_ANISOTROPIC模式下,当程序既不对图片大小跟纵横比给出任何建议信息时,xExt和yExt的值均为零。在这两种模式下,如果xExt和yExt的值为正数,它们就是以0.01mm单位(MM_HIMETRIC单位)表示该图像的宽度和高度。在MM_ISOTROPIC方式下,如果xExt和yExt为负值,它们就指出了图像的纵横比而不是大小。
在MM_ISOTROPIC和MM_ANISOTROPIC映像方式下,MetaFile本身含有对SetWindowExtEx的呼叫,也可能有对SetWindowOrgEx的呼叫。亦即,建立MetaFile的程序在MetaFile设备内容中呼叫这些函数。MetaFile一般不会包含对SetMapMode、SetViewportExtEx或SetViewportOrgEx的呼叫。
MetaFile应该是内存MetaFile,而不是MetaFile文件。
这里有一段范例程序代码,它建立MetaFile并将其复制到剪贴簿。如果MetaFile使用MM_ISOTROPIC或MM_ANISOTROPIC映像方式,则该MetaFile的第一个呼叫应该设定窗口范围(在其它模式中,窗
口的大小是固定的)。无论在哪种模式下,窗口的位置应如下设定:
hdcMeta = CreateMetaFile (NULL) ;
SetWindowExtEx (hdcMeta, ...) ;
SetWindowOrgEx (hdcMeta, ...) ;