一、什么是 WMF?
WMF(Windows Metafile,Windows 元文件)是微软开发的一种矢量图形存储格式。它不是一个「画好的图片」,而是一份绘图指令的记录——里面存储了一串 Windows GDI(图形设备接口)的绘图命令,播放这些命令就能重现图像。
核心特点:
-
指令记录:不是像素矩阵,而是一系列 GDI 函数调用
-
矢量本质:坐标和几何计算决定了图形内容,缩放不失真
-
Windows 原生:深度集成 Windows 系统,Office 默认使用
-
包含位图:WMF 可同时存储矢量指令和位图数据(BitBlt 记录)
-
可回放:通过 「播放元文件」API 将指令重新执行一遍,再现原始图形
一句话理解:WMF 是 Windows 的「录屏」文件——录的不是像素,而是 GDI 绘图操作。
二、WMF vs 位图 vs SVG
| 对比项 | WMF(元文件) | PNG/JPG(位图) | SVG |
|---|---|---|---|
| 本质 | GDI 指令序列(记录 + 回放) | 像素矩阵 | XML 文本描述图形 |
| 平台 | Windows 原生 | 跨平台 | 跨平台(Web 标准) |
| 放大效果 | 矢量部分不失真 | 马赛克 / 模糊 | 始终清晰,不失真 |
| 可编辑性 | 需 Windows GDI 或转换工具 | 需图像编辑软件 | 任意文本编辑器 |
| 内嵌位图 | ✅ 原生支持(BitBlt) | 本身就是位图 | 支持 <image> 嵌入 |
| 适用场景 | Windows 应用、Office 文档、剪贴板 | 照片、截图 | Web 图标、图表、响应式图形 |
| 文件大小 | 简单图形较小,含位图时大 | 与分辨率正相关 | 简单图形极小 |
三、WMF 的三种变体
WMF 在实际使用中有三种形态,它们的区别在于文件头部:
| 类型 | 说明 | 头部结构 |
|---|---|---|
| 标准 WMF | 最基本的格式,16 位 GDI 记录 | 18 字节 META_HEADER |
| Placeable WMF | 带有缩放和定位信息的版本,文件扩展名 .APM 或 .WMF | 22 字节前置头部 + 标准头部 |
| 剪贴板 WMF | 用于剪贴板交换,携带裁剪区域信息 | 8~16 字节前置头部 + 标准头部 |
💡 Placeable WMF 是最常见的磁盘存储格式。它的前置头部记录了图形的边界矩形和缩放比例(单位:twip = 1/1440 英寸),应用程序读取后可以正确放置和缩放图像。
四、WMF 文件结构
一个 WMF 文件由 头部 + 一系列记录 + 结尾标记 组成:
┌──────────────────────────────┐
│ Placeable Header (22字节) │ ← 可选,Placeable 变体才有
├──────────────────────────────┤
│ META_HEADER (18字节) │ ← 标准头部,必选
│ - 文件类型、版本 │
│ - 文件大小(WORD 数) │
│ - 对象数量、最大记录大小 │
├──────────────────────────────┤
│ Record #1 │ ← GDI 绘图指令
│ Record #2 │
│ Record #3 │
│ ... │
├──────────────────────────────┤
│ META_EOF 记录 │ ← 结束标记
└──────────────────────────────┘
META_HEADER 关键字段:
| 字段 | 含义 |
|---|---|
| FileType | 元文件类型(磁盘 = 0x0001,内存 = 0x0002) |
| HeaderSize | 头部大小(标准 = 9 WORD = 18 字节) |
| Version | 版本号(Windows 3.0+ = 0x0300) |
| FileSize | 整个文件大小(以 WORD 为单位) |
| NumOfObjects | 文件中创建的对象数量(画笔、画刷等) |
| MaxRecordSize | 最大单条记录的大小 |
五、WMF 记录系统——元文件的灵魂
看完静态的文件结构,接下来看动态的部分——每一条记录是如何编码一次 GDI 调用的。
5.1 记录基本格式
每条 WMF 记录都遵循统一结构:
typedef struct _StandardMetaRecord {
DWORD Size; // 记录总大小(WORD 为单位),最小 3
WORD Function; // GDI 函数编号
WORD Parameters[]; // 参数数组(逆序存储!)
} WMFRECORD;
关键细节:
-
Size:包含 Size 字段自身,最小值为 3
-
Function:低字节是具体 GDI 函数编号,高字节代表参数 WORD 数量。
0x0213= LineTo 函数(0x13),2 个参数 -
Parameters:参数按逆序存储(和 GDI 函数调用顺序相反)
举例——画一条到 (100, 50) 的直线:
Size 5 // 5 个 WORD
Function 0x0213 // LineTo = 0x0213
Parameters[0] 50 // Y 坐标(逆序!)
Parameters[1] 100 // X 坐标
// 对应的 GDI 调用:LineTo(hDC, 100, 50);
5.2 绘图命令
以下是最常用的 WMF 绘图记录:
| 记录名称 | 编号 | 功能 |
|---|---|---|
| MoveTo | 0x0214 | 移动画笔到指定坐标 |
| LineTo | 0x0213 | 从当前位置画线到目标坐标 |
| Rectangle | 0x041B | 画矩形 |
| Ellipse | 0x0418 | 画椭圆(或圆) |
| RoundRect | 0x061C | 画圆角矩形 |
| Arc | 0x0817 | 画圆弧 |
| Chord | 0x0830 | 画弓形(弧 + 弦) |
| Pie | 0x081A | 画扇形 |
| Polygon | 0x0324 | 画多边形 |
| Polyline | 0x0325 | 画折线 |
| TextOut | 0x0521 | 输出文本 |
5.3 对象创建命令
WMF 可以定义图形对象(画笔、画刷、字体等),之后在绘图命令中引用:
| 记录名称 | 编号 | 功能 |
|---|---|---|
| CreatePenIndirect | 0x02FA | 创建画笔(颜色、宽度、样式) |
| CreateBrushIndirect | 0x02FC | 创建画刷(纯色/图案填充) |
| CreateFontIndirect | 0x02FB | 创建字体 |
| CreateBitmapIndirect | 0x02FD | 创建位图 |
| SelectObject | 0x012D | 将对象选入设备上下文 |
| DeleteObject | 0x01F0 | 删除对象,释放资源 |
💡 对象句柄表:Windows 在内存中维护一个对象句柄表。每创建一个对象(如画笔),它就被分配一个句柄。后续的绘图命令通过 SelectObject 选择该画笔,绘图完成后用 DeleteObject 释放。
5.4 状态管理命令
| 记录名称 | 编号 | 功能 |
|---|---|---|
| SaveDC | 0x001E | 保存当前设备上下文状态 |
| RestoreDC | 0x0127 | 恢复到之前保存的状态 |
| SetBkColor | 0x0201 | 设置背景颜色 |
| SetBkMode | 0x0102 | 设置背景模式(透明/不透明) |
| SetTextColor | 0x0209 | 设置文字颜色 |
| SetROP2 | 0x0104 | 设置绘图模式(混合/覆盖等) |
六、坐标系与映射模式
WMF 的坐标系统比其他矢量格式更复杂,因为它需要适配设备无关的渲染目标。
6.1 逻辑单位:Twip
Placeable WMF 使用 twip 作为基本度量单位:
-
1 twip = 1/1440 英寸
-
720 twips = 0.5 英寸
-
1440 twips = 1 英寸
前置头部的 Inch 字段控制缩放比例:
| Inch 值 | 缩放效果 |
|---|---|
| 1440 | 1:1 原始大小 |
| 720 | 放大 2 倍 |
| 360 | 放大 4 倍 |
| 2880 | 缩小 2 倍 |
6.2 映射模式
剪贴板 WMF 和代码中可以使用不同的映射模式:
| 模式 | 含义 | 1 个单位 = |
|---|---|---|
| MM_TEXT | 文本映射(默认) | 1 像素 |
| MM_LOMETRIC | 低精度公制 | 0.1 mm |
| MM_HIMETRIC | 高精度公制 | 0.01 mm |
| MM_LOENGLISH | 低精度英制 | 0.01 英寸 |
| MM_HIENGLISH | 高精度英制 | 0.001 英寸 |
| MM_TWIPS | Twip 模式 | 1/1440 英寸 |
| MM_ISOTROPIC | 各向同性(保持纵横比) | 自定义 |
| MM_ANISOTROPIC | 各向异性(不保持纵横比) | 自定义 |
💡 SetMapMode 记录(0x0103)可以切换映射模式。日常生活中最常见的 WMF 文件(如 Office 导出的图形)使用 Placeable 格式,以 twip 为单位。
七、位图操作(BitBlt / StretchBlt)
WMF 区别于纯矢量格式的最大特点:它可以内嵌位图数据。
7.1 BitBlt 记录
BitBlt(Bit Block Transfer)将一块位图数据写入元文件:
// META_BITBLT 记录
{
Function: 0x0922,
RasterOp: SRCCOPY (直接复制),
Src: x=0, y=0, width=64, height=64,
Dest: (100, 100),
Bitmap: [像素数据数组]
}
Windows 3.0+ 使用 DIB(设备无关位图) 格式,可以跨设备正确显示颜色。
7.2 StretchBlt
StretchBlt 可以在绘制位图时进行缩放,让位图适配到任意大小的目标矩形中。
八、EMF——增强型元文件
EMF(Enhanced Metafile,增强型元文件)是 WMF 的 32 位升级版,从 Windows 95/NT 开始引入。
8.1 EMF vs WMF 对比
| 特性 | WMF | EMF |
|---|---|---|
| 位宽度 | 16 位 | 32 位 |
| 头部 | 独立的 META_HEADER | 头部作为第一条记录存储 |
| 字符集 | ANSI | Unicode |
| 记录格式 | Size(DWORD) + Function(WORD) | Function(DWORD) + Size(DWORD) |
| 参数 | 16 位 WORD 数组 | 32 位 DWORD 数组 |
| 颜色调色板 | 无独立调色板 | 可选,嵌入 EOF 记录 |
| 记录类型数量 | 约 70 种 | 约 97 种 |
| 内嵌描述 | 无 | 支持 Unicode 描述字符串 |
8.2 EMF 新增特性
-
颜色调色板:EOF 记录中可携带可编程颜色调色板,确保色彩还原准确
-
文件描述字符串:格式为
"应用名\0图片描述\0\0"(Unicode),可嵌入元数据 -
GDI Comment:可在文件中嵌入私有数据(甚至嵌入 PostScript 代码)
-
组支持:BeginGroup / EndGroup 记录可以将记录分组,并附加描述
-
更好的坐标系统:Bounds 和 Frame 两套坐标系统,分别描述绘图区域和显示框架
九、文本与颜色
除了几何图形,WMF 还要描述文本和颜色,这两类记录如下。
9.1 文本输出
WMF 中的文本操作通过 TextOut 和 ExtTextOut 记录实现:
| 记录 | 功能 |
|---|---|
| TextOut (0x0521) | 在指定位置输出文本 |
| ExtTextOut (0x0A32) | 增强文本输出(支持字符间距、裁剪矩形) |
| DrawText (0x062F) | 在矩形区域内格式化输出文本 |
| SetTextAlign (0x012E) | 设置文本对齐方式 |
| SetTextCharExtra (0x0108) | 设置字符间距 |
9.2 颜色模型
WMF 的颜色基于 RGB 三原色,每个颜色分量 8 位(0~255),存储在 COLORREF 结构中:
-
红色:0x000000FF → 255
-
绿色:0x0000FF00 → 65280(或 绿色 << 8)
-
蓝色:0x00FF0000 → 16711680(或 蓝色 << 16)
💡 WMF 不内置 sRGB / CMYK 颜色空间信息,颜色显示依赖回放设备的默认色彩配置。
十、WMF/EMF 的编程接口
Windows API 提供了完整的元文件创建、播放和保存功能:
10.1 WMF 相关 API
// 创建内存元文件 DC
HDC hMetaDC = CreateMetaFile(NULL);
// 在这个 DC 上绘图——GDI 调用会被记录
Rectangle(hMetaDC, 0, 0, 200, 100);
Ellipse(hMetaDC, 0, 0, 200, 100);
MoveToEx(hMetaDC, 0, 50, NULL);
LineTo(hMetaDC, 200, 50);
// 关闭 DC,获取元文件句柄
HMETAFILE hMF = CloseMetaFile(hMetaDC);
// 播放/回放元文件
PlayMetaFile(hDC, hMF); // 在指定 DC 上重现
// 保存到文件
CopyMetaFile(hMF, L"output.wmf");
// 释放资源
DeleteMetaFile(hMF);
10.2 EMF 相关 API
// 创建 EMF DC(需要引用设备上下文)
HDC hRefDC = GetDC(NULL); // 屏幕 DC 作为参考
HDC hEMFDC = CreateEnhMetaFile(hRefDC, L"output.emf", NULL, NULL);
// 绘图——和 WMF 一样的方式
Rectangle(hEMFDC, 0, 0, 300, 200);
TextOutW(hEMFDC, 10, 10, L"Hello EMF!", 10);
// 关闭并获取句柄
HENHMETAFILE hEMF = CloseEnhMetaFile(hEMFDC);
// 回放
PlayEnhMetaFile(hDC, hEMF, &rect);
// 释放
DeleteEnhMetaFile(hEMF);
ReleaseDC(NULL, hRefDC);
十一、实战:用 C++ 创建并播放一个 WMF
下面是一个完整示例——用 GDI 在 WMF 中画一个简单的流程图节点:
#include <windows.h>
int main() {
// 1. 创建元文件 DC
HDC hMetaDC = CreateMetaFile(L"simple.wmf");
// 2. 设置图形对象
HPEN hPen = CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
HBRUSH hBrush = CreateSolidBrush(RGB(100, 180, 255));
SelectObject(hMetaDC, hPen);
SelectObject(hMetaDC, hBrush);
// 3. 绘图——所有操作被记录到 WMF 中
// 画一个圆角矩形(流程图节点)
RoundRect(hMetaDC, 50, 30, 250, 100, 20, 20);
// 设置文字
SelectObject(hMetaDC, GetStockObject(SYSTEM_FONT));
SetTextColor(hMetaDC, RGB(255, 255, 255));
SetBkMode(hMetaDC, TRANSPARENT);
TextOut(hMetaDC, 90, 42, L"Hello WMF!", 10);
// 画箭头线
MoveToEx(hMetaDC, 150, 100, NULL);
LineTo(hMetaDC, 150, 160);
LineTo(hMetaDC, 145, 150);
MoveToEx(hMetaDC, 150, 160, NULL);
LineTo(hMetaDC, 155, 150);
// 4. 清理对象
DeleteObject(hPen);
DeleteObject(hBrush);
// 5. 关闭 DC,保存 WMF
HMETAFILE hMF = CloseMetaFile(hMetaDC);
DeleteMetaFile(hMF); // 文件已写入磁盘,释放句柄
MessageBox(NULL, L"WMF 文件已创建:simple.wmf", L"完成", MB_OK);
return 0;
}
效果说明:程序创建了一个蓝色圆角矩形(流程图节点),内部写了白色文字”Hello WMF!”,下方画了一个小箭头指向下一个节点位置。
十二、WMF 与 SVG 的转换
在实际工作中,经常需要在这两种矢量格式之间转换:
12.1 WMF → SVG
-
方法 1:使用在线转换工具(如 CloudConvert、Convertio)
-
方法 2:用 Inkscape(免费开源)打开 .wmf 文件,另存为 .svg
-
方法 3:用 ImageMagick 命令行:
convert input.wmf output.svg -
方法 4:用 Python 库
wmf2svg或cairosvg
12.2 SVG → WMF
-
方法 1:用 Inkscape 打开 .svg,另存为 .wmf
-
方法 2:Windows 上可用 GDI+(Graphics::DrawImage)
-
方法 3:商业库如 Aspose.Graphics
💡 当你需要在 Office 文档中嵌入矢量图(Word/PPT 原生支持 EMF/WMF),把 SVG 转成 EMF 再嵌入是常用做法。
十三、总结:WMF/EMF 的核心价值
回到最初的问题:为什么在 SVG 和 PNG 之后还要了解 WMF?
-
Windows 生态的核心格式:Office 文档中的矢量图、剪贴板中的图形,底层都是 WMF/EMF。理解它 = 理解 Windows 图形系统
-
跨格式转换的桥梁:SVG ↔ WMF ↔ PDF 转换中,理解 WMF 的结构能帮你排查转换丢失的问题
-
编程视角的启发:WMF 的「记录 + 回放」思想是命令模式(Command Pattern)的经典实现——很多图形编辑软件的撤销/重做功能也基于同样的设计
-
理解 GDI 的本质:WMF 的记录本质上就是序列化的 GDI 函数调用,掌握了 WMF,你对 Windows 图形编程的理解会提升一个层次
十四、速查表
最后把常用记录编号和 WMF/EMF 的差异整理成两张速查表,便于日后检索。
常用 WMF 记录速查
| 类别 | 记录名 | 编号 |
|---|---|---|
| 绘图 | MoveTo | 0x0214 |
| 绘图 | LineTo | 0x0213 |
| 绘图 | Rectangle | 0x041B |
| 绘图 | Ellipse | 0x0418 |
| 绘图 | RoundRect | 0x061C |
| 绘图 | Arc | 0x0817 |
| 绘图 | Pie | 0x081A |
| 绘图 | Polygon | 0x0324 |
| 文本 | TextOut | 0x0521 |
| 文本 | ExtTextOut | 0x0A32 |
| 对象 | CreatePenIndirect | 0x02FA |
| 对象 | CreateBrushIndirect | 0x02FC |
| 对象 | CreateFontIndirect | 0x02FB |
| 对象 | SelectObject | 0x012D |
| 对象 | DeleteObject | 0x01F0 |
| 状态 | SaveDC | 0x001E |
| 状态 | RestoreDC | 0x0127 |
| 位图 | BitBlt | 0x0922 |
| 位图 | StretchBlt | 0x0B23 |
| 结束 | EOF | 0x0000 |
WMF vs EMF 速查
| 对比维度 | WMF | EMF |
|---|---|---|
| 位宽 | 16 位 | 32 位 |
| 字符串 | ANSI | Unicode |
| 头部大小 | 18 字节(独立) | 80 字节(作为首记录) |
| 记录类型数 | ~70 | ~97 |
| 颜色调色板 | 无 | 有 |
| 引入版本 | Windows 2.0 | Windows 95/NT |
(注:部分内容可能由 AI 生成)