回 帖 发 新 帖 刷新版面

主题:[原创]WMF 文件的数据格式以及将绘图保存为WMF图像的代码

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      总是=0
0014    校验和    Integer
-------------------------------------------------------------------------------
  说明:在资源管理器左侧的“详细信息”中显示的“尺寸”,宽度尺寸就是用Right值减去Left值、高度尺寸就是用Botton值减去Top值得到的。


2.WMF文件头,18字节:
---------------------------------------------------------------------
偏移量  名称       数据类型 说明
---------------------------------------------------------------------
0016    文件类型     Integer    0=内存图元文件, 1=磁盘图元文件
0018    文件头大小   Integer    总是=9
001A    版本         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         0037
AbortDoc              0052   取消一份文档的打印
CreatePalette         00F7   建立逻辑色彩调色板
SetBkMode             0102   指定填充方式
setmapmode            0103   设置设备场景的映射模式
SetROP2               0104   设置绘图模式
SetRelabs             0105
SetPolyFillMode       0106   设置多边形的填充模式
SetStretchBltMode     0107   指定函数的伸缩模式
SetTextCharacterExtra 0108   指定要在描绘的文本间插入的额外间距
RestoreDC             0127   从堆栈恢复一个原先保存的设备场景
INVERTREGION          012A
PAINTREGION           012B
SELECTCLIPREGION      012C
SelectObject          012D   选入图形对象到设备场景
SetTextAlign          012E   设置文本对齐方式
Resizepalette         0139   修改逻辑调色板大小
DIBCREATEPATTERNBRUSH 0142
DeleteObject          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          06FF
Arc                   0817   画一个圆弧
Pie                   081A   画一个饼图
Chord                 0830   画一个弦
BitBlt                0922   将位图从一个设备场景复制到另一个
DIBBITBLT             0940
ExtTextOut            0A32   文本描绘
StretchBlt            0B23   将位图从一个设备场景缩放到另一个
DIBSTRETCHBLT         0B41
SETDIBTODEV           0D33
StretchDIB            0F43   将位图部分数据复制到指定的设备场景
------------------------------------------------------------------

回复列表 (共5个回复)

沙发

二、将绘画保存为WMF文件的源代码举例(画一个椭圆,在椭圆中打印“矢量图像”4个字):
  在窗体上添加1个按纽,3个图片框,Picture1 用来观看原图形(Width=3090,Height=1455),Picture2 用来观看采用向量方式放大的图形(宽、高要设置得大一些),Picture3 用来观看采用位图方式放大的图形(宽、高设置得与Picture2 一样大)。

Option Explicit

Private 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 LOGFONT
End Type
Private Type SIZE
  cx As Long
  cy As Long
End Type

Private Declare Function lcreat Lib "kernel32" Alias "_lcreat" (ByVal lpPathName As String, ByVal iAttribute As Long) As Long
Private Declare Function lclose Lib "kernel32" Alias "_lclose" (ByVal hFile As Long) As Long
Private Declare Function lopen Lib "kernel32" Alias "_lopen" (ByVal lpPathName As String, ByVal iReadWrite As Long) As Long
Private Declare Function CreateMetaFile Lib "gdi32" Alias "CreateMetaFileA" (ByVal lpString As String) As Long
Private Declare Function CloseMetaFile Lib "gdi32" (ByVal hMF As Long) As Long
Private Declare Function GetMetaFileBitsEx Lib "gdi32" (ByVal hMF As Long, ByVal nSize As Long, lpvData As Long) As Long 'Any) As Long
Private Declare Function GlobalAlloc Lib "kernel32" (ByVal wFlags As Long, ByVal dwBytes As Long) As Long
Private Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function lwrite Lib "kernel32" Alias "_lwrite" (ByVal hFile As Long, ByVal lpBuffer As Any, ByVal wBytes As Long) As Long
Private Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function GlobalFree Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function DeleteMetaFile Lib "gdi32" (ByVal hMF As Long) As Long

Private Declare Function SetTextColor Lib "gdi32" (ByVal hdc As Long, ByVal crColor As Long) As Long
Private 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 Long
Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private 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 Long
Private 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 Long

Private 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 Long
Private Declare Function PlayMetaFile Lib "gdi32" (ByVal hdc As Long, ByVal hMF As Long) As Long
Private Declare Function SetMapMode Lib "gdi32" (ByVal hdc As Long, ByVal nMapMode As Long) As Long
Private Declare Function SetViewportExtEx Lib "gdi32" (ByVal hdc As Long, ByVal nX As Long, ByVal nY As Long, lpSize As SIZE) As Long
Private Declare Function SetWindowExtEx Lib "gdi32" (ByVal hdc As Long, ByVal nX As Long, ByVal nY As Long, lpSize As SIZE) As Long

Private Sub Command1_Click()
Dim dc As Long, hWMF As Long, DCsize As SIZE, font As LOGFONT
Dim hFont As Long
Dim fhnd As Long
Dim mfinfosize As Long
Dim mfglbhnd As Long
Dim gptr As Long

fhnd = 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)) + 1
font.lfHeight = 30
font.lfWidth = 15
font.lfCharSet = 1
hFont = CreateFontIndirect(font)    '创建逻辑字体
SelectObject dc, hFont              '把逻辑字体选入设备场景
SetTextColor dc, vbRed              '设置字符为红色
TextOut dc, 40, 30, "矢量图像", LenB(StrConv("矢量图像", vbFromUnicode)) '在设备场景中打印文本
hWMF = CloseMetaFile(dc)            '关闭图元文件设备场景,返回向量图像句柄

板凳


Picture1.Cls
Picture2.Cls
Picture3.Cls
PlayMetaFile 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.hdc
SetMapMode 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    '将向量图像数据复制到内存缓冲区gptr
lwrite fhnd, ByVal gptr, mfinfosize '将gptr内存缓冲区中的mfinfosize个数据写入文件fhnd
GlobalUnlock mfglbhnd               '内存块解锁
GlobalFree mfglbhnd                 '释放全局内存块
DeleteMetaFile hWMF                 '删除设备场景
lclose fhnd                         '关闭图元文件
End Sub


  参考代码:用GDI 函数画矩形和正弦曲线,你可以用这段代码替换上面那段画椭圆和打印文字的代码(请自行声明API函数):

Const cycle = 200  '周期
Const extent = 40  '振幅
Const posX = 45    'X轴位置
Const Pi = 6.2832
Dim i As Integer
Rectangle 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 LOGFONT
dc = CreateMetaFile("D:\Temp\Image.wmf") '在磁盘上创建一个图元文件

Ellipse dc, 0, 0, 200, 90 
RtlMoveMemory font.lfFaceName(0), ByVal CStr("宋体"), LenB(StrConv("宋体", vbFromUnicode)) + 1
font.lfHeight = 30
font.lfWidth = 15
font.lfCharSet = 1
hFont = CreateFontIndirect(font) 
SelectObject dc, hFont 
SetTextColor dc, vbRed 
TextOut dc, 40, 30, "矢量图像", LenB(StrConv("矢量图像", vbFromUnicode)) '在图元文件中打印文本

hWMF = CloseMetaFile(dc)
DeleteMetaFile hWMF
End Sub

3 楼

由此我们可以总结出将绘画保存为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 00
0010: 00 00 07 00 00 00 18 04 5A 00 C8 00 00 00 00 00
0020: 1C 00 00 00 FB 02 1E 00 0F 00 00 00 00 00 00 00
0030: 00 00 00 01 00 00 00 00 CB CE CC E5 00 19 C5 77
0040: 40 00 00 00 9F 11 0A 5F 16 43 C5 77 1F 43 C5 77
0050: 20 C0 C7 77 00 00 30 00 04 00 00 00 2D 01 00 00
0060: 05 00 00 00 09 02 FF 00 00 00 0A 00 00 00 21 05
0070: 08 00 CA B8 C1 BF CD BC CF F1 1E 00 28 00 03 00
0080: 00 00 00 00

0000-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=0
0020-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:参数值为0
0060-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 楼

LZ相当有耐心,顶一个。

5 楼

好文章,顶上。

Private Declare Function lstrcpyn Lib "kernel32" Alias "lstrcpynA" (lpString1 As Any, ByVal lpString2 As Any, ByVal iMaxLength As Long) As Long

lstrcpyn font.lfFaceName(0), "宋体", 32

我来回复

您尚未登录,请登录后再回复。点此登录或注册