QOI decoder

dekoodaaja

Disregard the funny repository name. There may or may not be plans to add other image decoders to this repository optimized for speed. The interface is deliberately simple, as I don’t plan to have stateful decoding or anything like that. For non color images, there might be separate decode functions. For now this is designed with std.Reader as a source and std.Writer as a sink, and the output is always argb8888(BGRA LE) pixels.

Now why this thread is titled QOI decoder, is because the only decoder available as for the time of writing is a QOI decoder :smile:

I did lots of testing and benchmarking on this decoder. It is fastest decoder on my x86_64 machine. I won’t go as far as saying it’s the world fastest decoder considering it hasn’t been tested on other machines (or even cpu archs).

Benchmarks

Bench harness used: https://github.com/sorvi-platform/dekoodaaja/blob/master/bench/bench.zig

# AMD Ryzen 7 3700X 8-Core Processor
nix run .#bench -- -Doptimize=ReleaseFast
qoi (image/qoi): 0.44 GB/s in, 6.17 GB/s out, 170.081us
qoi <https://github.com/phoboslab/qoi.git> (image/qoi): 0.14 GB/s in, 2.00 GB/s out, 523.124us
qoi-simd <https://github.com/chocolate42/qoi-simd> (image/qoi): 0.11 GB/s in, 1.49 GB/s out, 705.961us
magicqoi <https://github.com/marty1885/magicqoi> (image/qoi): 0.28 GB/s in, 3.86 GB/s out, 271.645us
zig-qoi <https://github.com/ikskuh/zig-qoi> (image/qoi): 0.24 GB/s in, 3.42 GB/s out, 306.602us
zqoi <https://codeberg.org/Pivok/zqoi.git> (image/qoi): 0.18 GB/s in, 2.55 GB/s out, 410.529us
rapid-qoi <https://github.com/zakarumych/rapid-qoi> (image/qoi): 0.32 GB/s in, 4.44 GB/s out, 236.404us
qoi-rust <https://github.com/aldanor/qoi-rust> (image/qoi): 0.32 GB/s in, 4.48 GB/s out, 234.275us
qoicoubeh <https://github.com/elmarco/qoi-rust> (image/qoi): 0.34 GB/s in, 4.75 GB/s out, 220.692us
# AMD Ryzen 7 3700X 8-Core Processor
nix run .#bench -- -Doptimize=ReleaseSmall
qoi (image/qoi): 0.20 GB/s in, 2.82 GB/s out, 372.37us
qoi <https://github.com/phoboslab/qoi.git> (image/qoi): 0.15 GB/s in, 2.11 GB/s out, 496.278us
qoi-simd <https://github.com/chocolate42/qoi-simd> (image/qoi): 0.11 GB/s in, 1.49 GB/s out, 704.787us
magicqoi <https://github.com/marty1885/magicqoi> (image/qoi): 0.18 GB/s in, 2.58 GB/s out, 407.179us
zig-qoi <https://github.com/ikskuh/zig-qoi> (image/qoi): 0.11 GB/s in, 1.49 GB/s out, 703.877us
zqoi <https://codeberg.org/Pivok/zqoi.git> (image/qoi): 0.15 GB/s in, 2.13 GB/s out, 491.462us
rapid-qoi <https://github.com/zakarumych/rapid-qoi> (image/qoi): 0.25 GB/s in, 3.56 GB/s out, 294.652us
qoi-rust <https://github.com/aldanor/qoi-rust> (image/qoi): 0.19 GB/s in, 2.64 GB/s out, 396.598us
qoicoubeh <https://github.com/elmarco/qoi-rust> (image/qoi): 0.19 GB/s in, 2.66 GB/s out, 394.937us

I wanted to bench against blend2d decoder as well, but it doesn’t seem their QOI decoder can be easily seperated from the blend2d project itself.

Supported Zig versions

0.15.2
0.16.0

6 Likes

Couldn’t believe the format is that simple when I saw the code. But well written, as readable as the spec. (-: One question: this assumes well-formed images, right? Because I didn’t spot any range-checking on the output buffer. Edit: ah, nevermind. I thought the while-check was for the input. Nice!

It also checks for RLE chunks that might write out of bounds. It would be possible to feed it slightly evil qoi file that has large dimensions, but doesn’t actually produce that big image. Theres assert that catches that in safe modes, but I think that should be a error.

EDIT: actually, such image would eventually fail with Reader error, as the loop won’t exit unless there’s enough pixels written, so only thing that would happen is either OOM or short spike in reserved memory usage.

As for the spec itself, it’s nice. But there’s few things I would improve, like the hash could be calculated from the bitpacked pixel, not from the components. Header could be more tightly packed as well, and using big endian when it’s not very relevant anymore is certainly a choice itself. Doesn’t matter much with QOI, but with QOA (quite ok audio format) it’s more of a bummer.

I think more SIMD friendly image format based on rows and parallel states could be a interesting pet project.

1 Like

Slightly improved perf for ReleaseSmall. std.Io.Reader not getting inlined hurts it quite a bit. It does produce a smaller code for sure though. ReleaseSmall codegen in 0.16 seems also bit funky right now, esp relating @bitCasts. I don’t want to too much modify it for ReleaseSmall though, as I prefer keeping the code minimal and readable.

3 Likes