跳至主要内容
工具记录约 11 分钟

WMF 矢量图:从 Windows 元文件到实战应用

梳理 WMF/EMF 文件结构、绘图记录、坐标映射、位图操作、Windows API 与格式转换。

#WMF#EMF#矢量图#Windows API
本文目录
  1. 一、什么是 WMF?
  2. 二、WMF vs 位图 vs SVG
  3. 三、WMF 的三种变体
  4. 四、WMF 文件结构
  5. 五、WMF 记录系统——元文件的灵魂
  6. 5.1 记录基本格式
  7. 5.2 绘图命令
  8. 5.3 对象创建命令
  9. 5.4 状态管理命令
  10. 六、坐标系与映射模式
  11. 6.1 逻辑单位:Twip
  12. 6.2 映射模式
  13. 七、位图操作(BitBlt / StretchBlt)
  14. 7.1 BitBlt 记录
  15. 7.2 StretchBlt
  16. 八、EMF——增强型元文件
  17. 8.1 EMF vs WMF 对比
  18. 8.2 EMF 新增特性
  19. 九、文本与颜色
  20. 9.1 文本输出
  21. 9.2 颜色模型
  22. 十、WMF/EMF 的编程接口
  23. 10.1 WMF 相关 API
  24. 10.2 EMF 相关 API
  25. 十一、实战:用 C++ 创建并播放一个 WMF
  26. 十二、WMF 与 SVG 的转换
  27. 12.1 WMF → SVG
  28. 12.2 SVG → WMF
  29. 十三、总结:WMF/EMF 的核心价值
  30. 十四、速查表
  31. 常用 WMF 记录速查
  32. WMF vs EMF 速查

一、什么是 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 或 .WMF22 字节前置头部 + 标准头部
剪贴板 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 绘图记录:

记录名称编号功能
MoveTo0x0214移动画笔到指定坐标
LineTo0x0213从当前位置画线到目标坐标
Rectangle0x041B画矩形
Ellipse0x0418画椭圆(或圆)
RoundRect0x061C画圆角矩形
Arc0x0817画圆弧
Chord0x0830画弓形(弧 + 弦)
Pie0x081A画扇形
Polygon0x0324画多边形
Polyline0x0325画折线
TextOut0x0521输出文本

5.3 对象创建命令

WMF 可以定义图形对象(画笔、画刷、字体等),之后在绘图命令中引用:

记录名称编号功能
CreatePenIndirect0x02FA创建画笔(颜色、宽度、样式)
CreateBrushIndirect0x02FC创建画刷(纯色/图案填充)
CreateFontIndirect0x02FB创建字体
CreateBitmapIndirect0x02FD创建位图
SelectObject0x012D将对象选入设备上下文
DeleteObject0x01F0删除对象,释放资源

💡 对象句柄表:Windows 在内存中维护一个对象句柄表。每创建一个对象(如画笔),它就被分配一个句柄。后续的绘图命令通过 SelectObject 选择该画笔,绘图完成后用 DeleteObject 释放。

5.4 状态管理命令

记录名称编号功能
SaveDC0x001E保存当前设备上下文状态
RestoreDC0x0127恢复到之前保存的状态
SetBkColor0x0201设置背景颜色
SetBkMode0x0102设置背景模式(透明/不透明)
SetTextColor0x0209设置文字颜色
SetROP20x0104设置绘图模式(混合/覆盖等)

六、坐标系与映射模式

WMF 的坐标系统比其他矢量格式更复杂,因为它需要适配设备无关的渲染目标。

6.1 逻辑单位:Twip

Placeable WMF 使用 twip 作为基本度量单位:

  • 1 twip = 1/1440 英寸

  • 720 twips = 0.5 英寸

  • 1440 twips = 1 英寸

前置头部的 Inch 字段控制缩放比例:

Inch 值缩放效果
14401: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_TWIPSTwip 模式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 对比

特性WMFEMF
位宽度16 位32 位
头部独立的 META_HEADER头部作为第一条记录存储
字符集ANSIUnicode
记录格式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 中的文本操作通过 TextOutExtTextOut 记录实现:

记录功能
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 库 wmf2svgcairosvg

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?

  1. Windows 生态的核心格式:Office 文档中的矢量图、剪贴板中的图形,底层都是 WMF/EMF。理解它 = 理解 Windows 图形系统

  2. 跨格式转换的桥梁:SVG ↔ WMF ↔ PDF 转换中,理解 WMF 的结构能帮你排查转换丢失的问题

  3. 编程视角的启发:WMF 的「记录 + 回放」思想是命令模式(Command Pattern)的经典实现——很多图形编辑软件的撤销/重做功能也基于同样的设计

  4. 理解 GDI 的本质:WMF 的记录本质上就是序列化的 GDI 函数调用,掌握了 WMF,你对 Windows 图形编程的理解会提升一个层次


十四、速查表

最后把常用记录编号和 WMF/EMF 的差异整理成两张速查表,便于日后检索。

常用 WMF 记录速查

类别记录名编号
绘图MoveTo0x0214
绘图LineTo0x0213
绘图Rectangle0x041B
绘图Ellipse0x0418
绘图RoundRect0x061C
绘图Arc0x0817
绘图Pie0x081A
绘图Polygon0x0324
文本TextOut0x0521
文本ExtTextOut0x0A32
对象CreatePenIndirect0x02FA
对象CreateBrushIndirect0x02FC
对象CreateFontIndirect0x02FB
对象SelectObject0x012D
对象DeleteObject0x01F0
状态SaveDC0x001E
状态RestoreDC0x0127
位图BitBlt0x0922
位图StretchBlt0x0B23
结束EOF0x0000

WMF vs EMF 速查

对比维度WMFEMF
位宽16 位32 位
字符串ANSIUnicode
头部大小18 字节(独立)80 字节(作为首记录)
记录类型数~70~97
颜色调色板
引入版本Windows 2.0Windows 95/NT

(注:部分内容可能由 AI 生成)