WMF (Windows Metafile Format) is a 16-bit graphics format created by Microsoft for storing vector and raster graphics. It was the primary metafile format for Windows prior to Windows 2000, when it was superseded by EMF (Enhanced Metafile Format). WMF files contain a sequence of GDI (Graphics Device Interface) function calls.
- Standard: Microsoft proprietary format (published specification)
- Purpose: Vector graphics metafile for Windows applications
- Encoding: Binary format with little-endian byte order
- Era: Designed for 16-bit Windows systems (Windows 3.x era)
A WMF file consists of:
[Optional Placeable Header (22 bytes)]
[Standard Header (18 bytes)]
[Record 1]
[Record 2]
...
[Record N - META_EOF]
Each Record represents a GDI function call or state change.
Also called the "Aldus Placeable Metafile (APM) Header", this was added by Aldus Corporation for better device independence.
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | Key | Magic number: 0x9AC6CDD7 (little-endian) |
| 4 | 2 | Handle | Metafile handle (usually 0) |
| 6 | 2 | Left | Left coordinate of bounding box |
| 8 | 2 | Top | Top coordinate of bounding box |
| 10 | 2 | Right | Right coordinate of bounding box |
| 12 | 2 | Bottom | Bottom coordinate of bounding box |
| 14 | 2 | Inch | Units per inch (e.g., 1440 for twips) |
| 16 | 4 | Reserved | Reserved (usually 0) |
| 20 | 2 | Checksum | XOR checksum of first 10 words |
Detection: Check if first 4 bytes equal 0x9AC6CDD7 (or 0xD7CDC69A in big-endian representation).
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 2 | FileType | Type of metafile: 1=memory, 2=disk |
| 2 | 2 | HeaderSize | Size of header in WORDs (always 9) |
| 4 | 2 | Version | Windows version (0x0300 for Windows 3.x) |
| 6 | 4 | FileSize | Size of entire file in WORDs |
| 10 | 2 | NumOfObjects | Number of GDI objects |
| 12 | 4 | MaxRecordSize | Size of largest record in WORDs |
| 16 | 2 | NumOfParams | Not used (0) |
Each WMF record has the following structure:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | RecordSize | Size of record in WORDs (including this) |
| 4 | 2 | RecordFunction | GDI function identifier |
| 6 | Variable | Parameters | Function-specific parameters |
Important:
- All sizes are in WORDs (16-bit words, 2 bytes each)
- Byte order is little-endian (opposite of CGM)
- RecordSize includes the 6-byte header (3 WORDs)
| Function Code | Name | Description |
|---|---|---|
| 0x0000 | META_EOF | End of metafile |
| 0x0213 | META_LINETO | Draw line to (x, y) |
| 0x0214 | META_MOVETO | Move current position to (x, y) |
| 0x0324 | META_POLYGON | Draw filled polygon |
| 0x0325 | META_POLYLINE | Draw series of connected lines |
| 0x041B | META_RECTANGLE | Draw filled rectangle |
| 0x0418 | META_ELLIPSE | Draw filled ellipse |
| 0x0538 | META_POLYPOLYGON | Draw multiple polygons |
| 0x061C | META_ROUNDRECT | Draw rectangle with rounded corners |
| 0x0817 | META_ARC | Draw elliptical arc |
| 0x0830 | META_CHORD | Draw chord (closed arc) |
| 0x0A32 | META_PIE | Draw pie slice |
| 0x0521 | META_TEXTOUT | Output text string |
| 0x0B41 | META_EXTTEXTOUT | Output text with options |
| Function Code | Name | Description |
|---|---|---|
| 0x0102 | META_SETBKMODE | Set background mode |
| 0x0103 | META_SETMAPMODE | Set coordinate mapping mode |
| 0x0104 | META_SETROP2 | Set binary raster operation |
| 0x0106 | META_SETPOLYFILLMODE | Set polygon fill mode |
| 0x0209 | META_SETWINDOWORG | Set window origin |
| 0x020B | META_SETWINDOWEXT | Set window extent |
| 0x020C | META_SETVIEWPORTORG | Set viewport origin |
| 0x020E | META_SETVIEWPORTEXT | Set viewport extent |
| Function Code | Name | Description |
|---|---|---|
| 0x01F0 | META_DELETEOBJECT | Delete GDI object |
| 0x012D | META_SELECTOBJECT | Select object into DC |
| 0x02FA | META_CREATEPENINDIRECT | Create pen object |
| 0x02FB | META_CREATEBRUSHINDIRECT | Create brush object |
| 0x02FC | META_CREATEFONTINDIRECT | Create font object |
| 0x00F8 | META_CREATEPALETTE | Create palette object |
| 0x0142 | META_DIBCREATEPATTERNBRUSH | Create pattern brush from DIB |
| Function Code | Name | Description |
|---|---|---|
| 0x0201 | META_SETBKCOLOR | Set background color |
| 0x0209 | META_SETTEXTCOLOR | Set text color |
RecordSize: 5 WORDs
RecordFunction: 0x0214
Parameters:
Y: 16-bit signed integer
X: 16-bit signed integer
RecordSize: 5 WORDs
RecordFunction: 0x0213
Parameters:
Y: 16-bit signed integer
X: 16-bit signed integer
RecordSize: 4 + NumberOfPoints WORDs
RecordFunction: 0x0324
Parameters:
NumberOfPoints: 16-bit integer
Points: Array of (X, Y) coordinate pairs
X1: 16-bit signed integer
Y1: 16-bit signed integer
...
RecordSize: 4 + NumberOfPoints WORDs
RecordFunction: 0x0325
Parameters:
NumberOfPoints: 16-bit integer
Points: Array of (X, Y) coordinate pairs
RecordSize: 7 WORDs
RecordFunction: 0x041B
Parameters:
BottomRect: 16-bit signed integer
RightRect: 16-bit signed integer
TopRect: 16-bit signed integer
LeftRect: 16-bit signed integer
RecordSize: 7 WORDs
RecordFunction: 0x0418
Parameters:
BottomRect: 16-bit signed integer (bounding box)
RightRect: 16-bit signed integer
TopRect: 16-bit signed integer
LeftRect: 16-bit signed integer
RecordSize: 8 WORDs
RecordFunction: 0x02FA
Parameters:
PenStyle: 16-bit integer (0=solid, 1=dash, 2=dot, etc.)
WidthX: 16-bit integer (pen width X)
WidthY: 16-bit integer (pen width Y, usually 0)
ColorRef: 32-bit color (0x00BBGGRR format)
RecordSize: 7 WORDs
RecordFunction: 0x02FB
Parameters:
BrushStyle: 16-bit integer (0=solid, 1=hollow, 2=hatched, etc.)
ColorRef: 32-bit color (0x00BBGGRR format)
BrushHatch: 16-bit integer (hatch pattern if hatched)
RecordSize: 5 WORDs
RecordFunction: 0x0201
Parameters:
ColorRef: 32-bit color (0x00BBGGRR format)
WMF uses 32-bit COLORREF values with the format:
Bytes: [BB GG RR 00]
0x00BBGGRR (little-endian)
Bits 0-7: Red component (0-255)
Bits 8-15: Green component (0-255)
Bits 16-23: Blue component (0-255)
Bits 24-31: Reserved (0)
Parsing Example:
color_ref = struct.unpack('<I', data[0:4])[0] # Little-endian 32-bit
r = color_ref & 0xFF
g = (color_ref >> 8) & 0xFF
b = (color_ref >> 16) & 0xFF
# Result: RGB(r, g, b)WMF uses logical coordinates that must be mapped to device coordinates using the current mapping mode.
- MM_TEXT (1): Each logical unit = 1 device pixel, +X right, +Y down
- MM_LOMETRIC (2): Each unit = 0.1mm, +X right, +Y up
- MM_HIMETRIC (3): Each unit = 0.01mm, +X right, +Y up
- MM_LOENGLISH (4): Each unit = 0.01 inch, +X right, +Y up
- MM_HIENGLISH (5): Each unit = 0.001 inch, +X right, +Y up
- MM_TWIPS (6): Each unit = 1/1440 inch, +X right, +Y up
- MM_ISOTROPIC (7): User-defined, equally scaled axes
- MM_ANISOTROPIC (8): User-defined, arbitrary axis scales
# Window extent defines logical coordinate space
# Viewport extent defines device coordinate space
# Transform logical to device coordinates:
device_x = ((logical_x - window_org_x) * viewport_ext_x) / window_ext_x + viewport_org_x
device_y = ((logical_y - window_org_y) * viewport_ext_y) / window_ext_y + viewport_org_yFor placeable metafiles, use the bounding box and units per inch to calculate scaling.
WMF maintains a table of GDI objects (pens, brushes, fonts, etc.):
- Create: Records like META_CREATEPENINDIRECT add objects to the table
- Select: META_SELECTOBJECT activates an object for drawing
- Delete: META_DELETEOBJECT removes an object from the table
Important: Objects are referenced by their index in the object table (starting from 0).
import struct
def parse_wmf(path):
with open(path, 'rb') as f:
# Check for placeable header
magic = struct.unpack('<I', f.read(4))[0]
if magic == 0x9AC6CDD7:
# Read placeable header (22 bytes total)
f.seek(0)
placeable = f.read(22)
# Parse placeable fields...
left, top, right, bottom = struct.unpack('<hhhh', placeable[6:14])
inch = struct.unpack('<H', placeable[14:16])[0]
print(f"Placeable WMF: bbox=({left},{top},{right},{bottom}), inch={inch}")
else:
f.seek(0)
# Read standard header (18 bytes)
header = f.read(18)
file_type, header_size, version, file_size, num_objects, max_record = \
struct.unpack('<HHIHIH', header)
print(f"WMF Version: 0x{version:04X}, Objects: {num_objects}")
# Read records
while True:
record_header = f.read(6)
if len(record_header) < 6:
break
record_size, record_func = struct.unpack('<IH', record_header)
# record_size is in WORDs, includes header (3 WORDs = 6 bytes)
param_bytes = (record_size - 3) * 2
params = f.read(param_bytes)
print(f"Record: 0x{record_func:04X}, Size: {record_size} WORDs")
if record_func == 0x0000: # META_EOF
breakHex: D7 CD C6 9A 00 00 00 00 00 00 40 01 58 02 A0 05 00 00 00 00 4F 4D
- Magic:
0x9AC6CDD7✓ (placeable) - Bounding box: (0, 0, 320, 600)
- Units: 1440 (twips)
- Checksum: 0x4D4F
Hex: 07 00 00 00 1B 04 64 00 C8 00 00 00 00 00
- RecordSize:
0x00000007= 7 WORDs (14 bytes) - RecordFunction:
0x041B= META_RECTANGLE - Parameters:
- Bottom:
0x0064= 100 - Right:
0x00C8= 200 - Top:
0x0000= 0 - Left:
0x0000= 0 - Rectangle from (0,0) to (200,100)
- Bottom:
Hex: 08 00 00 00 FA 02 00 00 02 00 00 00 00 00 FF 00
- RecordSize:
0x00000008= 8 WORDs - RecordFunction:
0x02FA= META_CREATEPENINDIRECT - Parameters:
- PenStyle:
0x0000= PS_SOLID - Width:
0x0002= 2 - Color:
0x0000FF00= Green (0x00GGRRBB → RGB(0, 255, 0))
- PenStyle:
Hex: 09 00 00 00 24 03 04 00 00 00 00 00 64 00 00 00 64 00 64 00 00 00 64 00
- RecordSize:
0x00000009= 9 WORDs - RecordFunction:
0x0324= META_POLYGON - Parameters:
- NumberOfPoints:
0x0004= 4 - Points: (0,0), (100,0), (100,100), (0,100)
- Draws a square
- NumberOfPoints:
| Aspect | WMF | CGM |
|---|---|---|
| Byte Order | Little-endian | Big-endian |
| Origin | Top-left | Bottom-left |
| Size Units | WORDs (2 bytes) | Bytes |
| Color Format | COLORREF (0x00BBGGRR) | RGB triplets or indexed |
| Era | Windows 3.x (1990s) | ISO standard (1980s-present) |
| Design | GDI function playback | Device-independent graphics |
- WMF uses little-endian (Intel byte order)
- Always use
struct.unpack('<H', ...)notstruct.unpack('>H', ...) - The
<prefix is critical (opposite of CGM's>)
- RecordSize is in WORDs (16-bit words), not bytes
- To get byte size:
byte_size = record_size * 2 - RecordSize includes the 6-byte header (3 WORDs)
- Parameter bytes =
(record_size - 3) * 2
- COLORREF format is
0x00BBGGRR(reverse of RGB!) - Extract: R=bits 0-7, G=bits 8-15, B=bits 16-23
- NOT the same as RGB byte order
- WMF default: origin at top-left, +Y down (screen coordinates)
- Some mapping modes: origin at bottom-left, +Y up
- Check mapping mode before rendering
- Not all WMF files have placeable headers
- Always check magic number first
- If no placeable header, start directly with standard header
- Objects are created and destroyed dynamically
- Track object indices carefully (0-based)
- Selecting invalid object index causes errors
- Objects persist until explicitly deleted or EOF
- GDI state (pen, brush, colors) persists across records
- Must maintain current position for LINETO operations
- Background mode affects text rendering
- ROP2 mode affects drawing operations
- Check for placeable header first – detect magic
0x9AC6CDD7 - Handle little-endian byte order – critical for correct parsing
- Maintain object table – track created/selected/deleted objects
- Track graphics state – current position, selected pen/brush, colors
- Transform coordinates – account for mapping mode and extents
- Handle text rendering – requires font object management
- [MS-WMF]: Windows Metafile Format - Microsoft Open Specifications
- Windows Metafile Format (wmf) Specification - Microsoft Download Center
- Microsoft Windows Metafile File Format Summary - Encyclopedia of Graphics File Formats
- Kaitai Struct WMF specification - formats.kaitai.io/wmf
Author: Technical summary compiled from Microsoft WMF specification, file format documentation, and Windows GDI references.
Purpose: Companion documentation to CGM format guide for vintage vector image viewer project.