After the 14-byte header come the chunks, then an 8-byte end marker. Pixels are coded row by row, left to right and top to bottom. Chunks are byte-aligned, and within a chunk the most significant bit is on the left.
Decoder state
| State | Starts as | Purpose |
|---|---|---|
| previous pixel | r 0, g 0, b 0, a 255 | The base for DIFF, LUMA and RUN, and the alpha kept by RGB. |
| index | 64 entries, all r 0, g 0, b 0, a 0 | Recently seen pixels, addressed by the hash. |
After each pixel is decoded it becomes the previous pixel and is written to the index at its hash position.
QOI_OP_RGB
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0 | 1 | u8 | tag | FE |
| 1 | 1 | u8 | red | Red value. |
| 2 | 1 | u8 | green | Green value. |
| 3 | 1 | u8 | blue | Blue value. |
Alpha stays as it was in the previous pixel.
QOI_OP_RGBA
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0 | 1 | u8 | tag | FF |
| 1 | 1 | u8 | red | Red value. |
| 2 | 1 | u8 | green | Green value. |
| 3 | 1 | u8 | blue | Blue value. |
| 4 | 1 | u8 | alpha | Alpha value. |
QOI_OP_INDEX
| Bits | Name | Description |
|---|---|---|
| 7–6 | tag | 00 |
| 5–0 | index | Position in the index, 0 to 63. |
A valid encoder must not write two or more consecutive INDEX chunks for the same index. It should use RUN instead.
QOI_OP_DIFF
| Bits | Name | Range | Description |
|---|---|---|---|
| 7–6 | tag | 01 | |
| 5–4 | dr | −2 to 1 | Red difference from the previous pixel, stored with a bias of 2. |
| 3–2 | dg | −2 to 1 | Green difference, bias 2. |
| 1–0 | db | −2 to 1 | Blue difference, bias 2. |
Differences wrap around modulo 256, so 1 − 2 gives 255 and 255 + 1 gives 0. A stored 0 means −2 and a stored 3 means 1. Alpha is unchanged.
QOI_OP_LUMA
| Byte | Bits | Name | Range | Description |
|---|---|---|---|---|
| 0 | 7–6 | tag | 10 | |
| 0 | 5–0 | dg | −32 to 31 | Green difference from the previous pixel, bias 32. |
| 1 | 7–4 | dr − dg | −8 to 7 | Red difference minus the green difference, bias 8. |
| 1 | 3–0 | db − dg | −8 to 7 | Blue difference minus the green difference, bias 8. |
Differences wrap around as for DIFF, and alpha is unchanged. To decode:
dg = (byte0 & 0x3F) - 32
dr = dg + (byte1 >> 4) - 8
db = dg + (byte1 & 0x0F) - 8
QOI_OP_RUN
| Bits | Name | Range | Description |
|---|---|---|---|
| 7–6 | tag | 11 | |
| 5–0 | run | 1 to 62 | Repeat the previous pixel this many times, stored with a bias of −1, so 0 to 61. |
Run lengths 63 and 64 cannot be used, because the bit patterns 111110 and
111111 belong to the RGB and RGBA tags. Longer runs are split over several
RUN chunks.
The colour index hash
Each pixel's position in the index is:
index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64
There is one slot per position and no collision handling: a newer pixel replaces whatever was there.
End of the image
The image is complete when width × height pixels have been decoded. The stream then
ends with seven 00 bytes and one 01:
00 00 00 00 00 00 00 01
The reference decoder stops on the pixel count and does not check these bytes.
Worked example
A complete 2 by 1 RGBA file with two red pixels, 24 bytes long:
00000000 71 6F 69 66 00 00 00 02 00 00 00 01 04 00 5A C0
00000010 00 00 00 00 00 00 00 01
| Offset | Bytes | Meaning |
|---|---|---|
| 0 | 71 6F 69 66 | Magic qoif. |
| 4 | 00 00 00 02 | Width 2. |
| 8 | 00 00 00 01 | Height 1. |
| 12 | 04 | RGBA. |
| 13 | 00 | sRGB with linear alpha. |
| 14 | 5A | Binary 01 01 10 10: DIFF with dr −1, dg 0, db 0. Red goes from 0 to 255, wrapping, giving r 255, g 0, b 0, a 255. |
| 15 | C0 | Binary 11 000000: RUN of 1, repeating that pixel. |
| 16 | 00 … 01 | End marker. |