主题:[原创]WMF 文件的数据格式以及将绘图保存为WMF图像的代码
一江秋水
[专家分:9680] 发布于 2010-01-15 10:19:00
WMF 文件的数据格式以及将绘图保存为WMF图像的代码 Wmf 是 Windows Metafile 的缩写,简称图元文件,它是微软公司定义的一种 Windows平台下的图形文件格式,是一个16位矢量图元文件格式,可以同时包含矢量信息和位图信息。图元文件与其它图形文件的最大区别在于:图元文件保存的是一系列用来重建图片的GDI函数(注:GDI函数是API 函数中的绘图函数)和参数,而其它图形文件则保存的是用以构成图像的像素数据。 WMF 图元文件是与设备无关的,它的图象完全由GDI 函数来完成,因此在建立图元文件时,不能实现即画即得,而是将GDI 调用记录在图元文件中,之后,在GDI 环境中重新执行,才可显示图象。正因为没有保存构成图像的像素数据,WMF 图元文件所占的磁盘空间比其它任何格式的图形文件都要小,形成文件的速度要远大于其它图形格式的文件。又正是因为显示图像时需要调用一系列的GDI 函数,所以WMF 图元文件的显示速度要比其它格式的图象文件慢。 WMF 文件又分为内存图元文件和磁盘图元文件。内存图元文件是仅在内存某一个区域进行操作并存放的,大多用于图象的绘制、拷贝或者进程间的剪切板图形共享;磁盘图元文件则主要用于将绘制图象保存到磁盘文件中,以便事后重看。 WMF 文件还可分为一般图元文件和“可确定位置”的图元文件。一般图元文件包含绘制直线、曲线和文本等记录,而可确定位置的图元文件还可以包含位图化图像。如果放大一般图元文件,那么得到的结果是放大的直线、曲线和其他输出,其抗锯齿能力很好。如果放大包含位图的图元文件,那么得到的结果是相对块状放大的位图,其抗锯齿能力要差一些。但一般图元文件也有不足之处,那就是:当将一个包含绘图命令的图元文件加载到一个应用程序时,可能会发现椭圆和文本不能正常地绘制出来,这是因为并不是所有程序都能够理解所有图元文件命令。一、数据结构 WMF 文件的结构主要有两种,包括: ①WMF 文件头: 如果是可确定位置的图元文件,则还必须在WMF 文件头之前加上一个“位置头”。 ②图元文件记录:其长度可变,记录中包含了建立图象时所需要的GDI 函数及参数。 下面以“可确定位置的图元文件”为例,介绍WMF文件的数据结构:1.位置头,22字节:-------------------------------------------------------------------------------偏移量 名称 数据类型 说明-------------------------------------------------------------------------------0000 键 Long 总是=D7CDC69A,表示这是一个可确定位置的图元文件0004 保留的 Integer 总是=0 (以下4个数据构成位图化图像的原始大小(逻辑单位) 0006 Left Integer 左上角X坐标0008 Top Integer 左上角Y坐标000A Right Integer 右下角X坐标000C Bottom Integer 右下角Y坐标000E 图像单元 Integer 每英寸逻辑单位数目0010 保留的 Long 总是=00014 校验和 Integer------------------------------------------------------------------------------- 说明:在资源管理器左侧的“详细信息”中显示的“尺寸”,宽度尺寸就是用Right值减去Left值、高度尺寸就是用Botton值减去Top值得到的。2.WMF文件头,18字节:---------------------------------------------------------------------偏移量 名称 数据类型 说明---------------------------------------------------------------------0016 文件类型 Integer 0=内存图元文件, 1=磁盘图元文件0018 文件头大小 Integer 总是=9001A 版本 Integer 0300=支持设备无关位图,0100=不支持001C 文件大小 Long 文件的总长度0020 对象个数 Integer 文件中可以同时使用的对象数目0022 最大记录大小 Integer 文件中最大记录的长度0024 保留的 Long 未使用,总是=0--------------------------------------------------------------------- 说明:WMF 文件中的长度值是以“字数”为单位的(一个字=2字节),“文件头大小”、“文件大小”、“最大记录大小”,以及下面将要讲到的“文件记录大小”,都是如此。例如“文件头大小”总是=9,而 9×2=18字节。当然,这只是笔者的推测:这个推测还需验证。3.文件记录:长度不定 紧接文件头的是文件记录,这是图元文件中最重要的组成部分,它表明当前图元文件所描述图像的基本构成信息,每个记录都是由记录头,定义函数的相应代码以及函数调用所需要的参数说明三部分组成,一个文件记录中只记录了一个GDI 函数及对应的参数。大多数情况下,记录中包含的参数恰好为需要传送到相应GDI 函数中的数据值,但对于某些包含结构的复杂类型定义,这些参数值也可能是非常复杂的数据编码。 文件记录是一个接一个地存放着的,它的结构如下:----------------------------------------------------------------偏移量 名称 数据类型 说明----------------------------------------------------------------0028 大小 Long 该记录的长度002C 函数 Integer GDI函数编号002E 参数表 欲传递给函数的参数值,整型和长整型参数 均为2字节,逻辑型或字节型参数为1字节---------------------------------------------------------------- 说明:①记录的长度值,正如前面笔者所推测的,是指有多少个字。②文件记录中的GDI 函数并不是它们的函数名称,而是对应的函数编号,根据这个编号来调用函数。③有些API函数需用句柄,但句柄似乎没有保存。④参数的排列规律为:如果是一般的参数,反序排列,如果是一个结构,顺序排列。4.GDI函数在WMF文件中的16进制编号:------------------------------------------------------------------GDI函数名 编号 作用------------------------------------------------------------------savedc 001E 将设备场景状态保存到堆栈Realizepalette 0035 将逻辑调色板映像为系统调色板SETPALENTRIES 0037AbortDoc 0052 取消一份文档的打印CreatePalette 00F7 建立逻辑色彩调色板SetBkMode 0102 指定填充方式setmapmode 0103 设置设备场景的映射模式SetROP2 0104 设置绘图模式SetRelabs 0105SetPolyFillMode 0106 设置多边形的填充模式SetStretchBltMode 0107 指定函数的伸缩模式SetTextCharacterExtra 0108 指定要在描绘的文本间插入的额外间距RestoreDC 0127 从堆栈恢复一个原先保存的设备场景INVERTREGION 012APAINTREGION 012BSELECTCLIPREGION 012CSelectObject 012D 选入图形对象到设备场景SetTextAlign 012E 设置文本对齐方式Resizepalette 0139 修改逻辑调色板大小DIBCREATEPATTERNBRUSH 0142DeleteObject 01F0 删除GDI对象CreatePatternBrush 01F9 创建一个刷子SetBkColor 0201 设置背景颜色SetTextColor 0209 设置文本颜色SetTextJustification 020A 指定一个文本行应占据的额外空间SetWindowOrg 020B 设置设备场景窗口起点SetWindowExt 020C 设置设备场景窗口范围SetViewportOrg 020D 设置设备场景视口起点SetViewportExt 020E 设置设备场景视口范围OffsetWindowOrg 020F 平移设备场景窗口起点OffsetViewportOrg 0211 平移设备场景视口区域LineTo 0213 用当前画笔画一条线MoveTo 0214 为设备场景指定一个新的当前画笔位置OffsetClipRgn 0220 按指定量平移设备场景剪裁区SetMapperFlags 0231 选择与目标设备的纵横比相符的光栅字体SelectPalette 0234 选定调色板CreatePenIndirect 02FA 根据指定的LOGPEN结构创建一个画笔CreateFontIndirect 02FB 用指定的属性创建一种逻辑字体CreateBrushIndirect 02FC 在LOGBRUSH结构的基础上创建一个刷子Polygon 0324 描绘一个多边形Polyline 0325 用当前画笔描绘一系列线段ScaleWindowExt 0410 缩放设备场景窗口范围ScaleViewportExt 0412 缩放设备场景视口范围ExcludeClipRect 0415 从设备场景剪裁区中去掉一个矩形区IntersectClipRect 0416 为指定设备定义一个新的剪裁区Ellipse 0418 描绘一个椭圆FloodFill 0419 用选定的刷子在设备场景中填充一个区域Rectangle 041B 用选定的画笔描绘矩形SetPixel 041F 在设备场景中画点(像素的RGB值)AnimatePalette 0436 替换逻辑调色板中的项目TextOut 0521 文本绘图PolyPolygon 0538 用选定画笔描绘多边形ExtFloodFill 0548 用选择的刷子填充一个区域RoundRect 061C 画一个圆角矩形PatBlt 061D 用一个图案填充指定的设备场景Escape 0626 设备控制CREATEREGION 06FFArc 0817 画一个圆弧Pie 081A 画一个饼图Chord 0830 画一个弦BitBlt 0922 将位图从一个设备场景复制到另一个DIBBITBLT 0940ExtTextOut 0A32 文本描绘StretchBlt 0B23 将位图从一个设备场景缩放到另一个DIBSTRETCHBLT 0B41SETDIBTODEV 0D33StretchDIB 0F43 将位图部分数据复制到指定的设备场景------------------------------------------------------------------
本帖地址:
http://bbs.pfan.cn/post/316210.html
回复列表 (共5个回复)
沙发
一江秋水 [专家分:9680] 发布于 2010-01-15 10:23:00
二、将绘画保存为WMF文件的源代码举例(画一个椭圆,在椭圆中打印“矢量图像”4个字): 在窗体上添加1个按纽,3个图片框,Picture1 用来观看原图形(Width=3090,Height=1455),Picture2 用来观看采用向量方式放大的图形(宽、高要设置得大一些),Picture3 用来观看采用位图方式放大的图形(宽、高设置得与Picture2 一样大)。Option ExplicitPrivate Type LOGFONT lfHeight As Long '字体高度 lfWidth As Long '字体平均宽度 lfEscapement As Long '输出方向与当前坐标系X轴之间的字体旋转的角度,以1/10度为单位 lfOrientation As Long '每个字符与当前坐标系X轴之间的角度,以1/10度为单位 lfWeight As Long '是否粗体(范围为0-1000,字体加重程度:标准=400,加重=700) lfItalic As Byte '是否斜体(不为0表示采用斜体字) lfUnderline As Byte '是否有下划线(不为0表示带下划线) lfStrikeOut As Byte '是否有中划线(不为0表示带中划线) lfCharSet As Byte '字符集 lfOutPrecision As Byte '输出精度 lfClipPrecision As Byte '剪裁精度 lfQuality As Byte '品质 lfPitchAndFamily As Byte '间距 lfFaceName(0 To 31) As Byte '字体名称Private Type LOGFONTEnd TypePrivate Type SIZE cx As Long cy As LongEnd TypePrivate Declare Function lcreat Lib "kernel32" Alias "_lcreat" (ByVal lpPathName As String, ByVal iAttribute As Long) As LongPrivate Declare Function lclose Lib "kernel32" Alias "_lclose" (ByVal hFile As Long) As LongPrivate Declare Function lopen Lib "kernel32" Alias "_lopen" (ByVal lpPathName As String, ByVal iReadWrite As Long) As LongPrivate Declare Function CreateMetaFile Lib "gdi32" Alias "CreateMetaFileA" (ByVal lpString As String) As LongPrivate Declare Function CloseMetaFile Lib "gdi32" (ByVal hMF As Long) As LongPrivate Declare Function GetMetaFileBitsEx Lib "gdi32" (ByVal hMF As Long, ByVal nSize As Long, lpvData As Long) As Long 'Any) As LongPrivate Declare Function GlobalAlloc Lib "kernel32" (ByVal wFlags As Long, ByVal dwBytes As Long) As LongPrivate Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Long) As LongPrivate Declare Function lwrite Lib "kernel32" Alias "_lwrite" (ByVal hFile As Long, ByVal lpBuffer As Any, ByVal wBytes As Long) As LongPrivate Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Long) As LongPrivate Declare Function GlobalFree Lib "kernel32" (ByVal hMem As Long) As LongPrivate Declare Function DeleteMetaFile Lib "gdi32" (ByVal hMF As Long) As LongPrivate Declare Function SetTextColor Lib "gdi32" (ByVal hdc As Long, ByVal crColor As Long) As LongPrivate Declare Sub RtlMoveMemory Lib "kernel32" (lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)Private Declare Function CreateFontIndirect Lib "gdi32" Alias "CreateFontIndirectA" (lpLogFont As LOGFONT) As LongPrivate Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As LongPrivate Declare Function TextOut Lib "gdi32" Alias "TextOutA" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal lpString As String, ByVal nCount As Long) As LongPrivate Declare Function Ellipse Lib "gdi32" (ByVal hdc As Long, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As LongPrivate Declare Function StretchBlt Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal nSrcWidth As Long, ByVal nSrcHeight As Long, ByVal dwRop As Long) As LongPrivate Declare Function PlayMetaFile Lib "gdi32" (ByVal hdc As Long, ByVal hMF As Long) As LongPrivate Declare Function SetMapMode Lib "gdi32" (ByVal hdc As Long, ByVal nMapMode As Long) As LongPrivate Declare Function SetViewportExtEx Lib "gdi32" (ByVal hdc As Long, ByVal nX As Long, ByVal nY As Long, lpSize As SIZE) As LongPrivate Declare Function SetWindowExtEx Lib "gdi32" (ByVal hdc As Long, ByVal nX As Long, ByVal nY As Long, lpSize As SIZE) As LongPrivate Sub Command1_Click()Dim dc As Long, hWMF As Long, DCsize As SIZE, font As LOGFONTDim hFont As LongDim fhnd As LongDim mfinfosize As LongDim mfglbhnd As LongDim gptr As Longfhnd = lcreat("D:\Temp\Image.wmf", 0) '在磁盘上创建一个图元文件lclose fhnd '关闭图元文件fhnd = lopen("D:\Temp\Image.wmf", 2) '打开图元文件dc = CreateMetaFile(vbNullString) '在内存中创建一个没有“位置头”的图元文件的设备场景'用 GDI 函数画椭圆和打印文字Ellipse dc, 0, 0, 200, 90 '在设备场景中画一个椭圆RtlMoveMemory font.lfFaceName(0), ByVal CStr("宋体"), LenB(StrConv("宋体", vbFromUnicode)) + 1font.lfHeight = 30font.lfWidth = 15font.lfCharSet = 1hFont = CreateFontIndirect(font) '创建逻辑字体SelectObject dc, hFont '把逻辑字体选入设备场景SetTextColor dc, vbRed '设置字符为红色TextOut dc, 40, 30, "矢量图像", LenB(StrConv("矢量图像", vbFromUnicode)) '在设备场景中打印文本hWMF = CloseMetaFile(dc) '关闭图元文件设备场景,返回向量图像句柄
板凳
一江秋水 [专家分:9680] 发布于 2010-01-15 10:23:00
Picture1.ClsPicture2.ClsPicture3.ClsPlayMetaFile Picture1.hdc, hWMF '将向量图像显示在picture1中StretchBlt Picture3.hdc, 0, 0, Picture3.ScaleWidth, Picture3.ScaleHeight, Picture1.hdc, 0, 0, Picture1.ScaleWidth, Picture1.ScaleHeight, vbSrcCopy '将图像以位图放大方式显示在picture3中dc = Picture2.hdcSetMapMode dc, 8 '设置Picture2的映射模式:视口和窗口范围任意SetWindowExtEx dc, Picture1.ScaleWidth, Picture1.ScaleHeight, DCsize '设置Picture2窗口范围SetViewportExtEx dc, Picture2.ScaleWidth, Picture2.ScaleHeight, DCsize '设置Picture2视口范围PlayMetaFile dc, hWMF '将图像以向量放大方式显示在picture2中mfinfosize = GetMetaFileBitsEx(hWMF, 0, ByVal 0) '获取向量图像的大小mfglbhnd = GlobalAlloc(&H42, mfinfosize) '在堆栈中分配内存gptr = GlobalLock(mfglbhnd) '锁定内存GetMetaFileBitsEx hWMF, mfinfosize, ByVal gptr '将向量图像数据复制到内存缓冲区gptrlwrite fhnd, ByVal gptr, mfinfosize '将gptr内存缓冲区中的mfinfosize个数据写入文件fhndGlobalUnlock mfglbhnd '内存块解锁GlobalFree mfglbhnd '释放全局内存块DeleteMetaFile hWMF '删除设备场景lclose fhnd '关闭图元文件End Sub 参考代码:用GDI 函数画矩形和正弦曲线,你可以用这段代码替换上面那段画椭圆和打印文字的代码(请自行声明API函数):Const cycle = 200 '周期Const extent = 40 '振幅Const posX = 45 'X轴位置Const Pi = 6.2832Dim i As IntegerRectangle dc, 0, 0, cycle, posX * 2 '画矩形For i = 0 To cycle LineTo dc, i, posX - Sin(((i Mod cycle) * Pi) / cycle) * extent '画曲线Next 执行程序后,你可以看到,picture3的图像粗糙有锯齿,而picture2的图像线条流畅,没有锯齿。 另外,由于D:\Temp\Image.wmf 这个文件没有“位置头”,但在资源管理器和画图程序中显示其宽高分别为200、90,这说明对于一般的WMF 图元文件,Windows会自动计算绘图中的所有有关尺寸,并取最大的尺寸作为图片的尺寸。 如果你只想把图形直接保存到磁盘文件,不需要即时显示或放大,那么可采用更简单的代码(请自行删除多余的API函数和结构):Private Sub Command1_Click()Dim dc As Long, hFont As Long, hWMF As Long, font As LOGFONTdc = CreateMetaFile("D:\Temp\Image.wmf") '在磁盘上创建一个图元文件Ellipse dc, 0, 0, 200, 90 RtlMoveMemory font.lfFaceName(0), ByVal CStr("宋体"), LenB(StrConv("宋体", vbFromUnicode)) + 1font.lfHeight = 30font.lfWidth = 15font.lfCharSet = 1hFont = CreateFontIndirect(font) SelectObject dc, hFont SetTextColor dc, vbRed TextOut dc, 40, 30, "矢量图像", LenB(StrConv("矢量图像", vbFromUnicode)) '在图元文件中打印文本hWMF = CloseMetaFile(dc)DeleteMetaFile hWMFEnd Sub
3 楼
一江秋水 [专家分:9680] 发布于 2010-01-15 10:24:00
由此我们可以总结出将绘画保存为WMF文件的步骤(注意所有的步骤都是调用API函数):①在磁盘上用API 函数创建一个WMF 文件。②直接在这个文件上用API函数画图或打印文字③关闭WMF 文件④删除WMF 文件句柄三、D:\Temp\Image.wmf 的数据分析 用Hex编辑器打开这个WMF 文件,它的全部数据如下(你的数据可能会有差异):0000: 01 00 09 00 00 03 42 00 00 00 01 00 1C 00 00 000010: 00 00 07 00 00 00 18 04 5A 00 C8 00 00 00 00 000020: 1C 00 00 00 FB 02 1E 00 0F 00 00 00 00 00 00 000030: 00 00 00 01 00 00 00 00 CB CE CC E5 00 19 C5 770040: 40 00 00 00 9F 11 0A 5F 16 43 C5 77 1F 43 C5 770050: 20 C0 C7 77 00 00 30 00 04 00 00 00 2D 01 00 000060: 05 00 00 00 09 02 FF 00 00 00 0A 00 00 00 21 050070: 08 00 CA B8 C1 BF CD BC CF F1 1E 00 28 00 03 000080: 00 00 00 000000-0012:WMF文件头,其中: 0000-0001:数据=1,表示这是一个磁盘图元文件 0002-0003:这个数据总是=9 0004-0005:版本号=300,支持设备无关位图 0006-0009:文件总长度=42个字(10进制的66),66×2=132字节 000A-000B:文件中有1个对象 000C-000D:最大记录的长度=1C 个字 000E-0011:保留的0012-001F:第1个文件记录,其中: 0012-0015:记录长度=7 0016-0017:调用编号为418的API函数(Ellipse,下面的参数为反序排列) 0018-0019:椭圆的y2=5A(10进制的90) 001A-001B:椭圆的x2=C8(10进制的200) 001C-001D:椭圆的y1=0 001E-001F:椭圆的x1=00020-0057:第2个文件记录,其中: 0020-0023:记录长度=1C 0024-0025:调用编号为2FB的API函数(CreateFontIndirect,它的参数是一个结构,顺序排列) 0026-0027:字体高=1E(10进制的30) 0028-0029:字体宽=F(10进制的15) 002A-002F:3个整型数据参数=0,未设置 0030-0032:3个字节型参数=0,未设置 0033-0034:字符集=1(字节型参数) 0035-0037:3个字节型参数=0,未设置 0038-003B:4个字节型参数=“宋体”字符的编码 003C-0057:这些数据用途不明,也许是前面讲过的“非常复杂的数据编码”?0058-005F:第3文件记录,其中: 0058-005B:记录长度=4 005C-005D:调用编号为12D的API函数(SelectObject) 005E-005F:参数值为00060-0069:第4个文件记录,其中: 0060-0063:记录长度=5 0064-0065:调用编号为209的API函数(SetTextColor) 0066-0069:参数=FF000000,这是红色的RGB值006A-007D:第5个文件记录,其中: 006A-006D:记录长度=A 006E-006F:调用编号为521的API函数(TextOut,下面的参数为反序排列) 0070-0071:欲打印的字串长度为8个字节 0072-0079:欲打印的字符串“矢量图像”的编码 007A-007B:打印的Y坐标=1E(10进制的30) 007C-007D:打印的X坐标=28(10进制的40)007E-0083:笔者猜测这6个字节可能是矢量补充数据,又或者是WMF文件尾,存疑。
4 楼
天天学习 [专家分:4570] 发布于 2010-01-15 11:56:00
LZ相当有耐心,顶一个。
5 楼
bcahzvip [专家分:6040] 发布于 2010-01-17 02:22:00
好文章,顶上。Private Declare Function lstrcpyn Lib "kernel32" Alias "lstrcpynA" (lpString1 As Any, ByVal lpString2 As Any, ByVal iMaxLength As Long) As Longlstrcpyn font.lfFaceName(0), "宋体", 32
我来回复
您尚未登录,请登录后再回复。点此登录或注册