rapid_qoi/
lib.rs

1//! QOI - The "Quite OK Image" format for fast, lossless image compression
2//!
3//! <https://phoboslab.org>
4//!
5//! QOI encodes and decodes images in a lossless format. Compared to stb_image and
6//! stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and
7//! 20% better compression.
8//!
9//!
10//! # Data Format
11//!
12//! A QOI file has a 14 byte header, followed by any number of data "chunks" and an
13//! 8-byte end marker.
14//!
15//! ```rust
16//! #[repr(C)]
17//! struct QoiHeader {
18//!     magic: [u8; 4], // magic bytes "qoif"
19//!     width: u32,     // image width in pixels (BE)
20//!     height: u32,    // image height in pixels (BE)
21//!     channels: u8,   // 3 = RGB, 4 = RGBA
22//!     colorspace: u8, // 0 = sRGB with linear alpha, 1 = all channels linear
23//! }
24//! ```
25//! Images are encoded from top to bottom, left to right. The decoder and encoder
26//! start with `{r: 0, g: 0, b: 0, a: 255}` as the previous pixel value. An image is
27//! complete when all pixels specified by width * height have been covered.\
28//! Pixels are encoded as
29//!  * a run of the previous pixel
30//!  * an index into an array of previously seen pixels
31//!  * a difference to the previous pixel value in r,g,b
32//!  * full r,g,b or r,g,b,a values
33//!
34//! The color channels are assumed to not be premultiplied with the alpha channel
35//! ("un-premultiplied alpha").
36//!
37//! A running `array: [u32; 64]` (zero-initialized) of previously seen pixel values is
38//! maintained by the encoder and decoder. Each pixel that is seen by the encoder
39//! and decoder is put into this array at the position formed by a hash function of
40//! the color value. In the encoder, if the pixel value at the index matches the
41//! current pixel, this index position is written to the stream as QOI_OP_INDEX.
42//!
43//! The hash function for the index is:
44//! ```rust,ignore
45//! index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64
46//! ```
47//! Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The
48//! bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All
49//! values encoded in these data bits have the most significant bit on the left.\
50//! The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the
51//! presence of an 8-bit tag first.
52//!
53//! The byte stream's end is marked with 7 `0x00` bytes followed a single `0x01` byte.
54//! The possible chunks are:
55//! ```text
56//! .- QOI_OP_INDEX ----------.
57//! |         Byte[0]         |
58//! |  7  6  5  4  3  2  1  0 |
59//! |-------+-----------------|
60//! |  0  0 |     index       |
61//! `-------------------------`
62//! ```
63//! 2-bit tag `0b00`\
64//! 6-bit index into the color index array: `0..=63`\
65//! A valid encoder must not issue 7 or more consecutive `QOI_OP_INDEX` chunks to the
66//! index 0, to avoid confusion with the 8 byte end marker.
67//! ```text
68//! .- QOI_OP_DIFF -----------.
69//! |         Byte[0]         |
70//! |  7  6  5  4  3  2  1  0 |
71//! |-------+-----+-----+-----|
72//! |  0  1 |  dr |  dg |  db |
73//! `-------------------------`
74//! ```
75//! 2-bit tag `0b01`\
76//! 2-bit   red channel difference from the previous pixel between `-2..=1`\
77//! 2-bit green channel difference from the previous pixel between `-2..=1`\
78//! 2-bit  blue channel difference from the previous pixel between `-2..=1`\
79//! The difference to the current channel values are using a wraparound operation,
80//! so `1 - 2` will result in `255`, while `255 + 1` will result in `0`.\
81//! Values are stored as unsigned integers with a bias of 2. E.g. `-2` is stored as
82//! `0` (`0b00`). `1` is stored as `3` (`0b11`).\
83//! The alpha value remains unchanged from the previous pixel.
84//! ```text
85//! .- QOI_OP_LUMA -------------------------------------.
86//! |         Byte[0]         |         Byte[1]         |
87//! |  7  6  5  4  3  2  1  0 |  7  6  5  4  3  2  1  0 |
88//! |-------+-----------------+-------------+-----------|
89//! |  1  0 |  green diff     |   dr - dg   |  db - dg  |
90//! `---------------------------------------------------`
91//! ```
92//! 2-bit tag `0b10`\
93//! 6-bit green channel difference from the previous pixel `-32..=31`\
94//! 4-bit   red channel difference minus green channel difference `-8..=7`\
95//! 4-bit  blue channel difference minus green channel difference `-8..=7`\
96//! The green channel is used to indicate the general direction of change and is
97//! encoded in 6 bits. The red and blue channels (dr and db) base their diffs off
98//! of the green channel difference and are encoded in 4 bits. I.e.:
99//! ```rust,ignore
100//! dr_dg = (last_px.r - cur_px.r) - (last_px.g - cur_px.g)
101//! db_dg = (last_px.b - cur_px.b) - (last_px.g - cur_px.g)
102//! ```
103//! The difference to the current channel values are using a wraparound operation,
104//! so `10 - 13` will result in `253`, while `250 + 7` will result in `1`.\
105//! Values are stored as unsigned integers with a bias of 32 for the green channel
106//! and a bias of 8 for the red and blue channel.\
107//! The alpha value remains unchanged from the previous pixel.
108//! ```text
109//! .- QOI_OP_RUN ------------.
110//! |         Byte[0]         |
111//! |  7  6  5  4  3  2  1  0 |
112//! |-------+-----------------|
113//! |  1  1 |       run       |
114//! `-------------------------`
115//! ```
116//! 2-bit tag `0b11`\
117//! 6-bit run-length repeating the previous pixel: `1..=62`\
118//! The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64
119//! (`0b111110` and `0b111111`) are illegal as they are occupied by the `QOI_OP_RGB` and
120//! `QOI_OP_RGBA` tags.
121//! ```text
122//! .- QOI_OP_RGB ------------------------------------------.
123//! |         Byte[0]         | Byte[1] | Byte[2] | Byte[3] |
124//! |  7  6  5  4  3  2  1  0 | 7 .. 0  | 7 .. 0  | 7 .. 0  |
125//! |-------------------------+---------+---------+---------|
126//! |  1  1  1  1  1  1  1  0 |   red   |  green  |  blue   |
127//! `-------------------------------------------------------`
128//! ```
129//! 8-bit tag `0b11111110`\
130//! 8-bit   red channel value\
131//! 8-bit green channel value\
132//! 8-bit  blue channel value\
133//! The alpha value remains unchanged from the previous pixel.
134//! ```text
135//! .- QOI_OP_RGBA ---------------------------------------------------.
136//! |         Byte[0]         | Byte[1] | Byte[2] | Byte[3] | Byte[4] |
137//! |  7  6  5  4  3  2  1  0 | 7 .. 0  | 7 .. 0  | 7 .. 0  | 7 .. 0  |
138//! |-------------------------+---------+---------+---------+---------|
139//! |  1  1  1  1  1  1  1  1 |   red   |  green  |  blue   |  alpha  |
140//! `-----------------------------------------------------------------`
141//! ```
142//! 8-bit tag `0b11111111`\
143//! 8-bit   red channel value\
144//! 8-bit green channel value\
145//! 8-bit  blue channel value\
146//! 8-bit alpha channel value
147#![forbid(unsafe_code)]
148#![deny(missing_copy_implementations)]
149#![deny(missing_debug_implementations)]
150#![cfg_attr(not(feature = "std"), no_std)]
151
152#[cfg(feature = "alloc")]
153extern crate alloc;
154
155use core::{
156    convert::TryInto,
157    fmt::{self, Display},
158};
159
160mod decode;
161mod encode;
162
163pub use decode::DecodeError;
164pub use encode::EncodeError;
165
166const QOI_OP_INDEX: u8 = 0x00; /* 00xxxxxx */
167const QOI_OP_DIFF: u8 = 0x40; /* 01xxxxxx */
168const QOI_OP_LUMA: u8 = 0x80; /* 10xxxxxx */
169const QOI_OP_RUN: u8 = 0xc0; /* 11xxxxxx */
170const QOI_OP_RGB: u8 = 0xfe; /* 11111110 */
171const QOI_OP_RGBA: u8 = 0xff; /* 11111111 */
172
173const QOI_MAGIC: u32 = u32::from_be_bytes(*b"qoif");
174const QOI_HEADER_SIZE: usize = 14;
175const QOI_PADDING: usize = 8;
176
177/// Trait for pixel types.
178/// Supports byte operations, channels accessing and modifying.
179pub trait Pixel: Copy + Eq {
180    const HAS_ALPHA: bool;
181
182    fn new() -> Self;
183
184    fn new_opaque() -> Self;
185
186    fn read(&mut self, bytes: &[u8]);
187
188    fn write(&self, bytes: &mut [u8]);
189
190    fn var(&self, prev: &Self) -> Var;
191
192    fn rgb(&self) -> [u8; 3];
193
194    fn rgba(&self) -> [u8; 4];
195
196    fn r(&self) -> u8;
197
198    fn g(&self) -> u8;
199
200    fn b(&self) -> u8;
201
202    fn a(&self) -> u8;
203
204    fn set_r(&mut self, r: u8);
205
206    fn set_g(&mut self, g: u8);
207
208    fn set_b(&mut self, b: u8);
209
210    fn set_a(&mut self, a: u8);
211
212    fn set_rgb(&mut self, r: u8, g: u8, b: u8);
213
214    fn set_rgba(&mut self, r: u8, g: u8, b: u8, a: u8);
215
216    fn add_rgb(&mut self, r: u8, g: u8, b: u8);
217
218    fn hash(&self) -> u8;
219}
220
221impl Pixel for [u8; 3] {
222    const HAS_ALPHA: bool = false;
223
224    #[inline]
225    fn new() -> Self {
226        [0; 3]
227    }
228
229    #[inline]
230    fn new_opaque() -> Self {
231        [0; 3]
232    }
233
234    #[inline]
235    fn read(&mut self, bytes: &[u8]) {
236        self.copy_from_slice(bytes);
237    }
238
239    #[inline]
240    fn write(&self, bytes: &mut [u8]) {
241        assert_eq!(bytes.len(), self.len());
242        bytes.copy_from_slice(self)
243    }
244
245    #[inline]
246    fn var(&self, prev: &Self) -> Var {
247        let r = self[0].wrapping_sub(prev[0]);
248        let g = self[1].wrapping_sub(prev[1]);
249        let b = self[2].wrapping_sub(prev[2]);
250
251        Var { r, g, b }
252    }
253
254    #[inline]
255    fn r(&self) -> u8 {
256        self[0]
257    }
258
259    #[inline]
260    fn g(&self) -> u8 {
261        self[1]
262    }
263
264    #[inline]
265    fn b(&self) -> u8 {
266        self[2]
267    }
268
269    #[inline]
270    fn rgb(&self) -> [u8; 3] {
271        *self
272    }
273
274    #[inline]
275    fn rgba(&self) -> [u8; 4] {
276        unreachable!()
277    }
278
279    #[inline]
280    fn a(&self) -> u8 {
281        255
282    }
283
284    #[inline]
285    fn set_r(&mut self, r: u8) {
286        self[0] = r;
287    }
288
289    #[inline]
290    fn set_g(&mut self, g: u8) {
291        self[1] = g;
292    }
293
294    #[inline]
295    fn set_b(&mut self, b: u8) {
296        self[2] = b;
297    }
298
299    #[inline]
300    fn set_a(&mut self, a: u8) {
301        debug_assert_eq!(a, 255);
302    }
303
304    #[inline]
305    fn set_rgb(&mut self, r: u8, g: u8, b: u8) {
306        self[0] = r;
307        self[1] = g;
308        self[2] = b;
309    }
310
311    #[inline]
312    fn set_rgba(&mut self, r: u8, g: u8, b: u8, a: u8) {
313        debug_assert_eq!(a, 255);
314
315        self[0] = r;
316        self[1] = g;
317        self[2] = b;
318    }
319
320    #[inline]
321    fn add_rgb(&mut self, r: u8, g: u8, b: u8) {
322        self[0] = self[0].wrapping_add(r);
323        self[1] = self[1].wrapping_add(g);
324        self[2] = self[2].wrapping_add(b);
325    }
326
327    // #[inline]
328    // fn hash(&self) -> u8 {
329    //     let [r, g, b] = self;
330    //     r.wrapping_mul(3)
331    //         .wrapping_add(g.wrapping_mul(5))
332    //         .wrapping_add(b.wrapping_mul(7).wrapping_add(245))
333    //         & 63
334    // }
335
336    #[inline]
337    fn hash(&self) -> u8 {
338        let [r, g, b] = *self;
339        let v = u32::from_ne_bytes([r, g, b, 0xff]);
340        let s = (((v as u64) << 32) | (v as u64)) & 0xFF00FF0000FF00FF;
341
342        s.wrapping_mul(0x030007000005000Bu64.to_le()).swap_bytes() as u8 & 63
343    }
344}
345
346// /// Four channel pixel type.
347// /// Typically channels are Red, Green, Blue and Alpha.
348// #[repr(transparent)]
349// #[derive(Clone, Copy, PartialEq, Eq)]
350// pub struct Rgba {
351//     pub rgba: [u8; 4],
352// }
353
354impl Pixel for [u8; 4] {
355    const HAS_ALPHA: bool = true;
356
357    #[inline]
358    fn new() -> Self {
359        [0; 4]
360    }
361
362    #[inline]
363    fn new_opaque() -> Self {
364        [0, 0, 0, 0xff]
365    }
366
367    #[inline]
368    fn read(&mut self, bytes: &[u8]) {
369        match bytes.try_into() {
370            Ok(rgba) => {
371                *self = rgba;
372            }
373            _ => unreachable(),
374        }
375    }
376
377    #[inline]
378    fn write(&self, bytes: &mut [u8]) {
379        assert_eq!(bytes.len(), self.len());
380        bytes.copy_from_slice(self)
381    }
382
383    #[inline]
384    fn var(&self, prev: &Self) -> Var {
385        let [r, g, b, a] = *self;
386        let [pr, pg, pb, pa] = *prev;
387        debug_assert_eq!(a, pa);
388
389        let r = r.wrapping_sub(pr);
390        let g = g.wrapping_sub(pg);
391        let b = b.wrapping_sub(pb);
392
393        Var { r, g, b }
394    }
395
396    #[inline]
397    fn r(&self) -> u8 {
398        self[0]
399    }
400
401    #[inline]
402    fn g(&self) -> u8 {
403        self[1]
404    }
405
406    #[inline]
407    fn b(&self) -> u8 {
408        self[2]
409    }
410
411    #[inline]
412    fn rgb(&self) -> [u8; 3] {
413        let [r, g, b, _] = *self;
414        [r, g, b]
415    }
416
417    #[inline]
418    fn rgba(&self) -> [u8; 4] {
419        *self
420    }
421
422    #[inline]
423    fn a(&self) -> u8 {
424        self[3]
425    }
426
427    #[inline]
428    fn set_r(&mut self, r: u8) {
429        self[0] = r;
430    }
431
432    #[inline]
433    fn set_g(&mut self, g: u8) {
434        self[1] = g;
435    }
436
437    #[inline]
438    fn set_b(&mut self, b: u8) {
439        self[2] = b;
440    }
441
442    #[inline]
443    fn set_a(&mut self, a: u8) {
444        self[3] = a;
445    }
446
447    #[inline]
448    fn set_rgb(&mut self, r: u8, g: u8, b: u8) {
449        *self = [r, g, b, self[3]];
450    }
451
452    #[inline]
453    fn set_rgba(&mut self, r: u8, g: u8, b: u8, a: u8) {
454        *self = [r, g, b, a];
455    }
456
457    #[inline]
458    fn add_rgb(&mut self, r: u8, g: u8, b: u8) {
459        self[0] = self[0].wrapping_add(r);
460        self[1] = self[1].wrapping_add(g);
461        self[2] = self[2].wrapping_add(b);
462    }
463
464    // #[inline]
465    // fn hash(&self) -> u8 {
466    //     let [r, g, b, a] = self;
467    //     r.wrapping_mul(3)
468    //         .wrapping_add(g.wrapping_mul(5))
469    //         .wrapping_add(b.wrapping_mul(7).wrapping_add(a.wrapping_mul(11)))
470    //         & 63
471    // }
472
473    #[inline]
474    fn hash(&self) -> u8 {
475        let v = u32::from_ne_bytes(*self);
476        let s = (((v as u64) << 32) | (v as u64)) & 0xFF00FF0000FF00FF;
477
478        s.wrapping_mul(0x030007000005000Bu64.to_le()).swap_bytes() as u8 & 63
479    }
480}
481
482/// Color variance value.
483/// Wrapping difference between two pixels.
484#[derive(Clone, Copy, Debug)]
485#[repr(C)]
486pub struct Var {
487    pub r: u8,
488    pub g: u8,
489    pub b: u8,
490}
491
492impl Var {
493    #[inline]
494    fn diff(&self) -> Option<u8> {
495        let r = self.r.wrapping_add(2);
496        let g = self.g.wrapping_add(2);
497        let b = self.b.wrapping_add(2);
498
499        match r | g | b {
500            0x00..=0x03 => Some(QOI_OP_DIFF | (r << 4) as u8 | (g << 2) as u8 | b as u8),
501            _ => None,
502        }
503    }
504
505    #[inline]
506    fn luma(&self) -> Option<[u8; 2]> {
507        let r = self.r.wrapping_add(8).wrapping_sub(self.g);
508        let g = self.g.wrapping_add(32);
509        let b = self.b.wrapping_add(8).wrapping_sub(self.g);
510
511        match (r | b, g) {
512            (0x00..=0x0F, 0x00..=0x3F) => Some([QOI_OP_LUMA | g, r << 4 | b]),
513            _ => None,
514        }
515    }
516}
517
518/// Image color space variants.
519#[derive(Clone, Copy, Debug)]
520pub enum Colors {
521    /// SRGB color channels.
522    Srgb,
523
524    /// SRGB color channels and linear alpha channel.
525    SrgbLinA,
526
527    /// Lineear color channels.
528    Rgb,
529
530    /// Linear color and alpha channels.
531    Rgba,
532}
533
534impl Colors {
535    /// Returns `true` if color space has alpha channel.
536    /// Returns `false` otherwise.
537    #[inline]
538    pub const fn has_alpha(&self) -> bool {
539        match self {
540            Colors::Rgb | Colors::Srgb => false,
541            Colors::Rgba | Colors::SrgbLinA => true,
542        }
543    }
544
545    /// Returns `true` if color space has alpha channel.
546    /// Returns `false` otherwise.
547    #[inline]
548    pub const fn channels(&self) -> usize {
549        match self {
550            Colors::Rgb | Colors::Srgb => 3,
551            Colors::Rgba | Colors::SrgbLinA => 4,
552        }
553    }
554}
555
556/// QOI descriptor value.\
557/// This value is parsed from image header during decoding.\
558/// Or provided by caller to drive encoding.
559#[derive(Clone, Copy, Debug)]
560pub struct Qoi {
561    /// Width of the image in pixels.
562    pub width: u32,
563
564    /// Height of the image in pixels.
565    pub height: u32,
566
567    /// Specifies image color space.
568    pub colors: Colors,
569}
570
571#[inline]
572#[cold]
573const fn cold() {}
574
575#[inline]
576const fn likely(b: bool) -> bool {
577    if !b {
578        cold();
579    }
580    b
581}
582
583#[inline]
584const fn unlikely(b: bool) -> bool {
585    if !b {
586        cold();
587    }
588    b
589}
590
591/// Next best thing after `core::hint::unreachable_unchecked()`
592/// If happens to be called this will stall CPU, instead of causing UB.
593#[inline]
594#[cold]
595#[allow(clippy::empty_loop)]
596const fn unreachable() -> ! {
597    loop {}
598}