# QOI

Canonical URL: https://encode.wiki/codecs/images/qoi/
Content version: ca42e61ede0a24f789d7287a0001244ad22f4abf
Status: current
License: CC BY-SA 4.0
Checksum (SHA-256): 11362a6d89b82f512ae96fe6f34091bd49624083f5331686694b7025a553a2ac

QOI ("Quite OK Image") is a lossless format for 8-bit RGB and RGBA images, designed by Dominic Szablewski so that a complete encoder and decoder fits in a single C header. Its selling point is speed: in the author's benchmark, encoding is about 30 times faster than libpng and decoding about 3 times faster, at the cost of larger files. It suits game assets, textures, screenshot tools and anything that writes lots of images quickly. It is not a delivery format for the web.

| Property     | Value                                                               |
| ------------ | ------------------------------------------------------------------- |
| Compression  | Lossless                                                            |
| Bit depth    | 8 bits per channel, 3 (RGB) or 4 (RGBA) channels                    |
| Alpha        | Straight alpha in 4-channel files                                   |
| Metadata     | None (no ICC, Exif or XMP); header has a sRGB/linear flag only      |
| Animation    | No                                                                  |
| Progressive  | No                                                                  |
| Royalty-free | Yes (reference library is MIT-licensed)                             |

## How it compares with PNG

The author's benchmark on 2,879 screenshots, icons, photos and textures (single-threaded, i7-6700K) gives:

| Codec  | Average size | Encode | Decode |
| ------ | ------------ | ------ | ------ |
| libpng | 398 KB (24.2% of raw) | 83.8 ms (5.5 MP/s) | 7.0 ms (67 MP/s) |
| stb_image_write | 561 KB (34.2%) | 60.5 ms | 7.0 ms |
| QOI    | 463 KB (28.2%) | 2.9 ms (162 MP/s) | 2.1 ms (226 MP/s) |

So QOI is about 16% **larger** than libpng on average and much faster. An optimiser such as [oxipng](/codecs/images/png/) widens the gap further, and lossless [WebP](/codecs/images/webp/) or [JPEG XL](/codecs/images/jxl/) are smaller still. QOI does best on images with flat runs and smooth gradients; it does poorly on noise and photographs.

## Using it

The reference tool converts between PNG and QOI:

```sh
qoiconv input.png output.qoi
qoiconv output.qoi restored.png
```

`qoiconv` has no options; it always writes sRGB QOI and loads PNGs as 8-bit, so 16-bit input is truncated. The same C header, [`qoi.h`](https://github.com/phoboslab/qoi), is what most applications embed; ports for many other languages are listed in the repository README.

FFmpeg has a native QOI encoder and decoder:

```sh
ffmpeg -i input.png output.qoi
ffmpeg -i output.qoi restored.png
```

ImageMagick reads and writes QOI (`magick input.png output.qoi`). Browsers have no native support, so convert to PNG, WebP or AVIF for the web.

## How the format works

A 14-byte header (magic `qoif`, width, height, channels, colour-space flag) is followed by a stream of byte-aligned chunks and an 8-byte end marker. The encoder keeps the previous pixel and a table of 64 recently seen colours, and emits one of six chunk types:

| Chunk | Size | Meaning |
| --- | --- | --- |
| `QOI_OP_RUN` | 1 byte | Repeat the previous pixel 1–62 times |
| `QOI_OP_INDEX` | 1 byte | Pixel found in the 64-entry colour table (hash of the colour) |
| `QOI_OP_DIFF` | 1 byte | Small per-channel difference from the previous pixel (−2…+1) |
| `QOI_OP_LUMA` | 2 bytes | Green difference (−32…+31) with red and blue differences relative to it |
| `QOI_OP_RGB` | 4 bytes | Full RGB pixel |
| `QOI_OP_RGBA` | 5 bytes | Full RGBA pixel |

There is nothing tunable: no quality or effort setting, so every QOI encoder writes nearly the same file. The [one-page specification](https://qoiformat.org/qoi-specification.pdf) is the whole format.

## Pitfalls

- **Colour management**: the header's `colorspace` flag (sRGB with linear alpha, or all linear) does not convert anything and there is no ICC support. Keep the profile elsewhere if it matters.
- **16-bit and HDR sources** must be reduced to 8 bits first, which loses data. Use PNG, TIFF or [JPEG XL](/codecs/images/jxl/) instead.
- **Size limit**: the reference library refuses images over 400 million pixels. The format itself allows far larger images.

## Full option reference

The reference converter, library and FFmpeg interfaces, option by option. [Reading option tables](/reference/encoder-parameters/) explains the Default column.

## Complete reference-converter arguments

`qoiconv` takes two positional operands and no options. Both must end in a lowercase `.png` or `.qoi`, and the writer overwrites existing files. Arguments after the second operand are ignored, and a leading flag such as `--quality` is read as the input path, so it fails.

| Argument | Default  | Accepted values and responsibility                                                    |
| -------- | -------- | ------------------------------------------------------------------------------------- |
| `INPUT`  | required | Source path ending in lowercase .png or .qoi; other PNG channel layouts load as RGBA. |
| `OUTPUT` | required | Destination path ending in .png or .qoi; an existing file is overwritten.             |

## Complete library encode contract

`qoi_encode(data, desc, out_len)` has no presets; the caller supplies every field and frees the returned buffer.

| Parameter / field | Default  | Accepted value / responsibility                                                  |
| ----------------- | -------- | -------------------------------------------------------------------------------- |
| `data`            | required | Tightly packed row-major RGB or RGBA bytes matching the descriptor.              |
| `desc`            | required | Pointer to a filled qoi_desc.                                                    |
| `out_len`         | required | Receives the compressed size in bytes on success.                                |
| `desc.width`      | required | Pixel width, at least 1.                                                         |
| `desc.height`     | required | Pixel height, at least 1; width times height must stay below 400 million pixels. |
| `desc.channels`   | required | 3 for RGB or 4 for RGBA.                                                         |
| `desc.colorspace` | required | QOI_SRGB = 0 or QOI_LINEAR = 1; a label stored in the header, not a conversion.  |

Invalid descriptors return null, so check the result before using `out_len`. `qoi_write` adds a filename, and `qoi_read` / `qoi_decode` form the separate decoder contract.

## FFmpeg QOI encoder interface

FFmpeg 9.0.2's `qoi` encoder has no private options. It takes `rgb24` or `rgba` input, and writes the linear flag in the header only when the frames are tagged with a `linear` transfer characteristic (the flag is signalling only; it does not linearise anything).

```sh
ffmpeg -i input.png -c:v qoi -pix_fmt rgba output.qoi
ffmpeg -i linear.png -vf setparams=color_trc=linear -c:v qoi output.qoi
```

Converting to `rgb24` drops the alpha channel.
