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}