Paint.NET does not publish its format. This layout is what independent readers have worked out, chiefly the Python library pypdn. The middle of the file is a .NET BinaryFormatter stream ([MS-NRBF], which Microsoft does publish) holding Paint.NET’s own objects, so a reader needs an NRBF parser and knowledge of Paint.NET’s class and field names.
Layout
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0 | 4 | char[4] | Magic | PDN3. |
| 4 | 3 | u24, little-endian | HeaderLength | Length of the XML header in bytes. |
| 7 | HeaderLength | UTF-8 | Header | An XML header, which holds a thumbnail. Readers need not parse it: pypdn notes that it holds nothing the document that follows does not. |
| 2 | bytes | Marker | 00 01. Readers check it and then start the NRBF parser. | |
| varies | NRBF | Document | The serialised PaintDotNet.Document, below. | |
| varies | Pixels | Then, for each layer in order, its pixel data, below. |
Serialised objects
| Object | Fields readers use |
|---|---|
| Document | width, height, layers (a layer list), savedWith (the Paint.NET version), userMetadataItems (metadata such as Exif). |
| Layer list | A .NET ArrayList: _size is the number of layers and _items the array, which may be longer and padded with nulls. Layers are listed bottom first. |
| Bitmap layer | The|
| Layer properties | name, visible, isBackground, opacity (0 to 255), blendMode (an enumeration, below). |
| Surface | width, height, stride (bytes per row) and scan0, whose length64 is the size of the pixel data. The pixels themselves are not in the NRBF stream. |
Blend modes
| Value | Mode | Value | Mode |
|---|---|---|---|
| 0 | Normal | 7 | Overlay |
| 1 | Multiply | 8 | Difference |
| 2 | Additive | 9 | Negation |
| 3 | Colour burn | 10 | Lighten |
| 4 | Colour dodge | 11 | Darken |
| 5 | Reflect | 12 | Screen |
| 6 | Glow | 13 | Xor |
Files without a blendMode field name the blend by the class of their
blendOp object instead, such as
PaintDotNet.UserBlendOps+MultiplyBlendOp.
Pixel data
Each layer’s pixels follow the NRBF stream, in layer order. All numbers here are big-endian.
| Size | Type | Name | Description |
|---|---|---|---|
| 1 | u8 | Format | 0 the chunks are gzip-compressed, 1 they are stored raw. |
| 4 | u32 | ChunkSize | Uncompressed size of every chunk but the last. |
Chunks | ceil(length64 / ChunkSize) chunks, below. |
| Size | Type | Name | Description |
|---|---|---|---|
| 4 | u32 | ChunkNumber | Index of this chunk. Chunks may come in any order; chunk n fills the bytes from n × ChunkSize. |
| 4 | u32 | DataSize | Stored size of the chunk. |
| DataSize | bytes | Data | The chunk, gzip-compressed if Format is 0. |
The assembled bytes are rows of stride bytes, top row first. Paint.NET
stores 32-bit pixels in the order blue, green, red, alpha.
Unknowns
- The XML header’s element and attribute names are not documented by the readers.
- Whether alpha is premultiplied is not stated; readers treat it as straight alpha.
- Layer types other than bitmap layers, and the effects history that Paint.NET keeps while editing, are not described.
- Changes in the most recent Paint.NET versions have not been publicly documented.