metafile是啥集显占用大量内存存

Windows程式开发设计指南(十八)Metafile - CSDN博客
Windows程式开发设计指南(十八)Metafile
18. Metafile
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既能够暂时储存在记忆体中,也能够以档案的形式储存在磁片上。对应用程式来说,两者区别不大,尤其是由Windows来处理磁片上储存和载入metafile资料的档案I/O时,更是如此。
记忆体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遍。
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&) ;
wndclass.style
= CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc
wndclass.cbClsExtra
wndclass.cbWndExtra
wndclass.hInstance
wndclass.hIcon
= LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor
= LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground
= (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName
wndclass.lpszClassName
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.wP
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
static HMETAFILE
static int
cxClient, cyC
PAINTSTRUCT
switch (message)
WM_CREATE:
CreateMetaFile (NULL) ;
hBrush = CreateSolidBrush (RGB (0, 0, 255)) ;
(hdcMeta, 0, 0, 100, 100) ;
(hdcMeta, 0, 0,
(hdcMeta, 0, 100,
(hdcMeta, 100, 0)
SelectObject (hdcMeta, hBrush) ;
Ellipse (hdcMeta, 20, 20, 80, 80) ;
hmf = CloseMetaFile (hdcMeta) ;
DeleteObject (hBrush) ;
return 0 ;
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
hdc = BeginPaint (hwnd, &ps) ;
SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExtEx (hdc, , 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 ;
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所示。
图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讯息结束时被删除了,但是仍然需要删除磁碟档案:
DeleteFile (szFileName) ;
当然,除非您想储存该档案。
正如在第十章讨论过的,metafile也可以作为使用者自订资源。您可以简单地把它当作资料块载入。如果您有一块包含metafile内容的资料,那么您可以使用
hmf = SetMetaFileBitsEx (iSize, pData) ;
来建立metafile。SetMetaFileBitsEx有一个对应的函式-GetMetaFileBitsEx,此函式将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
// mapping mode
// width of the metafile image
// 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, ...) ;
Metafile绘图函式中的座标决定於这些视窗范围和视窗原点。当程式使用GDI呼叫在metafile装置内容中绘制完成後,关闭metafile以得到metafile代号:
hmf = CloseMetaFile (hdcMeta) ;
该程式还需要定义指向METAFILEPICT型态结构的指标,并为此结构配置一块整体记忆体:
GLOBALHANDLE
LPMETAFILEPICT
其他行程式
hGlobal= GlobalAlloc (GHND | GMEM_SHARE, sizeof (METAFILEPICT)) ;
pMFP = (LPMETAFILEPICT) GlobalLock (hGlobal) ;
接著,程式设定该结构的4个栏位:
= MM_... ;
pMFP-&xExt
pMFP-&yExt
GlobalUnlock (hGlobal) ;
然後,程式将包含有metafile图片的整体记忆体块传送给剪贴簿:
OpenClipboard (hwnd) ;
EmptyClipboard () ;
SetClipboardData (CF_METAFILEPICT, hGlobal) ;
CloseClipboard () ;
完成这些呼叫後,hGlobal代号(包含metafile图片结构的记忆体块)和hmf代号(metafile本身)就对建立它们的程式失效了。
现在来看一看难的部分。当程式从剪贴簿取得metafile并显示它时,必须完成下列步骤:
程式利用metafile图片结构的mm栏位设定映射方式。 对於MM_ISOTROPIC或MM_ANISOTROPIC以外的映射方式,程式用xExt和yExt值设定剪贴矩形或简单地设定图像大小。而在MM_ISOTROPIC和MM_ANISOTROPIC映射方式,程式使用xExt和yExt来设定视埠范围。然後,程式显示metafile。
下面程式码,首先打开剪贴簿,得到metafile图片结构代号并将其锁定:
OpenClipboard (hwnd) ;
hGlobal = GetClipboardData (CF_METAFILEPICT) ;
pMFP = (LPMETAFILEPICT) GlobalLock (hGlobal) ;
现在可以储存目前装置内容的属性,并将映射方式设定为结构中的mm值:
SaveDC (hdc) ;
SetMappingMode (pMFP-&mm) ;
如果映射方式不是MM_ISOTROPIC或MM_ANISOTROPIC,则可以用xExt和yExt的值设定剪贴矩形。由於这两个值是逻辑单位,必须用LPtoDP将其转换为用於剪贴矩形的装置单位的座标。也可以简单地储存这些值以掌握图像的大小。
对於MM_ISOTROPIC或MM_ANISOTROPIC映射方式,xExt和yExt用来设定视埠范围。下面有一个用来完成此项任务的函式,如果xExt和yExt没有建议的大小,则该函式假定cxClient和cyClient分别表示metafile显示区域的图素高度和宽度。
void PrepareMetaFile ( HDC hdc, LPMETAFILEPICT pmfp,
int cxClient, int cyClient)
int xScale, yScale, iS
SetMapMode (hdc, pmfp-&mm) ;
if (pmfp-&mm == MM_ISOTROPIC || pmfp-&mm == MM_ANISOTROPIC)
if (pmfp-&xExt == 0)
SetViewportExtEx (hdc, cxClient, cyClient, NULL) ;
else if (pmfp-&xExt & 0)
SetViewportExtEx (hdc,
pmfp-&xExt * GetDeviceCaps (hdc, HORZRES) /
GetDeviceCaps (hdc, HORZSIZE) / 100),
pmfp-&yExt * GetDeviceCaps (hdc, VERTRES) /
GetDeviceCaps (hdc, VERTSIZE) / 100), NULL) ;
else if (pmfp-&xExt & 0)
xScale = 100 * cxClient * GetDeviceCaps (hdc, HORZSIZE) /
GetDeviceCaps (hdc, HORZRES) / -pmfp-&xE
lScale = 100 * cyClient * GetDeviceCaps (hdc, VERTSIZE) /
GetDeviceCaps (hdc, VERTRES) / -pmfp-&yE
iScale = min (xScale, yScale) ;
SetViewportExtEx (hdc, -pmfp-&xExt * iScale * GetDeviceCaps (hdc, HORZRES) /
GetDeviceCaps (hdc, HORZSIZE) / 100, -pmfp-&yExt * iScale
* GetDeviceCaps (hdc, VERTRES) / GetDeviceCaps (hdc, VERTSIZE) / 100, NULL) ;
上面的程式码假设xExt和yExt同时都为零、大於零或小於零,这三种状态之一。如果范围为零,表示没有建议大小或纵横比,视埠范围设定为显示metafile的区域。如果大於零,则xExt和yExt的值代表图像的建议大小,单位是0.01mm。GetDeviceCaps函式用来确定每0.01mm中包含的图素数,并且该值与metafile图片结构的范围值相乘。如果小於零,则xExt和yExt的值表示建议的纵横比而不是建议的大小。iScale的值首先根据对应cxClient和cyClient的毫米表示的纵横比计算出来,该缩放因数用於设定图素单位的视埠范围。
完成了上述工作後,可以设定视埠原点,显示metafile,并恢复装置内容:
PlayMetaFile (pMFP-&hMF) ;
RestoreDC (hdc, -1) ;
然後,对记忆体块解锁并关闭剪贴簿:
GlobalUnlock (hGlobal) ;
CloseClipboard () ;
如果程式使用增强型metafile就可以省去这项工作。当某个应用程式将这些格式放入剪贴簿而另一个程式却要求从剪贴簿中获得其他格式时,Windows剪贴簿会自动在老式metafile和增强型metafile之间进行格式转换。
增强型metafile
「增强型metafile」 格式是在32位元Windows版本中发表的。它包含一组新的函式呼叫、一对新的资料结构、新的剪贴簿格式和新的档案副档名.EMF。
这种新的metafile格式最重要的改进是加入可通过函式呼叫取得的更丰富的表头资讯,这种表头资讯可用来帮助应用程式显示metafile图像。
有些增强型metafile函式使您能够在增强型metafile(EMF)格式和老式metafile格式(也称作Windows metafile(WMF)格式)之间来回转换。当然,这种转换很可能遇到麻烦,因为老式metafile格式并不支援某些,例如GDI绘图路径等,新的32位元图形功能。
程式18-2所示的EMF1建立并显示增强型metafile。
/*----------------------------------------------------------------------------
EMF1.C -- Enhanced Metafile Demo #1
(c) Charles Petzold, 1998
-----------------------------------------------------------------------------*/
#include &windows.h&
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
static TCHAR
szAppName[] = TEXT (&EMF1&) ;
wndclass.style
= CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc
wndclass.cbClsExtra
wndclass.cbWndExtra
wndclass.hInstance
wndclass.hIcon
= LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor
= LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground
= GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName
wndclass.lpszClassName
if (!RegisterClass (&wndclass))
MessageBox ( NULL, TEXT (&This program requires Windows NT!&),
szAppName, MB_ICONERROR) ;
return 0 ;
hwnd = CreateWindow ( szAppName, TEXT (&Enhanced Metafile Demo #1&),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, nCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
return msg.wP
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
static HENHMETAFILE
hdc, hdcEMF ;
PAINTSTRUCT
switch (message)
WM_CREATE:
hdcEMF = CreateEnhMetaFile (NULL, NULL, NULL, NULL) ;
(hdcEMF, 100, 100, 200, 200) ;
(hdcEMF, 100, 100, NULL) ;
(hdcEMF, 200, 200) ;
(hdcEMF, 200, 100, NULL) ;
(hdcEMF, 100, 200) ;
hemf = CloseEnhMetaFile (hdcEMF) ;
return 0 ;
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
rect.right
rect.right
rect.right
rect.bottom
rect.bottom
rect.bottom
PlayEnhMetaFile (hdc, hemf, &rect) ;
EndPaint (hwnd, &ps) ;
return 0 ;
WM_DESTROY:
DeleteEnhMetaFile (hemf) ;
PostQuitMessage (0) ;
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
在EMF1的视窗讯息处理程式处理WM_CREATE讯息处理期间,程式首先通过呼叫CreateEnhMetaFile来建立增强型metafile。该函式有4个参数,但可以把它们都设为NULL。稍候我将说明这4个参数在非NULL情况下的使用方法。
和CreateMetaFile一样,CreateEnhMetaFile函式传回特定的装置内容代号。该程式利用这个代号绘制一个矩形和该矩形的两条对角线。这些函式呼叫及其参数被转换为二进位元的形式并储存在metafile中。
最後通过对CloseEnhMetaFile函式的呼叫结束了增强型metafile的建立并传回指向它的代号。该档案代号储存在HENHMETAFILE型态的静态变数中。
在WM_PAINT讯息处理期间,EMF1以RECT结构取得程式的显示区域视窗大小。通过调整结构中的4个栏位,使该矩形的长和宽为显示区域视窗长和宽的一半并位於视窗的中央。然後EMF1呼叫PlayEnhMetaFile,该函式的第一个参数是视窗的装置内容代号,第二个参数是该增强型metafile的代号,第三个参数是指向RECT结构的指标。
在metafile的建立程序中,GDI得出整个metafile图像的尺寸。在本例中,图像的长和宽均为100个单位。在metafile的显示程序中,GDI将图像拉伸以适应PlayEnhMetaFile函式指定的矩形大小。EMF1在Windows下执行的三个执行实体如图18-2所示。
图18-2 EMF1得萤幕显示
最後,在WM_DESTROY讯息处理期间,EMF1呼叫DeleteEnhMetaFile删除metafile。
让我们总结一下从EMF1程式学到的一些东西。
首先,该程式在建立增强型metafile时,画矩形和直线的函式所使用的座标并不是实际意义上的座标。您可以将它们同时加倍或都减去某个常数,而其结果不会改变。这些座标只是在定义图像时说明彼此间的对应关系。
其次,为了适於在传递给PlayEnhMetaFile函式的矩形中显示,图像大小会被缩放。因此,如图18-2所示,图像可能会变形。尽管metafile座标指出该图像是正方形的,但一般情况下我们却得不到这样的图像。而在某些时候,这又正是我们想要得到的图像。例如,将图像嵌入一段文书处理格式的文字中时,可能会要求使用者为图像指定矩形,并且确保整个图像恰好位於矩形中而不浪费空间。这样,使用者可通过适当调整矩形的大小来得到正确的纵横比。
然而有时候,您也许希望保留图像最初的纵横比,因为这一点对於表现视觉资讯尤为重要。例如,警察的嫌疑犯草图既不能比原型胖也不能比原型瘦。或者您希望保留原来图像的度量尺寸,图像必须是两英寸高,否则就不能正常显示。在这种情况下,保留图像的原来尺寸就非常重要了。
同时也要注意metafile中画出的那些对角线似乎没有与矩形顶点相交。这是由於Windows在metafile中储存矩形座标的方式造成的。稍後,会说明解决这个问题的方法。
如果看一看metafile的内容会对metafile工作的方式有一个更好的理解。如果您有一个metafile档案,这将很容易做到,程式18-3中的EMF2程式建立了一个metafile。
/*-----------------------------------------------------------------------------
EMF2.C -- Enhanced Metafile Demo #2
(c) Charles Petzold, 1998
-----------------------------------------------------------------------------*/
#include &windows.h&
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
static TCHAR
szAppName[] = TEXT (&EMF2&) ;
wndclass.style
= CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc
wndclass.cbClsExtra
wndclass.cbWndExtra
wndclass.hInstance
wndclass.hIcon
= LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor
= LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground
= GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName
wndclass.lpszClassName
if (!RegisterClass (&wndclass))
MessageBox ( NULL, TEXT (&This program requires Windows NT!&),
szAppName, MB_ICONERROR) ;
return 0 ;
hwnd = CreateWindow (szAppName, TEXT (&Enhanced Metafile Demo #2&),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, nCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
return msg.wP
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
hdc, hdcEMF ;
HENHMETAFILE
PAINTSTRUCT
switch (message)
WM_CREATE:
hdcEMF = CreateEnhMetaFile (NULL, TEXT (&emf2.emf&), NULL,
TEXT (&EMF2\0EMF Demo #2\0&)) ;
if (!hdcEMF)
return 0 ;
(hdcEMF, 100, 100, 200, 200) ;
(hdcEMF, 100, 100, NULL) ;
(hdcEMF, 200, 200) ;
(hdcEMF, 200, 100, NULL) ;
(hdcEMF, 100, 200) ;
hemf = CloseEnhMetaFile (hdcEMF) ;
DeleteEnhMetaFile (hemf) ;
return 0 ;
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
rect.right
rect.right
rect.right
rect.bottom
rect.bottom
= 3 * rect.bottom
if (hemf = GetEnhMetaFile (TEXT (&emf2.emf&)))
PlayEnhMetaFile (hdc, hemf, &rect) ;
DeleteEnhMetaFile (hemf) ;
EndPaint (hwnd, &ps) ;
return 0 ;
WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
在EMF1程式中,CreateEnhMetaFile函式的所有参数均被设定为NULL。在EMF2中,第一个参数仍旧设定为NULL,该参数还可以是装置内容代号。GDI使用该参数在metafile表头中插入度量资讯,很快我会讨论它。如果该参数为NULL,则GDI认为度量资讯是由视讯装置内容决定的。
CreateEnhMetaFile函式的第二个参数是档案名称。如果该参数为NULL(在EMF1中为NULL,但在EMF2中不为NULL),则该函式建立记忆体metafile。EMF2建立名为EMF2.EMF的metafile档案。
函式的第三个参数是RECT结构的位址,它指出了以0.01mm为单位的metafile的总大小。这是metafile表头资料中极其重要的资讯(这是早期的Windows metafile格式的缺陷之一)。如果该参数为NULL,GDI会计算出尺寸。我比较喜欢让作业系统替我做这些事,所以将该参数设定为NULL。当应用程式对性能要求比较严格时,就需要使用该参数以避免让GDI处理太多东西。
最後的参数是描述该metafile的字串。该字串分为两部分:第一部分是以NULL字元结尾的应用程式名称(不一定是程式的档案名称),第二部分是描述视觉图像内容的说明,以两个NULL字元结尾。例如用C中的符号「\0」作为NULL字元,则该描述字串可以是「LoonyCad V6.4\0Flying Frogs\0\0」。由於在C中通常会在使用的字串末尾放入一个NULL字元,所以如EMF2所示,在末尾仅需一个「\0」。
建立完metafile後,与EMF1一样,EMF2也透过利用由CreateEnhMetaFile函式传回的装置内容代号进行一些GDI函式呼叫。然後程式呼叫CloseEnhMetaFile删除装置内容代号并取得完成的metafile的代号。
然後,在WM_CREATE讯息还没处理完毕时,EMF2做了一些EMF1没有做的事情:在获得metafile代号之後,程式呼叫DeleteEnhMetaFile。该操作释放了用於储存metafile的所有记忆体资源。然而,metafile档案仍然保留在磁碟机中(如果愿意,您可以使用如DeleteFile的档案删除函式来删除该档案)。注意metafile代号并不像EMF1中那样储存在静态变数中,这意味著在讯息之间不需要储存它。
现在,为了使用该metafile,EMF2需要存取磁片档案。这是在WM_PAINT讯息处理期间透过呼叫GetEnhMetaFile进行的。Metafile的档案名称是该函式的唯一参数,该函式传回metafile代号。和EMF1一样,EMF2将这个档案代号传递给PlayEnhMetaFile函式。该metafile图像在PlayEnhMetaFile函式的最後一个参数所指定的矩形中显示。与EMF1不同的是,EMF2在WM_PAINT讯息结束之前就删除该metafile。此後每次处理WM_PAINT讯息时,EMF2都会再次读取metafile,显示并删除它。
要记住,对metafile的删除操作仅是释放了用以储存metafile的记忆体资源而已,磁片metafile甚至在程式执行结束後还保留在磁片上。
由於EMF2留下了metafile档案,您可以看一看它的内容。图18-3显示了该程式建立的EMF2.EMF档案的一堆十六进位代码。
01 00 00 00 88 00 00 00 64 00 00 00 64 00 00 00
........d...d...
C8 00 00 00 C8 00 00 00 35 0C 00 00 35 0C 00 00
........5...5...
6A 18 00 00 6A 18 00 00 20 45 4D 46 00 00 01 00
j...j...EMF....
F4 00 00 00 07 00 00 00 01 00 00 00 12 00 00 00
................
64 00 00 00 00 00 00 00 00 04 00 00 00 03 00 00
d...............
40 01 00 00 F0 00 00 00 00 00 00 00 00 00 00 00
@...............
00 00 00 00 45 00 4D 00 46 00 32 00 00 00 45 00
....E.M.F.2...E.
4D 00 46 00 20 00 44 00 65 00 6D 00 6F 00 20 00
M.F..D.e.m.o..
23 00 32 00 00 00 00 00 2B 00 00 00 18 00 00 00
#.2.....+.......
63 00 00 00 63 00 00 00 C6 00 00 00 C6 00 00 00
c...c...........
1B 00 00 00 10 00 00 00 64 00 00 00 64 00 00 00
........d...d...
36 00 00 00 10 00 00 00 C8 00 00 00 C8 00 00 00
6...............
1B 00 00 00 10 00 00 00 C8 00 00 00 64 00 00 00
............d...
36 00 00 00 10 00 00 00 64 00 00 00 C8 00 00 00
6.......d.......
0E 00 00 00 14 00 00 00 00 00 00 00 10 00 00 00
................
14 00 00 00
图18-3 EMF2.EMF的十六进位代码
图18-3所示的metafile是EMF2在Microsoft Windows NT 4下,视讯显示器的解析度为时建立的。同一程式在Windows 98下建立的metafile会比前者少12个位元组,这一点将在稍後讨论。同样地,视讯显示器的解析度也影响metafile表头的某些资讯。
增强型metafile格式使我们对metafile的工作方式有更深刻的理解。增强型metafile由可变长度的记录组成,这些记录的一般格式由ENHMETARECORD结构说明,它在WINGDI.H表头档案中定义如下:
typedef struct tagENHMETARECORD
// record type
// record size
DWORD dParm [1] ;
// parameters
ENHMETARECORD ;
当然,那个只有一个元素的阵列指出了阵列元素的变数。参数的数量取决於记录型态。iType栏位可以是定义在WINGDI.H档案中以字首EMR_开始的近百个常数之一。nSize栏位是总记录的大小,包括iType和nSize栏位以及一个或多个dParm栏位。
有了这些知识後,让我们看一下图18-3。第一个栏位型态为0x,大小为0x,所以它占据档案的前136个位元组。记录型态为1表示常数EMR_HEADER。我们不妨把对表头纪录的讨论往後搁,先跳到位於第一个记录末尾的偏移量0x0088处。
後面的5个记录与EMF2建立metafile之後的5个GDI函式呼叫有关。该记录在偏移量0x0088处有一个值为0x0000002B的型态代码,这代表EMR_RECTANGLE,很明显是用於Rectangle呼叫的metafile记录。它的长度为0x (十进位24)位元组,用以容纳4个32位元参数。实际上Rectangle函式有5个参数,但是第一个参数,也就是装置内容代号并未储存在metafile中,因为它没有实际意义。尽管在EMF2的函式呼叫中指定了矩形的顶点座标分别是(100,100)和(200,200),但4个参数中的2个是0x
(99),另外2个是0x (198)。EMF2程式在Windows 98下建立的metafile显示出前两个参数是0x0),後2个参数是0x(199)。显然,在Rectangle参数储存到metafile之前,Windows对它们作了调整,但没有保持一致。这就是对角线端点与矩形顶点不能重合的原因。
其次,有4个16位元记录与2个MoveToEx (0x0000001B或EMR_MOVETOEX)和LineTo (0x或EMR_LINETO)呼叫有关。位於metafile中的参数与传递给函式的参数相同。
Metafile以20个位元组长的型态代码为0x0000000E或EMR_EOF(「end of file」)的记录结尾。
增强型metafile总是以表头纪录开始。它对应於ENHMETAHEADER型态的结构,定义如下:
typedef struct tagENHMETAHEADER
// EMR_HEADER = 1
// structure size
RECTL rclB
// bounding rectangle in pixels
RECTL rclF
// size of image in 0.01 millimeters
// ENHMETA_SIGNATURE = & EMF&
// file size in bytes
// total number of records
// number of handles in handle table
// character length of description string
DWORD offD
// offset of description string in file
DWORD nPalE
// number of entries in palette
SIZEL szlD
// device resolution in pixels
SIZEL szlM
// device resolution in millimeters
DWORD cbPixelF
// size of pixel format
DWORD offPixelF
// offset of pixel format
DWORD bOpenGL ;
// FALSE if no OpenGL records
ENHMETAHEADER ;
这种表头纪录的存在可能是增强型metafile格式对早期Windows metafile所做的最为重要的改进。不需要对metafile档案使用档案I/O函式来取得这些表头资讯。如果具有metafile代号,就可以使用GetEnhMetaFileHeader函式:
GetEnhMetaFileHeader (hemf, cbSize, &emh) ;
第一个参数是metafile代号。最後一个参数是指向ENHMETAHEADER结构的指标。第二个参数是该结构的大小。可以使用类似的GetEnh-MetaFileDescription函式取得描述字串。
如上面所定义的,ENHMETAHEADER结构有100位元组长,但在MF2.EMFmetafile中,记录的大小包括描述字串,所以大小为0x88,即136位元组。而Windows 98metafile的表头纪录不包含ENHMETAHEADER结构的最後3个栏位,这一点解释了12个位元组的差别。
rclBounds栏位是指出图像大小的RECT结构,单位是图素。将其从十六进位转换过来,我们看到该图像正如我们希望的那样,其左上角位於(100,100),右下角位於(200,200)。
rclFrame栏位是提供相同资讯的另一个矩形结构,但它是以0.01毫米为单位。在这种情况下,该档案显示两对角顶点分别位於(0x0C35,0x0C35)和(0x186A,0x186A),用十进位表示为()和()的矩形。这些数字是怎么来的?我们很快就会明白。
dSignature栏位始终为值ENHMETA_SIGNATURE或0x464D4520。这看上去是一个奇怪的数字,但如果将位元组的排列顺序倒过来(就像Intel处理器在记忆体中储存多位元组数那样)并转换成ASCII码,就变成字串& EMF&。dVersion栏位的值始终是0x。
其後是nBytes栏位,该栏位在本例中是0x,这是该metafile的总位元组数。nRecords栏位(在本例中是0x)指出了记录数-包括表头纪录、5个GDI函式呼叫和档案结束记录。
下面是两个十六位元的栏位。nHandles栏位为0x0001。该栏位一般指出metafile所使用的图形物件(如画笔、画刷和字体)的非内定代号的数量。由於没有使用这些图形物件,您可能会认为该栏位为零,但实际上GDI自己保留了第一个栏位。我们将很快见到代号储存在metafile中的方式。
下两个栏位指出描述字串的字元个数,以及描述字串在档案中的偏移量,这里它们分别为0x(十进位数字18)和0x。如果metafile没有描述字串,则这两个栏位均为零。
nPalEntries栏位指出在metafile的调色盘表中条目的个数,本例中没有这种情况。
接著表头纪录包括两个SIZEL结构,它们包含两个32位栏位,cx和cy。szlDevice栏位 (在metafile中的偏移量为0x0040)指出了以图素为单位的输出设备大小,szlMillimeters栏位(偏移量为0x0050)指出了以毫米为单位的输出设备大小。在增强型metafile文件中,这个输出设备被称作「参考设备(reference
device)」。它是依据作为第一个参数传递给CreateEnhMetaFile呼叫的代号所指出的装置内容。如果该参数设为NULL,则GDI使用视讯显示器。当EMF2建立上面所示的metafile时,正巧是在Windows NT上以显示模式工作,因此这就是GDI使用的参考设备。
GDI通过呼叫GetDeviceCaps取得此资讯。EMF2.EMF中的szlDevice栏位是0x0(即),它是以HORZRES和VERTRES作为参数呼叫GetDeviceCaps得到的。szlMillimeters栏位是0x140×0xF0,或320×240,是以HORZSIZE和VERTSIZE作为参数呼叫GetDeviceCaps得到的。
通过简单的除法就可以得出图素为0.3125mm高和0.3125mm宽,这就是前面描述的GDI计算rclFrame矩形尺寸的方法。
在metafile中,ENHMETAHEADER结构後跟一个描述字串,该字串是CreateEnhMetaFile函式的最後一个参数。在本例中,该字串由後跟一个NULL字元的「EMF2」字串和後跟两个NULL字元的「EMF Demo #2」字串组成。总共18个字元,要是以Unicode方式储存则为36个字元。无论建立metafile的程式执行在Windows
NT还是Windows 98下,该字串始终以Unicode方式储存。
metafile与GDI物件
我们已经知道了GDI绘图命令储存在metafile中方式,现在看一下GDI物件的储存方式。程式18-4 EMF3除了建立用於绘制矩形和直线的非内定画笔和画刷以外,与前面介绍的EMF2程式很相似。该程式也对Rectangle座标的问题提出了一点修改。EMF3程式使用GetVersion来确定执行环境是Windows 98还是Windows NT,并适当地调整参数。
/*----------------------------------------------------------------------------
Enhanced Metafile Demo #3
(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 (&EMF3&) ;
wndclass.style
= CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc
wndclass.cbClsExtra
wndclass.cbWndExtra
wndclass.hInstance
wndclass.hIcon
= LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor
= LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground
= GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName
wndclass.lpszClassName
if (!RegisterClass (&wndclass))
MessageBox ( NULL, TEXT (&This program requires Windows NT!&),
szAppName, MB_ICONERROR) ;
return 0 ;
hwnd = CreateWindow ( szAppName, TEXT (&Enhanced Metafile Demo #3&),
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.wP
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
hdc, hdcEMF ;
HENHMETAFILE
PAINTSTRUCT
switch (message)
WM_CREATE:
hdcEMF = CreateEnhMetaFile (NULL, TEXT (&emf3.emf&), NULL,
TEXT (&EMF3\0EMF Demo #3\0&)) ;
SelectObject (hdcEMF, CreateSolidBrush (RGB (0, 0, 255))) ;
lb.lbStyle = BS_SOLID ;
lb.lbColor = RGB (255, 0, 0) ;
lb.lbHatch = 0 ;
SelectObject (hdcEMF,
ExtCreatePen (PS_SOLID | PS_GEOMETRIC, 5, &lb, 0, NULL)) ;
if (GetVersion () & 0x) // Windows 98
Rectangle (hdcEMF, 100, 100, 201, 201) ;
// Windows NT
Rectangle (hdcEMF, 101, 101, 202, 202) ;
(hdcEMF, 100, 100, NULL) ;
(hdcEMF, 200, 200) ;
(hdcEMF, 200, 100, NULL) ;
(hdcEMF, 100, 200) ;
DeleteObject (SelectObject (hdcEMF, GetStockObject (BLACK_PEN))) ;
DeleteObject (SelectObject (hdcEMF, GetStockObject (WHITE_BRUSH))) ;
hemf = CloseEnhMetaFile (hdcEMF) ;
DeleteEnhMetaFile (hemf) ;
return 0 ;
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
rect.right
rect.right
rect.right
rect.bottom
rect.bottom
rect.bottom
hemf = GetEnhMetaFile (TEXT (&emf3.emf&)) ;
PlayEnhMetaFile (hdc, hemf, &rect) ;
DeleteEnhMetaFile (hemf) ;
EndPaint (hwnd, &ps) ;
return 0 ;
WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
如我们所看到的,当利用CreateEnhMetaFile传回的装置内容代号来呼叫GDI函式时,这些函式呼叫被储存在metafile中而不是直接输出到萤幕或印表机上。然而,一些GDI函式根本不涉及特定的装置内容。其中有关建立画笔和画刷等图形物件的GDI函式十分重要。虽然逻辑画笔和画刷的定义储存在由GDI保留的记忆体中,但是在建立这些物件时,这些抽象的定义并未与任何特定的装置内容相关。
EMF3呼叫CreateSolidBrush和ExtCreatePen函式。因为这些函式不需要装置内容代号,`所以GDI不会把这些呼叫储存在metafile里。当呼叫它们时,GDI函式只是简单地建立图形绘制物件而不会影响metafile。
然而,当程式呼叫SelectObject函式将GDI物件选入metafile装置内容时,GDI既为物件建立函式编码(源自用於储存物件的内部GDI资料)也为metafile中的SelectObject呼叫进行编码。为了解其工作方式,我们来看一下EMF3.EMF档案的十六进位代码,如图18-4所示:
01 00 00 00 88 00 00 00 60 00 00 00 60 00 00 00
........`...`...
CC 00 00 00 CC 00 00 00 B8 0B 00 00 B8 0B 00 00
................
E7 18 00 00 E7 18 00 00 20 45 4D 46 00 00 01 00
........EMF.....
88 01 00 00 0F 00 00 00 03 00 00 00 12 00 00 00
................
64 00 00 00 00 00 00 00 00 04 00 00 00 03 00 00
d...............
40 01 00 00 F0 00 00 00 00 00 00 00 00 00 00 00
@...............
00 00 00 00 45 00 4D 00 46 00 33 00 00 00 45 00
....E.M.F.3...E.
4D 00 46 00 20 00 44 00 65 00 6D 00 6F 00 20 00
M.F....D.e.m.o..
23 00 33 00 00 00 00 00 27 00 00 00 18 00 00 00
#.3.....'.......
01 00 00 00 00 00 00 00 00 00 FF 00 00 00 00 00
................
25 00 00 00 0C 00 00 00 01 00 00 00 5F 00 00 00
%..........._...
34 00 00 00 02 00 00 00 34 00 00 00 00 00 00 00
4.......4.......
34 00 00 00 00 00 00 00 00 00 01 00 05 00 00 00
4...............
00 00 00 00 FF 00 00 00 00 00 00 00 00 00 00 00
................
25 00 00 00 0C 00 00 00 02 00 00 00 2B 00 00 00
%...........+...
18 00 00 00 63 00 00 00 63 00 00 00 C6 00 00 00
....c...c.......
C6 00 00 00 1B 00 00 00 10 00 00 00 64 00 00 00
............d...
64 00 00 00 36 00 00 00 10 00 00 00 C8 00 00 00
d...6...........
C8 00 00 00 1B 00 00 00 10 00 00 00 C8 00 00 00
................
64 00 00 00 36 00 00 00 10 00 00 00 64 00 00 00
d...6.......d...
C8 00 00 00 25 00 00 00 0C 00 00 00 07 00 00 80
....%...........
28 00 00 00 0C 00 00 00 02 00 00 00 25 00 00 00
(...........%...
0C 00 00 00 00 00 00 80 28 00 00 00 0C 00 00 00
........(.......
01 00 00 00 0E 00 00 00 14 00 00 00 00 00 00 00
................
10 00 00 00 14 00 00 00
图18-4 EMF3.EMF的十六进位代码
如果把这个metafile跟前面的EMF2.EMF档案进行比较,第一个不同点就是EMF3.EMF表头部分中的rclBounds栏位。在EMF2.EMF中,它指出图像限定在座标(0x64,0x64)和(0xC8,0xC8)区域内。而在EMF3.EMF中,座标是(0x60,0x60)和(0xCC,0xCC)。这表示使用了较粗的笔。rclFrame栏位(以0.01mm为单位指出图像大小)也受到影响。
EMF2.EMF中的nBytes栏位(偏移量为0x0030)显示该metafile长度为0xFA位元组,EMF3.EMF中长度为0x0188位元组。EMF2.EMFmetafile包含7个记录(一个表头纪录,5个GDI函式呼叫和一个档案结束记录),但是EMF3.EMF档案包含15个记录。多出的8个记录是两个物件建立函式、4个对SelectObject函式的呼叫和两个对DeleteObject函式的呼叫。
nHandles栏位(在档案中偏移量为0x0038)指出GDI物件的代号个数。该栏位的值总是比metafile使用的非内定物件数多一。(Platform SDK文件解释这个多出来的一是「此表中保留的零索引」)。该栏位在EMF2.EMF的值为1,而在EMF3.EMF中的值为3,多出的数指出了画笔和画刷。
让我们跳到档案中偏移量为0x0088的地方,即第二个记录(表头纪录之後的第一个记录)。记录型态为0x27,对应常数为EMR_CREATE-BRUSHINDIRECT。该metafile记录用於CreateBrushIndirect函式,此函式需要指向LOGBRUSH结构的指标作为参数。该记录的长度为0x18(或24)位元组。
每个被选入metafile装置内容的非备用GDI物件得到一个号码,该号码从1开始编号。这在此记录的下4个位元组中指出,在metafile中的偏移量是0x0090。此记录下面的3个4位元组栏位分别对应LOGBRUSH结构的3个栏位:0x(BS_SOLID的lbStyle栏位)、0x00FF0000(lbColor栏位)和0x(lbHatch栏位)。
下一个记录在EMF3.EMF中的偏移量为0x00A0,记录型态为0x25,或EMR_SELECTOBJECT,是用於SelectObject呼叫的metafile记录。该记录的长度为0x0C(或12)位元组,下一个栏位是数&#,指出它是选中的第一个GDI物件,这就是逻辑画刷。
EMF3.EMF中的偏移量0x00AC是下一个记录,它的记录型态为0x5F或EMR_EXTCREATEPEN。该记录有0x34(或52)个位元组。下一个4位元组栏位是0x02,它表示这是在metafile内使用的第二个非备用GDI物件。
EMR_EXTCREATEPEN记录的下4个栏位重复记录大小两次,之间用0栏位隔开:0x34、0x00、0x34和0x00。下一个栏位是0x,它是PS_SOLID (0x)与PS_GEOMETRIC (0x)组合的画笔样式。接下来是5个单元的宽度,紧接著是ExtCreatePen中使用的逻辑画刷结构的3个栏位,後接0栏位。
如果建立了自订的扩展画笔样式,EMR_EXTCREATEPEN记录会超过52个位元组,这样会影响记录的第二栏位及两个重复的大小栏位。在描述LOGBRUSH结构的3个栏位後面不会是0(像在EMF3.EMF中那样),而是指出了虚线和空格的数量。这後面接著用於虚线和空格长度的许多栏位。
EMF3.EMF的下一个12位元组的栏位是指出第二个物件(画笔)的另一个SelectObject呼叫。接下来的5个记录与EMF2.EMF中的一样-一个0x2B(EMR_RECTANGLE)的记录型态和两组0x1B (EMR_MOVETOEX)和0x36 (EMR_LINETO)记录。
这些绘图函式後面跟著两组0x25(EMR_SELECTOBJECT)和0x28 (EMR_DELETEOBJECT)的12位元组记录。选择物件记录具有0xx的参数。在设定高位元时,它指出一个备用物件,在此例中是0x07(对应BLACK_PEN)和0x00 (WHITE_BRUSH)。
DeleteObject呼叫有2和1两个参数,用於在metafile中使用的两个非内定物件。虽然DeleteObject函式并不需要装置内容代号作为它的第一个参数,但GDI显然保留了metafile中使用的被程式删除的物件。
最後,metafile以0x0E(EMF_EOF)记录结束。
总结一下,每当非内定的GDI物件首次被选入metafile装置内容时,GDI都会为该物件建立函式的记录编码(此例中,为EMR_CREATEBRUSHINDIRECT和EMR_EXTCREATEPEN)。每个物件有一个依序从1开始的唯一数值,此数值由记录的第三个栏位表示。跟在此记录後的是引用该数值的EMR_SELECTOBJECT记录。以後,将物件选入metafile装置内容时(在中间时期没有被删除),就只需要EMR_SELECTOBJECT记录了。
metafile和点阵图
现在,让我们做点稍微复杂的事,在metafile装置内容中绘制一幅点阵图,如程式18-5 EMF4所示。
/*----------------------------------------------------------------------------
Enhanced Metafile Demo #4
(c) Charles Petzold, 1998
----------------------------------------------------------------------------*/
#define OEMRESOURCE
#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 (&EMF4&) ;
wndclass.style
= CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc
wndclass.cbClsExtra
wndclass.cbWndExtra
wndclass.hInstance
wndclass.hIcon
= LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor
= LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground
= GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName
wndclass.lpszClassName
if (!RegisterClass (&wndclass))
MessageBox ( NULL, TEXT (&This program requires Windows NT!&),
szAppName, MB_ICONERROR) ;
return 0 ;
hwnd = CreateWindow ( szAppName, TEXT (&Enhanced Metafile Demo #4&),
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.wP
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
hdc, hdcEMF, hdcM
HENHMETAFILE
PAINTSTRUCT
switch (message)
WM_CREATE:
hdcEMF = CreateEnhMetaFile (NULL, TEXT (&emf4.emf&), NULL,
TEXT (&EMF4\0EMF Demo #4\0&)) ;
hbm = LoadBitmap (NULL, MAKEINTRESOURCE (OBM_CLOSE)) ;
GetObject (hbm, sizeof (BITMAP), &bm) ;
hdcMem = CreateCompatibleDC (hdcEMF) ;
SelectObject (hdcMem, hbm) ;
StretchBlt (hdcEMF,100,100,100,100,
hdcMem, 0,0,bm.bmWidth, bm.bmHeight, SRCCOPY) ;
DeleteDC (hdcMem) ;
DeleteObject (hbm) ;
hemf = CloseEnhMetaFile (hdcEMF) ;
DeleteEnhMetaFile (hemf) ;
return 0 ;
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
rect.right
rect.right
= 3 * rect.right
rect.bottom / 4 ;
rect.bottom
= 3 * rect.bottom
hemf = GetEnhMetaFile (TEXT (&emf4.emf&)) ;
PlayEnhMetaFile (hdc, hemf, &rect) ;
DeleteEnhMetaFile (hemf) ;
EndPaint (hwnd, &ps) ;
return 0 ;
WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
为了方便,EMF4载入由常数OEM_CLOSE指出的系统点阵图。在装置内容中显示点阵图的惯用方法是通过使用CreateCompatibleDC建立与目的装置内容(此例为metafile装置内容)相容的记忆体装置内容。然後,通过使用SelectObject将点阵图选入该记忆体装置内容并且从该记忆体装置内容呼叫BitBlt或StretchBlt把点阵图画到目的装置内容。结束後,删除记忆体装置内容和点阵图。
您会注意到EMF4也呼叫GetObject来确定点阵图的大小。这对SelectObject呼叫是很必要的。
首先,这份程式码储存metafile的空间对GDI来说就是个挑战。在StretchBlt呼叫前根本没有别的GDI函式去处理metafile的装置内容。因此,让我们来看一看EMF4.EMF里头是如何做的,图18-5只显示了一部分。
01 00 00 00 88 00 00 00 64 00 00 00 64 00 00 00
........d...d...
C7 00 00 00 C7 00 00 00 35 0C 00 00 35 0C 00 00
........5...5...
4B 18 00 00 4B 18 00 00 20 45 4D 46 00 00 01 00
K...K....EMF....
F0 0E 00 00 03 00 00 00 01 00 00 00 12 00 00 00
................
64 00 00 00 00 00 00 00 00 04 00 00 00 03 00 00
d...............
40 01 00 00 F0 00 00 00 00 00 00 00 00 00 00 00
@...............
00 00 00 00 45 00 4D 00 46 00 34 00 00 00 45 00
....E.M.F.4...E.
4D 00 46 00 20 00 44 00 65 00 6D 00 6F 00 20 00
M.F...D.e.m.o...
23 00 34 00 00 00 00 00 4D 00 00 00 54 0E 00 00
#.4.....M...T...
64 00 00 00 64 00 00 00 C7 00 00 00 C7 00 00 00
d...d...........
64 00 00 00 64 00 00 00 64 00 00 00 64 00 00 00
d...d...d...d...
20 00 CC 00 00 00 00 00 00 00 00 00 00 00 80 3F
..............?
00 00 00 00 00 00 00 00 00 00 80 3F 00 00 00 00
...........?....
00 00 00 00 FF FF FF 00 00 00 00 00 6C 00 00 00
............l...
28 00 00 00 94 00 00 00 C0 0D 00 00 28 00 00 00
(...........(...
16 00 00 00 28 00 00 00 28 00 00 00 16 00 00 00
....(...(.......
01 00 20 00 00 00 00 00 C0 0D 00 00 00 00 00 00
.. .............
00 00 00 00 00 00 00 00 00 00 00 00 C0 C0 C0 00
................
C0 C0 C0 00 C0 C0 C0 00 C0 C0 C0 00 C0 C0 C0 00
................
C0 C0 C0 00 C0 C0 C0 00 C0 C0 C0 00 0E 00 00 00
................
14 00 00 00 00 00 00 00 10 00 00 00 14 00 00 00
................
图18-5 EMF4.EMF的部分十六进位代码
此metafile只包含3个记录-表头纪录、0x0E54位元组长度的0x4D(或EMR_STRETCHBLT)和档案结束记录。
我不解释该记录每个栏位的含义,但我会指出关键部分,以便理解GDI把EMF4.C中的一系列函式呼叫转化为单个metafile记录的方法。
GDI已经把原始的与设备相关的点阵图转化为与装置无关的点阵图(DIB)。整个DIB储存在记录著自身大小的记录中。我想,在显示metafile和点阵图时,GDI实际上使用StretchDIBits函式而不是StretchBlt。或者,GDI使用CreateDIBitmap把DIB转变回与设备相关的点阵图,然後使用记忆体装置内容及StretchBlt来显示点阵图。
EMR_STRETCHBLT记录开始於metafile的偏移量0x0088处。DIB储存在metafile中,以偏移量0x00F4开始,到0x0EDC处的记录结尾结束。DIB以BITMAPINFOHEADER型态的40位元组的结构开始。在偏移量0x011C处接有22个图素行,每行40个图素。这是每图素32位元的DIB,所以每个图素需要4个位元组。
列举metafile内容
当您希望存取metafile内的个别记录时,可以使用称作metafile列举的程序。如程式18-6 EMF5所示。此程式使用metafile来显示与EMF3相同的图像,但它是通过metafile列举来进行的。
程式18-6 EMF5
/*----------------------------------------------------------------------------
Enhanced Metafile Demo #5
(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 (&EMF5&) ;
wndclass.style
= CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc
wndclass.cbClsExtra
wndclass.cbWndExtra
wndclass.hInstance
wndclass.hIcon
= LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor
= LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground
= GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName
wndclass.lpszClassName
if (!RegisterClass (&wndclass))
MessageBox ( NULL, TEXT (&This program requires Windows NT!&),
szAppName, MB_ICONERROR) ;
return 0 ;
hwnd = CreateWindow ( szAppName, TEXT (&Enhanced Metafile Demo #5&),
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.wP
int CALLBACK EnhMetaFileProc ( HDC hdc, HANDLETABLE * pHandleTable,
CONST ENHMETARECORD * pEmfRecord,
int iHandles, LPARAM pData)
PlayEnhMetaFileRecord (hdc, pHandleTable, pEmfRecord, iHandles) ;
return TRUE ;
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
HENHMETAFILE
PAINTSTRUCT
switch (message)
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
rect.right
rect.right
= 3 * rect.right
rect.bottom
rect.bottom
= 3 * rect.bottom
hemf = GetEnhMetaFile (TEXT (&..\\emf3\\emf3.emf&)) ;
EnumEnhMetaFile (hdc, hemf, EnhMetaFileProc, NULL, &rect) ;
DeleteEnhMetaFile (hemf) ;
EndPaint (hwnd, &ps) ;
return 0 ;
WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
此程式使用EMF3程式建立的EMF3.EMF档案,所以确定在执行此程式前先执行EMF3程式。同时,需要在Visual C++环境中执行两个程式,以确保路径的正确。在处理WM_PAINT时,两个程式的主要区别是EMF3呼叫PlayEnhMetaFile,而EMF5呼叫EnumEnhMetaFile。PlayEnhMetaFile函式有下面的语法:
PlayEnhMetaFile (hdc, hemf, &rect) ;
第一个参数是要显示的metafile的装置内容代号。第二个参数是增强型metafile代号。第三个参数是指向描述装置内容平面上矩形的RECT结构的指标。Metafile图像大小被缩放过,以便刚好能够显示在不超过该矩形的区域内。
EnumEnhMetaFile有5个参数,其中3个与PlayEnhMetaFile一样(虽然RECT结构的指标已经移到参数表的末尾)。
EnumEnhMetaFile的第三个参数是列举函式的名称,它用於呼叫EnhMetaFileProc。第四个参数是希望传递给列举函式的任意资料的指标,这里将该参数简单地设定为NULL。
现在看一看列举函式。当呼叫EnumEnhMetaFile时,对於metafile中的每一个记录,GDI都将呼叫EnhMetaFileProc一次,包括表头纪录和档案结束记录。通常列举函式传回TRUE,但它可能传回FALSE以略过剩下的列举程序。
该列举函式有5个参数,稍後会描述它们。在这个程式中,我仅把前4个参数传递给PlayEnhMetaFileRecord,它使GDI执行由该记录代表的函式呼叫,好像您明确地呼叫它一样。
EMF5使用EnumEnhMetaFile和PlayEnhMetaFileRecord得到的结果与EMF3呼叫PlayEnhMetaFile得到的结果一样。区别在於EMF5现在直接介入了metafile的显示程序,并能够存取各个metafile记录。这是很有用的。
列举函式的第一个参数是装置内容代号。GDI从EnumEnhMetaFile的第一个参数中简单地取得此代号。列举函式把该代号传递给PlayEnhMetaFileRecord来标识图像显示的目的装置内容。
我们先跳到列举函式的第三个参数,它是指向ENHMETARECORD型态结构的指标,前面已经提到过。这个结构描述实际的metafile记录,就像它亲自在metafile中编码一样。
您可以写一些程式码来检查这些记录。您也许不想把某些记录传送到PlayEnhMetaFileRecord函式。例如,在EMF5.C中,把下行插入到PlayEnhMetaFileRecord呼叫的前面:
if (pEmfRecord-&iType != EMR_LINETO)
重新编译程序,执行它,将只看到矩形,而没有两条线。或使用下面的叙述:
if (pEmfRecord-&iType != EMR_SELECTOBJECT)
这个小改变会让GDI用内定物件显示图像,而不是用metafile所建立的画笔和画刷。
程式中不应该修改metafile记录,不过先不要担心这一点。先来看一看程式18-7 EMF6。
程式18-7 EMF6
/*----------------------------------------------------------------------------
Enhanced Metafile Demo #6
(c) Charles Petzold, 1998
-----------------------------------------------------------------------------*/
#include &windows.h&
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR lpszCmdLine, int iCmdShow)
static TCHAR
szAppName[] = TEXT (&EMF6&) ;
wndclass.style
= CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc
wndclass.cbClsExtra
wndclass.cbWndExtra
wndclass.hInstance
wndclass.hIcon
= LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor
= LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground
= GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName
wndclass.lpszClassName
if (!RegisterClass (&wndclass))
MessageBox ( NULL, TEXT (&This program requires Windows NT!&),
szAppName, MB_ICONERROR) ;
return 0 ;
hwnd = CreateWindow (szAppName, TEXT (&Enhanced Metafile Demo #6&),
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.wP
int CALLBACK EnhMetaFileProc ( HDC hdc, HANDLETABLE * pHandleTable,
CONST ENHMETARECORD * pEmfRecord,
int iHandles, LPARAM pData)
ENHMETARECORD * pE
pEmfr = (ENHMETARECORD *) malloc (pEmfRecord-&nSize) ;
CopyMemory (pEmfr, pEmfRecord, pEmfRecord-&nSize) ;
if (pEmfr-&iType == EMR_RECTANGLE)
pEmfr-&iType = EMR_ELLIPSE ;
PlayEnhMetaFileRecord (hdc, pHandleTable, pEmfr, iHandles) ;
free (pEmfr) ;
return TRUE ;
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
HENHMETAFILE
PAINTSTRUCT
switch (message)
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
rect.right / 4 ;
rect.right
= 3 * rect.right
rect.bottom / 4 ;
rect.bottom
= 3 * rect.bottom / 4 ;
hemf = GetEnhMetaFile (TEXT (&..\\emf3\\emf3.emf&)) ;
EnumEnhMetaFile ( hdc, hemf, EnhMetaFileProc, NULL, &rect) ;
DeleteEnhMetaFile (hemf) ;
EndPaint (hwnd, &ps) ;
return 0 ;
WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
与EMF5一样,EMF6使用EMF3程式建立的EMF3.EMFmetafile,因此要在Visual C++中执行这个程式之前先执行过EMF3程式。
EMF6展示了如果在显示metafile之前要修改它们,解决方法是非常简单的:做个被修改过的副本出来就好了。您可以看到,列举程序一开始使用malloc配置一块metafile记录大小的记忆体,它是由传递给该函式的pEmfRecord结构的nSize栏位表示的。这个记忆体块的指标储存在变数pEmfr中,pEmfr本身是指向ENHMETARECORD结构的指标。
程式使用CopyMemory把pEmfRecord指向的结构内容复制到pEmfr指向的结构中。现在我们就可以做些修改了。程式检查记录是否为EMR_RECTANGLE型态,如果是,则用EMR_ELLIPSE取代iType栏位。PEmfr指标被传递到PlayEnhMetaFileRecord然後被释放。结果是程式画出一个椭圆而不是矩形。其他的内容的修改方式都是相同的。
当然,我们的小改变很容易起作用,因为Rectangle和Ellipse函式有同样的参数,这些参数都定义同一件事-图画的边界框。要进行范围更广的修改需要一些不同metafile记录格式的相关知识。
另一个可能性是插入一、两个额外的记录。例如,用下面的叙述代替EMF6.C中的if叙述:
if (pEmfr-&iType == EMR_RECTANGLE)
PlayEnhMetaFileRecord (hdc, pHandleTable, pEmfr, nObjects) ;
pEmfr-&iType = EMR_ELLIPSE ;
无论何时出现Rectangle记录,程式都会处理此记录并把它更改为Ellipse,然後再显示。现在程式将画出矩形和椭圆。
现在讨论一下在列举metafile时GDI物件处理的方式。
在metafile表头中,ENHMETAHEADER结构的nHandles栏位是比在metafile中建立的GDI物件数还要大的值。因此,对於EMF5和EMF6中的metafile,此栏位是3,表示画笔、画刷和其他东西。「其他东西」的具体内容,稍後我会说明。
您会注意到EMF5和EMF6中列举函式的倒数第二个参数,也称作nHandles,它是同一个数,3。
列举函式的第二个参数是指向HANDLETABLE结构的指标,在WINGDI.H中定义如下:
typedef struct tagHANDLETABLE
HGDIOBJ objectHandle [1] ;
HANDLETABLE ;
HGDIOBJ资料型态是GDI物件的代号,被定义为32位元的指标,类似於所有其他GDI物件。这是那些带有一个元素的阵列栏位的结构之一。这意味著此栏位具有可变的长度。objectHandle阵列中的元素数等於nHandles,在此程式中是3。
在列举函式中,可以使用以下运算式取得这些GDI物件代号:
pHandleTable-&objectHandle[i]
对於3个代号,i是0、1和2。
每次呼叫列举函式时,阵列的第一个元素都将包含所列举的metafile代号。这就是前面提到的「其他东西」。
在第一次呼叫列举函式时,表的第二、第三个元素将是0。它们是画笔和画刷代号的保留位置。
以下是列举函式运作的方式:metafile中的第一个物件建立函式具有EMR_CREATEBRUSHINDIRECT的记录型态,此记录指出了物件编号1。当把该记录传递给PlayEnhMetaFileRecord时,GDI建立画刷并取得它的代号。此代号储存在objectHandle阵列的元素1(第二个元素)中。当把第一个EMR_SELECTOBJECT记录传递给PlayEnhMetaFileRecord时,GDI发现此物件编号为1,并能够从表中找到该物件实际的代号,而把它用来呼叫SelectObject。当metafile最後删除画刷时,GDI将objectHandle阵列的元素1设定回0。
通过存取objectHandle阵列,可以使用例如GetObjectType和GetObject等呼叫取得在metafile中使用的物件资讯。
列举metafile的最重要应用也许是在现有的metafile中嵌入其他图像(甚至是整个metafile)。事实上,现有的metafile保持不变;真正进行的是建立包含现有metafile和新嵌入图像的新metafile。基本的技巧是把metafile装置内容代号传递给EnumEnhMetaFile,作为它的第一个参数。这使您能够在metafile装置内容上显示metafile记录和GDI函式呼叫。
在metafile命令序列的开头或结尾嵌入新图像是极简单的-就在EMR_HEADER记录之後或在EMF_EOF记录之前。然而,如果您熟悉现有的metafile结构,就可以把新的绘图命令嵌入所需的任何地方。如程式18-8 EMF7所示。
程式18-8 EMF7
/*---------------------------------------------------------------------------
EMF7.C -- Enhanced Metafile Demo #7
(c) Charles Petzold, 1998
-----------------------------------------------------------------------------*/
#include &windows.h&
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR lpszCmdLine, int iCmdShow)
static TCHAR
szAppName[] = TEXT (&EMF7&) ;
wndclass.style
= CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc
wndclass.cbClsExtra
wndclass.cbWndExtra
wndclass.hInstance
wndclass.hIcon
= LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor
= LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground
= GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName
wndclass.lpszClassName
if (!RegisterClass (&wndclass))
MessageBox ( NULL, TEXT (&This program requires Windows NT!&),
szAppName, MB_ICONERROR) ;
return 0 ;
hwnd = CreateWindow (szAppName, TEXT (&Enhanced Metafile Demo #7&),
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.wP
int CALLBACK EnhMetaFileProc ( HDC hdc, HANDLETABLE * pHandleTable,
CONST ENHMETARECORD * pEmfRecord,
int iHandles, LPARAM pData)
if (pEmfRecord-&iType != EMR_HEADER && pEmfRecord-&iType != EMR_EOF)
PlayEnhMetaFileRecord (hdc, pHandleTable, pEmfRecord, iHandles) ;
if (pEmfRecord-&iType == EMR_RECTANGLE)
hBrush = SelectObject (hdc, GetStockObject (NULL_BRUSH)) ;
lb.lbStyle = BS_SOLID ;
lb.lbColor = RGB (0, 255, 0) ;
lb.lbHatch = 0 ;
hPen = SelectObject (hdc,
ExtCreatePen (PS_SOLID | PS_GEOMETRIC, 5, &lb, 0, NULL)) ;
Ellipse (hdc, 100, 100, 200, 200) ;
DeleteObject (SelectObject (hdc, hPen)) ;
SelectObject (hdc, hBrush) ;
return TRUE ;
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
ENHMETAHEADER
hdc, hdcEMF ;
HENHMETAFILE
PAINTSTRUCT
switch (message)
WM_CREATE:
// Retrieve existing metafile and header
hemfOld = GetEnhMetaFile (TEXT (&..\\emf3\\emf3.emf&)) ;
GetEnhMetaFileHeader (hemfOld, sizeof (ENHMETAHEADER), &emh) ;
// Create a new metafile DC
hdcEMF = CreateEnhMetaFile (NULL, TEXT (&emf7.emf&), NULL,
TEXT (&EMF7\0EMF Demo #7\0&)) ;
// Enumerate the existing metafile
EnumEnhMetaFile (hdcEMF, hemfOld, EnhMetaFileProc, NULL,
(RECT *) & emh.rclBounds) ;
// Clean up
hemf = CloseEnhMetaFile (hdcEMF) ;
DeleteEnhMetaFile (hemfOld) ;
DeleteEnhMetaFile (hemf) ;
return 0 ;
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
rect.right
rect.right
= 3 * rect.right
rect.bottom
rect.bottom
= 3 * rect.bottom
hemf = GetEnhMetaFile (TEXT (&emf7.emf&)) ;
PlayEnhMetaFile (hdc, hemf, &rect) ;
DeleteEnhMetaFile (hemf) ;
EndPaint (hwnd, &ps) ;
return 0 ;
WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
EMF7使用EMF3程式建立的EMF3.EMF,所以在执行EMF7之前要执行EMF3程式建立metafile。
EMF7中的WM_PAINT处理使用PlayEnhMetaFile而不是EnumEnhMetaFile,而且WM_CREATE处理有很大的差别。
首先,程式通过呼叫GetEnhMetaFile取得EMF3.EMF档案的metafile代号,还呼叫GetEnhMetaFileHeader得到增强型metafile表头记录,目的是在後面的EnumEnhMetaFile呼叫中使用rclBounds栏位。
接下来,程式建立新的metafile档案,名为EMF7.EMF。CreateEnhMetaFile函式为metafile传回装置内容代号。然後,使用EMF7.EMF的metafile装置内容代号和EMF3.EMF的metafile代号呼叫EnumEnhMetaFile。
现在来看一看EnhMetaFileProc。如果被列举的记录不是表头纪录或档案结束记录,函式就呼叫PlayEnhMetaFileRecord把记录转换为新的metafile装置内容(并不一定排除表头纪录或档案结束记录,但它们会使metafile变大)。
如果刚转换的记录是Rectangle呼叫,则函式建立画笔用绿色的轮廓线和透明的内部来绘制椭圆。注意程式中经由储存先前的画笔和画刷代号来恢复装置内容状态的方法。在此期间,所有这些函式都被插入到metafile中(记住,也可以使用PlayEnhMetaFile在现有的metafile中插入整个metafile)。
回到WM_CREATE处理,程式呼叫CloseEnhMetaFile取得新metafile的代号。然後,它删除两个metafile代号,将EMF3.EMF和EMF7.EMF档案留在磁片上。
从程式显示输出中可以很明显地看到,椭圆是在矩形之後两条交叉线之前绘制的。
增强型metafile阅览器和印表机
使用剪贴簿转换增强型metafile非常简单,剪贴簿型态是CF_ENHMETAFILE。GetClipboardData函式传回增强型metafile代号,SetClipboardData也使用该metafile代号。复制metafile时可以使用CopyEnhMetaFile函式。如果把增强型metafile放在剪贴簿中,Windows会让需要旧格式的那些程式也可以使用它。如果在剪贴簿中放置旧格式的metafile,Windows将也会自动视需要把内容转换为增强型metafile的格式。
程式18-9 EMFVIEW所示为在剪贴簿中传送metafile的程式码,它也允许载入、储存和列印metafile。
程式18-9 EMFVIEW
/*---------------------------------------------------------------------------
EMFVIEW.C --
View Enhanced Metafiles
(c) Charles Petzold, 1998
-----------------------------------------------------------------------------*/
#include &windows.h&
#include &commdlg.h&
#include &resource.h&
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
TCHAR szAppName[] = TEXT (&EmfView&) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
wndclass.style
= CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc
wndclass.cbClsExtra
wndclass.cbWndExtra
wndclass.hInstance
wndclass.hIcon
= LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor
= LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground
= (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName
wndclass.lpszClassName
if (!RegisterClass (&wndclass))
MessageBox ( NULL, TEXT (&This program requires Windows NT!&),
szAppName, MB_ICONERROR) ;
return 0 ;
hwnd = CreateWindow ( szAppName, TEXT (&Enhanced Metafile Viewer&),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
hAccel = LoadAccelerators (hInstance, szAppName) ;
while (GetMessage (&msg, NULL, 0, 0))
if (!TranslateAccelerator (hwnd, hAccel, &msg))
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
return msg.wP
HPALETTE CreatePaletteFromMetaFile (HENHMETAFILE hemf)
LOGPALETTE *
if (!hemf)
return NULL ;
if (0 == (iNum = GetEnhMetaFilePaletteEntries (hemf, 0, NULL)))
return NULL ;
plp = malloc (sizeof (LOGPALETTE) + (iNum - 1) * sizeof (PALETTEENTRY)) ;
plp-&palVersion
= 0x0300 ;
plp-&palNumEntries = iN
GetEnhMetaFilePaletteEntries (hemf, iNum, plp-&palPalEntry) ;
hPalette = CreatePalette (plp) ;
free (plp) ;
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
static DOCINFO
di = { sizeof (DOCINFO), TEXT (&EmfView: Printing&) } ;
static HENHMETAFILE
static OPENFILENAME
static PRINTDLG
printdlg = { sizeof (PRINTDLG) } ;
static TCHAR
szFileName [MAX_PATH], szTitleName [MAX_PATH] ;
static TCHAR
szFilter[] =
TEXT (&Enhanced Metafiles (*.EMF)\0*.emf\0&)
TEXT (&All Files (*.*)\0*.*\0\0&) ;
ENHMETAHEADER
HENHMETAFILE
i, iLength, iE
PAINTSTRUCT
switch (message)
case WM_CREATE:
// Initialize OPENFILENAME structure
ofn.lStructSize
= sizeof (OPENFILENAME) ;
ofn.hwndOwner
ofn.hInstance
ofn.lpstrFilter
ofn.lpstrCustomFilter
ofn.nMaxCustFilter
ofn.nFilterIndex
ofn.lpstrFile
ofn.nMaxFile
= MAX_PATH ;
ofn.lpstrFileTitle
= szTitleN
ofn.nMaxFileTitle
= MAX_PATH ;
ofn.lpstrInitialDir
ofn.lpstrTitle
ofn.nFileOffset
ofn.nFileExtension
ofn.lpstrDefExt
= TEXT (&emf&) ;
ofn.lCustData
ofn.lpfnHook
ofn.lpTemplateName
return 0 ;
WM_INITMENUPOPUP:
hMenu = GetMenu (hwnd) ;
iEnable = hemf ? MF_ENABLED : MF_GRAYED ;
EnableMenuItem (hMenu, IDM_FILE_SAVE_AS,
iEnable) ;
EnableMenuItem (hMenu, IDM_FILE_PRINT,
iEnable) ;
EnableMenuItem (hMenu, IDM_FILE_PROPERTIES,
iEnable) ;
EnableMenuItem (hMenu, IDM_EDIT_CUT,
iEnable) ;
EnableMenuItem (hMenu, IDM_EDIT_COPY,
iEnable) ;
EnableMenuItem (hMenu, IDM_EDIT_DELETE,
iEnable) ;
EnableMenuItem (hMenu, IDM_EDIT_PASTE,
IsClipboardFormatAvailable (CF_ENHMETAFILE) ?
MF_ENABLED : MF_GRAYED) ;
return 0 ;
WM_COMMAND:
switch (LOWORD (wParam))
IDM_FILE_OPEN:
// Show the File Open dialog box
ofn.Flags = 0 ;
if (!GetOpenFileName (&ofn))
return 0 ;
// If there's an existing EMF, get rid of it.
DeleteEnhMetaFile (hemf) ;
hemf = NULL ;
// Load the EMF into memory
SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
ShowCursor (TRUE) ;
hemf = GetEnhMetaFile (szFileName) ;
ShowCursor (FALSE) ;
SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
// Invalidate the client area for later update
InvalidateRect (hwnd, NULL, TRUE) ;
if (hemf == NULL)
MessageBox ( hwnd, TEXT (&Cannot load metafile&),
szAppName, MB_ICONEXCLAMATION | MB_OK) ;
return 0 ;
IDM_FILE_SAVE_AS:
if (!hemf)
return 0 ;
// Show the File Save dialog box
ofn.Flags = OFN_OVERWRITEPROMPT ;
if (!GetSaveFileName (&ofn))
return 0 ;
// Save the EMF to disk file
SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
ShowCursor (TRUE) ;
hemfCopy = CopyEnhMetaFile (hemf, szFileName) ;
ShowCursor (FALSE) ;
SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
if (hemfCopy)
DeleteEnhMetaFile (hemf) ;
hemf = hemfC
MessageBox ( hwnd, TEXT (&Cannot save metafile&),
szAppName, MB_ICONEXCLAMATION | MB_OK) ;
return 0 ;
IDM_FILE_PRINT:
// Show the Print dialog box and get printer DC
printdlg.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION ;
if (!PrintDlg (&printdlg))
return 0 ;
if (NULL == (hdcPrn = printdlg.hDC))
MessageBox ( hwnd, TEXT (&Cannot obtain printer DC&),
szAppName, MB_ICONEXCLAMATION | MB_OK) ;
return 0 ;
// Get size of printable area of page
rect.right
= GetDeviceCaps (hdcPrn, HORZRES) ;
rect.bottom
= GetDeviceCaps (hdcPrn, VERTRES) ;
bSuccess = FALSE ;
// Play the EMF to the printer
SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
ShowCursor (TRUE) ;
if ((StartDoc (hdcPrn, &di) & 0) && (StartPage (hdcPrn) & 0))
PlayEnhMetaFile (hdcPrn, hemf, &rect) ;
if (EndPage (hdcPrn) & 0)
bSuccess = TRUE ;
EndDoc (hdcPrn) ;
ShowCursor (FALSE) ;
SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
DeleteDC (hdcPrn) ;
if (!bSuccess)
MessageBox ( hwnd, TEXT (&Could not print metafile&),
szAppName, MB_ICONEXCLAMATION | MB_OK) ;
return 0 ;
IDM_FILE_PROPERTIES:
if (!hemf)
return 0 ;
iLength = GetEnhMetaFileDescription (hemf, 0, NULL) ;
pBuffer = malloc ((iLength + 256) * sizeof (TCHAR)) ;
GetEnhMetaFileHeader (hemf, sizeof (ENHMETAHEADER), &header) ;
// Format header file information
= wsprintf (pBuffer,
TEXT (&Bounds = (%i, %i) to (%i, %i) pixels\n&),
header.rclBounds.left, header.rclBounds.top,
header.rclBounds.right, header.rclBounds.bottom) ;
i += wsprintf (pBuffer + i,
TEXT (&Frame = (%i, %i) to (%i, %i) mms\n&),
header.rclFrame.left, header.rclFrame.top,
header.rclFrame.right, header.rclFrame.bottom) ;
i += wsprintf (pBuffer + i,
TEXT (&Resolution = (%i, %i) pixels&)
TEXT (& = (%i, %i) mms\n&),
header.szlDevice.cx, header.szlDevice.cy,
header.szlMillimeters.cx,
header.szlMillimeters.cy) ;
i += wsprintf (pBuffer + i,
TEXT (&Size = %i, Records = %i, &)
TEXT (&Handles = %i, Palette entries = %i\n&),
header.nBytes, header.nRecords,
header.nHandles, header.nPalEntries) ;
// Include the metafile description, if present
if (iLength)
i += wsprintf (pBuffer + i, TEXT (&Description = &)) ;
GetEnhMetaFileDescription (hemf, iLength, pBuffer + i) ;
pBuffer [lstrlen (pBuffer)] = '\t' ;
MessageBox (hwnd, pBuffer, TEXT (&Metafile Properties&), MB_OK) ;
free (pBuffer) ;
return 0 ;
IDM_EDIT_COPY:
IDM_EDIT_CUT:
if (!hemf)
return 0 ;
// Transfer metafile copy to the clipboard
hemfCopy = CopyEnhMetaFile (hemf, NULL) ;
OpenClipboard (hwnd) ;
EmptyClipboard () ;
SetClipboardData (CF_ENHMETAFILE, hemfCopy) ;
CloseClipboard () ;
if (LOWORD (wParam) == IDM_EDIT_COPY)
return 0 ;
// fall through if IDM_EDIT_CUT
IDM_EDIT_DELETE:
DeleteEnhMetaFile (hemf) ;
hemf = NULL ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
IDM_EDIT_PASTE:
OpenClipboard (hwnd) ;
hemfCopy = GetClipboardData (CF_ENHMETAFILE) ;
CloseClipboard () ;
if (hemfCopy && hemf)
DeleteEnhMetaFile (hemf) ;
hemf = NULL ;
hemf = CopyEnhMetaFile (hemfCopy, NULL) ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case IDM_APP_ABOUT:
MessageBox ( hwnd,
TEXT (&Enhanced Metafile Viewer\n&)
TEXT (&(c) Charles Petzold, 1998&),
szAppName, MB_OK) ;
return 0 ;
IDM_APP_EXIT:
SendMessage (hwnd, WM_CLOSE, 0, 0L) ;
return 0 ;
hdc = BeginPaint (hwnd, &ps) ;
if ( hPalette = CreatePaletteFromMetaFile (hemf))
SelectPalette (hdc, hPalette, FALSE) ;
RealizePalette (hdc) ;
GetClientRect (hwnd, &rect) ;
PlayEnhMetaFile (hdc, hemf, &rect) ;
if (hPalette)
DeleteObject (hPalette) ;
EndPaint (hwnd, &ps) ;
return 0 ;
WM_QUERYNEWPALETTE:
if (!hemf || !(hPalet

我要回帖

更多关于 集显占用大量内存 的文章

 

随机推荐