From 0a43a8b9f79cfe7f57680143f75fb6a6cca5aba5 Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Fri, 14 Jun 2024 16:40:28 -0500 Subject: [PATCH] Split into a library and many modules Wow, what a mess. Why was I making one giant file to contain everything? I'm splitting it out into several files and taking another crack at completing the decoder. --- src/constants.rs | 10 + src/decoder.rs | 602 ++++++++++++++++++++++++++++++++++++++++++++ src/error.rs | 8 + src/lib.rs | 11 + src/main.rs | 632 +---------------------------------------------- src/pixel.rs | 18 ++ 6 files changed, 651 insertions(+), 630 deletions(-) create mode 100644 src/constants.rs create mode 100644 src/decoder.rs create mode 100644 src/error.rs create mode 100644 src/lib.rs create mode 100644 src/pixel.rs diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..15920a1 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,10 @@ + +pub const MAGIC: [u8; 4] = [b'q', b'o', b'i', b'f']; + +pub const QOI_OP_RGB: u8 = 0b1111_1110; +pub const QOI_OP_RGBA: u8 = 0b1111_1111; +pub const QOI_OP_INDEX: u8 = 0b0000_0000; +pub const QOI_OP_DIFF: u8 = 0b0100_0000; +pub const QOI_OP_LUMA: u8 = 0b1000_0000; +pub const QOI_OP_RUN: u8 = 0b1100_0000; +pub const QOI_OP_SMALL_MASK: u8 = 0b1100_0000; // mask for the small op codes diff --git a/src/decoder.rs b/src/decoder.rs new file mode 100644 index 0000000..139329f --- /dev/null +++ b/src/decoder.rs @@ -0,0 +1,602 @@ +use crate::{ + constants::*, + DecodeError, + PixelRGBA, +}; + +pub fn try_read_magic>(bytes: &mut I) -> Result<(), DecodeError> { + let magic: [u8; 4] = [b'q', b'o', b'i', b'f']; + for i in 0..4 { + if let Some(letter) = bytes.next() { + if letter != magic[i] { + return Err(DecodeError::Magic) + } + } else { + return Err(DecodeError::EarlyIteratorExhaustion) + } + } + return Ok(()) +} + +fn try_read_u32>(bytes: &mut I) -> Result { + let mut res: u32 = 0; + + Ok(res) +} + +mod codec_utils { + use super::PixelRGBA; + pub(crate) fn hash(pixel: PixelRGBA) -> u8 { + pixel + .r + .wrapping_mul(3) + .wrapping_add(pixel.g.wrapping_mul(5)) + .wrapping_add(pixel.b.wrapping_mul(7)) + .wrapping_add(pixel.a.wrapping_mul(11)) + % 64 + } +} + + +struct Decoder> { + // QOI codec state information + back_buffer: [PixelRGBA; 64], + prev_pixel: PixelRGBA, + + bytes: I, + run_len: u8, +} + +impl Decoder +where + I: Iterator, +{ + fn new(bytes: I) -> Self { + Self { + back_buffer: [PixelRGBA::zero(); 64], + prev_pixel: PixelRGBA { + r: 0, + g: 0, + b: 0, + a: 255, + }, + bytes, + run_len: 0, + } + } +} + +impl<'input, I> Iterator for Decoder +where + I: Iterator, +{ + type Item = PixelRGBA; + + fn next(&mut self) -> Option { + if self.run_len > 0 { + self.run_len -= 1; + Some(self.prev_pixel) + } else { + // Two kinds of patterns to match: + // 1. Whole byte tag -- RGB and RGBA + // 2. Partial byte tag -- 2 front bits of Index, Diff, Luma, and Run + let byte = self.bytes.next()?; + if byte == QOI_OP_RGB { + let result = PixelRGBA { + r: self.bytes.next()?, + g: self.bytes.next()?, + b: self.bytes.next()?, + a: self.prev_pixel.a, + }; + self.prev_pixel = result; + self.back_buffer[codec_utils::hash(result) as usize] = result; + return Some(result); + } else if byte == QOI_OP_RGBA { + let result = PixelRGBA { + r: self.bytes.next()?, + g: self.bytes.next()?, + b: self.bytes.next()?, + a: self.bytes.next()?, + }; + self.prev_pixel = result; + self.back_buffer[codec_utils::hash(result) as usize] = result; + return Some(result); + } else { + match byte & QOI_OP_SMALL_MASK { + QOI_OP_INDEX => { + let idx = (byte & !QOI_OP_SMALL_MASK) as usize; + self.prev_pixel = self.back_buffer[idx]; + return Some(self.back_buffer[idx]); + } + QOI_OP_DIFF => { + let dr = ((byte & 0b0011_0000) >> 4).wrapping_sub(2); + let dg = ((byte & 0b0000_1100) >> 2).wrapping_sub(2); + let db = (byte & 0b0000_0011).wrapping_sub(2); + let result = PixelRGBA { + r: self.prev_pixel.r.wrapping_add(dr), + g: self.prev_pixel.g.wrapping_add(dg), + b: self.prev_pixel.b.wrapping_add(db), + a: self.prev_pixel.a, + }; + self.prev_pixel = result; + self.back_buffer[codec_utils::hash(result) as usize] = result; + return Some(result); + } + QOI_OP_LUMA => { + let dg = (byte & !QOI_OP_SMALL_MASK).wrapping_sub(32); + let packed = self.bytes.next()?; + let drdg = ((packed & 0b1111_0000) >> 4).wrapping_sub(8); + let dbdg = (packed & 0b0000_1111).wrapping_sub(8); + let dr = drdg.wrapping_add(dg); + let db = dbdg.wrapping_add(dg); + let result = PixelRGBA { + r: self.prev_pixel.r.wrapping_add(dr), + g: self.prev_pixel.g.wrapping_add(dg), + b: self.prev_pixel.b.wrapping_add(db), + a: self.prev_pixel.a, + }; + self.prev_pixel = result; + self.back_buffer[codec_utils::hash(result) as usize] = result; + return Some(result); + } + QOI_OP_RUN => { + self.run_len = byte & !QOI_OP_SMALL_MASK; + // storage bias of -1, so a +1 should be on the end here. + // However, I'm immediately popping off the first occurrence + // and returning a PixelRGBA, so the count is also immediatly + // dropped by 1 + return Some(self.prev_pixel); + } + _ => panic!("bad op code{}", byte), + } + } + } + } +} + +#[cfg(test)] +mod test { + use super::*; + impl Decoder + where + I: Iterator, + { + // A hack to unit test the index lookup behavior. A partial test can be done + // to verify the basic indexing principles by preloading a known buffer and + // then extracting back-referenced data out of it. A complete test should + // feed in other valid operations that populate the backbuffer, and then index + // op codes to demonstrate the indexing operations. + fn new_with_backbuffer(bytes: I, back_buffer: [PixelRGBA; 64]) -> Self { + Self { + back_buffer, + prev_pixel: PixelRGBA { + r: 0, + g: 0, + b: 0, + a: 255, + }, + bytes, + run_len: 0, + } + } + + // A hack to unit test the run behavior. Same idea as the new_with_backbuffer() + // function, but for testing a run of pixels. + fn new_with_previous_pixel(bytes: I, prev_pixel: PixelRGBA) -> Self { + Self { + back_buffer: [PixelRGBA::zero(); 64], + prev_pixel, + bytes, + run_len: 0, + } + } + + fn peek_prev_pixel(&self) -> &PixelRGBA { + &self.prev_pixel + } + + fn peek_backbuffer(&self, idx: usize) -> &PixelRGBA { + &self.back_buffer[idx] + } + } + + #[test] // this is mostly just to drive the function. Make sure it wraps or crashes in debug. + fn test_backref_hash_function() { + let pixel = PixelRGBA { + r: 100, + g: 80, + b: 90, + a: 255, + }; + let expected = 39; + assert_eq!(codec_utils::hash(pixel), expected); + } + + #[test] + fn decoder_unpack_rgb() { + // compressed RGB values should be expanded back out to RGBA + // with an assumed alpha of 0xFF. + let compressed = [ + QOI_OP_RGB, 0xFF, 0xFF, 0xFF, QOI_OP_RGB, 0x7F, 0x00, 0xAD, QOI_OP_RGB, 0x00, 0x00, + 0x00, + ]; + + let expected = [ + PixelRGBA { + r: 0xFF, + g: 0xFF, + b: 0xFF, + a: 0xFF, + }, + PixelRGBA { + r: 0x7F, + g: 0x00, + b: 0xAD, + a: 0xFF, + }, + PixelRGBA { + r: 0x00, + g: 0x00, + b: 0x00, + a: 0xFF, + }, + ]; + + let decoder = Decoder::new(compressed.into_iter()); + let result = decoder.collect::>(); + assert_eq!(result, expected); + } + + #[test] + fn decoder_unpack_rgba() { + let compressed = [ + QOI_OP_RGBA, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + QOI_OP_RGBA, + 0x7F, + 0x7F, + 0x7F, + 0xFF, + QOI_OP_RGBA, + 0x10, + 0x20, + 0x30, + 0x40, + ]; + + let expected = [ + PixelRGBA { + r: 0xFF, + g: 0xFF, + b: 0xFF, + a: 0xFF, + }, + PixelRGBA { + r: 0x7f, + g: 0x7f, + b: 0x7f, + a: 0xFF, + }, + PixelRGBA { + r: 0x10, + g: 0x20, + b: 0x30, + a: 0x40, + }, + ]; + + let decoder = Decoder::new(compressed.into_iter()); + let result: Vec = decoder.collect(); + assert_eq!(result, expected); + } + + #[test] + fn decoder_unpack_index() { + let mut backbuffer = [PixelRGBA::zero(); 64]; + backbuffer[0] = PixelRGBA::new(255, 255, 255, 255); + backbuffer[1] = PixelRGBA::new(0, 255, 0, 255); + backbuffer[2] = PixelRGBA::new(255, 0, 255, 255); + backbuffer[3] = PixelRGBA::new(0, 0, 0, 0); + backbuffer[10] = PixelRGBA::new(10, 10, 10, 0); + backbuffer[11] = PixelRGBA::new(0xF0, 0x2C, 0xAF, 0xFF); + + let compressed = [ + (QOI_OP_INDEX | 0), + (QOI_OP_INDEX | 11), + (QOI_OP_INDEX | 1), + (QOI_OP_INDEX | 2), + (QOI_OP_INDEX | 3), + (QOI_OP_INDEX | 10), + (QOI_OP_INDEX | 42), + ]; + + let expected = [ + PixelRGBA::new(255, 255, 255, 255), + PixelRGBA::new(0xF0, 0x2C, 0xAF, 0xFF), + PixelRGBA::new(0, 0xFF, 0, 0xFF), + PixelRGBA::new(255, 0, 255, 255), + PixelRGBA::new(0, 0, 0, 0), + PixelRGBA::new(10, 10, 10, 0), + PixelRGBA::zero(), + ]; + + let decoder = Decoder::new_with_backbuffer(compressed.into_iter(), backbuffer); + let result: Vec = decoder.collect(); + assert_eq!(result, expected); + } + + #[test] + fn decoder_unpack_diff() { + // DIFF components are 2 bit values with a bias of 2. + // i.e. : 0b00 is -2, and 0b11 is +1 + let compressed = [ + (QOI_OP_DIFF | 0b0011_1111), // (1, 1, 1) + (QOI_OP_DIFF | 0b0011_1010), // (1, 0, 0) + (QOI_OP_DIFF | 0b0010_1110), // (0, 1, 0) + (QOI_OP_DIFF | 0b0010_1011), // (0, 0, 1) + (QOI_OP_DIFF | 0b0011_1011), // (1, 0, 1) + (QOI_OP_DIFF | 0b0001_1001), // (-1, 0, -1) + (QOI_OP_DIFF | 0b0000_0000), // (-2, -2, -2) + ]; + + // the codec begins with a pixel at (0, 0, 0, 255), so these results + // are diffs from that. + let expected = [ + PixelRGBA::new(1, 1, 1, 255), + PixelRGBA::new(2, 1, 1, 255), + PixelRGBA::new(2, 2, 1, 255), + PixelRGBA::new(2, 2, 2, 255), + PixelRGBA::new(3, 2, 3, 255), + PixelRGBA::new(2, 2, 2, 255), + PixelRGBA::new(0, 0, 0, 255), + ]; + + let decoder = Decoder::new(compressed.into_iter()); + let result: Vec = decoder.collect(); + assert_eq!(result, expected); + } + + #[test] + fn decoder_unpack_diff_rollover() { + let init_pixel = PixelRGBA::new(255, 255, 255, 255); + let compressed = [ + (QOI_OP_DIFF | 0b0011_1111), // +1s + (QOI_OP_DIFF | 0b0011_1111), // +1s + (QOI_OP_DIFF | 0b0010_1010), // +0s, could have been an index or a run, probably + ]; + let expected = [ + PixelRGBA::new(0, 0, 0, 255), // 255 rollover to 0 + PixelRGBA::new(1, 1, 1, 255), // +1s + PixelRGBA::new(1, 1, 1, 255), // holds at 1s + ]; + + let decoder = Decoder::new_with_previous_pixel(compressed.into_iter(), init_pixel); + let result: Vec = decoder.collect(); + assert_eq!(result, expected); + } + + #[test] + fn decoder_unpack_diff_rollunder() { + let init_pixel = PixelRGBA::new(0, 0, 0, 255); + let compressed = [ + (QOI_OP_DIFF | 0b0001_0101), // -1s + (QOI_OP_DIFF | 0b0001_0101), // -1s + (QOI_OP_DIFF | 0b0010_1010), // 0s + ]; + let expected = [ + PixelRGBA::new(255, 255, 255, 255), + PixelRGBA::new(254, 254, 254, 255), + PixelRGBA::new(254, 254, 254, 255), + ]; + + let decoder = Decoder::new_with_previous_pixel(compressed.into_iter(), init_pixel); + let result: Vec = decoder.collect(); + assert_eq!(result, expected); + } + + #[test] + fn decoder_unpack_luma() { + // red and blue diffs are relative to the green channel as (dr - dg) and (db - dg) + // Their finished diffs need to invert this operation. + // Diff(dg, dr-dg, db-dg) and Pix (dr, dg, db) + let compressed = [ + (QOI_OP_LUMA | 0b0011_1111), + (0b1111_1111), // Diff( 31, 7, 7) -> Pix (38, 31, 38) + (QOI_OP_LUMA | 0b0010_0000), + (0b1000_1000), // Diff( 0, 0, 0) -> Pix (0, 0, 0) + (QOI_OP_LUMA | 0b0010_0001), + (0b1111_1111), // Diff( 1, 7, 7) -> Pix (8, 1, 8) + (QOI_OP_LUMA | 0b0001_0011), + (0b1100_0011), // Diff(-13, 4, -5) -> Pix (-9, -13, -18) + ]; + + let expected = [ + PixelRGBA::new(38, 31, 38, 255), + PixelRGBA::new(38, 31, 38, 255), + PixelRGBA::new(46, 32, 46, 255), + PixelRGBA::new(37, 19, 28, 255), + ]; + + let decoder = Decoder::new(compressed.into_iter()); + let result: Vec = decoder.collect(); + assert_eq!(result, expected); + } + + #[test] + fn decoder_unpack_luma_rollover() { + let init_pixel = PixelRGBA::new(255, 255, 255, 255); + let compressed = [ + (QOI_OP_LUMA | 0b0011_1111), + (0b1111_1111), // Diff (31, 7, 7) -> Pix (38, 31, 38) + ]; + let expected = PixelRGBA::new(37, 30, 37, 255); + + let mut decoder = Decoder::new_with_previous_pixel(compressed.into_iter(), init_pixel); + let result = decoder + .next() + .expect("Oops, didn't get a Pixel back from the Decoder"); + assert_eq!(result, expected); + } + + #[test] + fn decoder_unpack_luma_rollunder() { + let init_pixel = PixelRGBA::new(0, 0, 0, 255); + let compressed = [ + (QOI_OP_LUMA | 0b0001_0011), + (0b1100_0011), // Diff(-13, 4, -5) -> Pix (-9, -13, -18) + ]; + let expected = PixelRGBA::new(247, 243, 238, 255); + + let mut decoder = Decoder::new_with_previous_pixel(compressed.into_iter(), init_pixel); + let result = decoder + .next() + .expect("Oops, didn't get a Pixel back from the Decoder"); + assert_eq!(result, expected); + } + + #[test] + fn decoder_unpack_run() { + let compressed = [ + (QOI_OP_RUN | 0b0000_0000), // 1 -- bias of -1, so all zeros is a run of 1 pixel + (QOI_OP_RUN | 0b0000_1100), // 13 // 0b1111? no it isn't. what? + ]; + + let init_pixel = PixelRGBA::new(50, 100, 150, 200); + let expected = [init_pixel; 14]; + + // the run instructions should really have been collapsed into just one, but + // lets pretend an encoder did this for some reason. The decoder can still + // unpack this correctly, it's just a sub-optimal compression is all. + + let decoder = Decoder::new_with_previous_pixel(compressed.into_iter(), init_pixel); + let result: Vec = decoder.collect(); + assert_eq!(result, expected); + } + + #[test] + fn decoder_prev_pixel_verify() { + let compressed = [ + QOI_OP_RGB, + 0x10, + 0x10, + 0x10, + QOI_OP_RGBA, + 0x20, + 0x20, + 0x20, + 0x20, + (QOI_OP_INDEX | 1), + (QOI_OP_DIFF | 0b0011_1111), + (QOI_OP_LUMA | 0b0011_1111), + 0b1111_1111, + (QOI_OP_RUN | 2), + QOI_OP_RGBA, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + ]; + + let expected = [ + PixelRGBA::new(0, 0, 0, 0xFF), // init + PixelRGBA::new(0x10, 0x10, 0x10, 0xFF), // RGB + PixelRGBA::new(0x20, 0x20, 0x20, 0x20), // RGBA + PixelRGBA::new(0, 0, 0, 0), // INDEX -- this doubles as a small test for the backbuffer operation + PixelRGBA::new(0x1, 0x1, 0x1, 0x0), // DIFF + PixelRGBA::new(0x27, 0x20, 0x27, 0x0), // LUMA + PixelRGBA::new(0x27, 0x20, 0x27, 0x0), // RUN 1 + PixelRGBA::new(0x27, 0x20, 0x27, 0x0), // RUN 2 + PixelRGBA::new(0x27, 0x20, 0x27, 0x0), // RUN 3 + PixelRGBA::new(0xFF, 0xFF, 0xFF, 0xFF), + ]; + + let mut decoder = Decoder::new(compressed.into_iter()); + + let mut result = Vec::::new(); + loop { + result.push(*decoder.peek_prev_pixel()); + if let None = decoder.next() { + break; + } + } + assert_eq!(result, expected); + } + + #[test] + fn decoder_backbuffer_verify() { + let compressed = [ + QOI_OP_RGB, + 0x10, + 0x10, + 0x10, + QOI_OP_RGBA, + 0x20, + 0x20, + 0x20, + 0x20, + QOI_OP_RGBA, + 0x03, + 0x01, + 0x01, + 0x04, // filler to populate backbuffer[1] for the next OP_INDEX + (QOI_OP_INDEX | 1), + (QOI_OP_DIFF | 0b0011_1111), // + Pix (1, 1, 1, 0) + (QOI_OP_LUMA | 0b0011_1111), + 0b1111_1111, // + Pix (38, 31, 38) + (QOI_OP_RUN | 2), + QOI_OP_RGBA, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + ]; + // these are the indices where we're expecting each pixel to land. + // Each pixel gets put into this backbuffer as it's en/de-coded. + // For RGB and RGBA, it'll simply assign a value into the index. + + // For INDEX, the write can be skipped, and the expected index will be + // the same as the one in the op code. + // *however* it's possible for an index to refer to a value that shouldn't + // hash to that location. The backbuffer initialization will cause this + // scenario. + // + // Without an OP_INDEX buffer write, this condition will cause codec corruption. + // If the Indexed value should have landed somewhere else, and that other location + // has a different value in it, then the next operation to reference that field + // will receive the wrong data. + + // OP_DIFF & OP_LUMA need to consider the value in the backbuffer, as + // they'll be using it to compute the new pixel. + let indices = [ + 37, // Pix (16, 16, 16, 255) + 0, // Pix (32, 32, 32, 32) + 1, // Pix (3, 1, 1, 4) + 1, // Pix (3, 1, 1, 4) + 16, // Pix (4, 2, 2, 4) + 39, // Pix (42, 33, 40, 4) + 39, // run x1 + 39, // run x2 + 39, // run x3 + 38, // final RGBA + ]; + let mut decoder = Decoder::new(compressed.into_iter()); + let mut iters = 0; + loop { + if let Some(pixel) = decoder.next() { + // pixel has been decompressed, so it should be in the backbuffer by this point + + // query it out: + let stored_px = decoder.peek_backbuffer(indices[iters]); + + // and compare it to the value returned from iteration + assert_eq!(&pixel, stored_px); + } else { + break; + } + iters += 1; + } + } +} \ No newline at end of file diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..57b32c2 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,8 @@ + +#[derive(Debug)] +pub enum DecodeError { + Magic, + Channels, + ColorSpace, + EarlyIteratorExhaustion, +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a93e3ec --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ + +pub(crate) mod constants; +mod decoder; +// mod encode; +mod error; +mod pixel; + +pub use decoder::*; +// pub use encode::*; +pub use error::*; +pub use pixel::*; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 6c2eb9a..8abf080 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,6 @@ +use qoicodec::DecodeError; +use qoicodec::try_read_magic; use std::{fs::File, io::Read}; fn main() { @@ -21,633 +23,3 @@ fn main() { todo!("The rest of the main function"); } - -fn try_read_magic>(bytes: &mut I) -> Result<(), DecodeError> { - let magic: [u8; 4] = [b'q', b'o', b'i', b'f']; - for i in 0..4 { - if let Some(letter) = bytes.next() { - if letter != magic[i] { - return Err(DecodeError::Magic) - } - } else { - return Err(DecodeError::EarlyIteratorExhaustion) - } - } - return Ok(()) -} - -fn try_read_u32>(bytes: &mut I) -> Result { - let mut res: u32 = 0; - - Ok(res) -} - -#[derive(Debug)] -enum DecodeError { - Magic, - Channels, - ColorSpace, - EarlyIteratorExhaustion, -} - -#[derive(Clone, Copy, Default, Debug, PartialEq)] -struct PixelRGBA { - r: u8, - g: u8, - b: u8, - a: u8, -} - -impl PixelRGBA { - fn new(r: u8, g: u8, b: u8, a: u8) -> Self { - Self { r, g, b, a } - } - - fn zero() -> Self { - Self::new(0, 0, 0, 0) - } -} - -const QOI_OP_RGB: u8 = 0b1111_1110; -const QOI_OP_RGBA: u8 = 0b1111_1111; -const QOI_OP_INDEX: u8 = 0b0000_0000; -const QOI_OP_DIFF: u8 = 0b0100_0000; -const QOI_OP_LUMA: u8 = 0b1000_0000; -const QOI_OP_RUN: u8 = 0b1100_0000; -const QOI_OP_SMALL_MASK: u8 = 0b1100_0000; // mask for the small op codes - -struct Decoder> { - // QOI codec state information - back_buffer: [PixelRGBA; 64], - prev_pixel: PixelRGBA, - - bytes: I, - run_len: u8, -} - -impl Decoder -where - I: Iterator, -{ - fn new(bytes: I) -> Self { - Self { - back_buffer: [PixelRGBA::zero(); 64], - prev_pixel: PixelRGBA { - r: 0, - g: 0, - b: 0, - a: 255, - }, - bytes, - run_len: 0, - } - } -} - -mod codec_utils { - use super::PixelRGBA; - pub(crate) fn hash(pixel: PixelRGBA) -> u8 { - pixel - .r - .wrapping_mul(3) - .wrapping_add(pixel.g.wrapping_mul(5)) - .wrapping_add(pixel.b.wrapping_mul(7)) - .wrapping_add(pixel.a.wrapping_mul(11)) - % 64 - } -} - -impl<'input, I> Iterator for Decoder -where - I: Iterator, -{ - type Item = PixelRGBA; - - fn next(&mut self) -> Option { - if self.run_len > 0 { - self.run_len -= 1; - Some(self.prev_pixel) - } else { - // Two kinds of patterns to match: - // 1. Whole byte tag -- RGB and RGBA - // 2. Partial byte tag -- 2 front bits of Index, Diff, Luma, and Run - let byte = self.bytes.next()?; - if byte == QOI_OP_RGB { - let result = PixelRGBA { - r: self.bytes.next()?, - g: self.bytes.next()?, - b: self.bytes.next()?, - a: self.prev_pixel.a, - }; - self.prev_pixel = result; - self.back_buffer[codec_utils::hash(result) as usize] = result; - return Some(result); - } else if byte == QOI_OP_RGBA { - let result = PixelRGBA { - r: self.bytes.next()?, - g: self.bytes.next()?, - b: self.bytes.next()?, - a: self.bytes.next()?, - }; - self.prev_pixel = result; - self.back_buffer[codec_utils::hash(result) as usize] = result; - return Some(result); - } else { - match byte & QOI_OP_SMALL_MASK { - QOI_OP_INDEX => { - let idx = (byte & !QOI_OP_SMALL_MASK) as usize; - self.prev_pixel = self.back_buffer[idx]; - return Some(self.back_buffer[idx]); - } - QOI_OP_DIFF => { - let dr = ((byte & 0b0011_0000) >> 4).wrapping_sub(2); - let dg = ((byte & 0b0000_1100) >> 2).wrapping_sub(2); - let db = (byte & 0b0000_0011).wrapping_sub(2); - let result = PixelRGBA { - r: self.prev_pixel.r.wrapping_add(dr), - g: self.prev_pixel.g.wrapping_add(dg), - b: self.prev_pixel.b.wrapping_add(db), - a: self.prev_pixel.a, - }; - self.prev_pixel = result; - self.back_buffer[codec_utils::hash(result) as usize] = result; - return Some(result); - } - QOI_OP_LUMA => { - let dg = (byte & !QOI_OP_SMALL_MASK).wrapping_sub(32); - let packed = self.bytes.next()?; - let drdg = ((packed & 0b1111_0000) >> 4).wrapping_sub(8); - let dbdg = (packed & 0b0000_1111).wrapping_sub(8); - let dr = drdg.wrapping_add(dg); - let db = dbdg.wrapping_add(dg); - let result = PixelRGBA { - r: self.prev_pixel.r.wrapping_add(dr), - g: self.prev_pixel.g.wrapping_add(dg), - b: self.prev_pixel.b.wrapping_add(db), - a: self.prev_pixel.a, - }; - self.prev_pixel = result; - self.back_buffer[codec_utils::hash(result) as usize] = result; - return Some(result); - } - QOI_OP_RUN => { - self.run_len = byte & !QOI_OP_SMALL_MASK; - // storage bias of -1, so a +1 should be on the end here. - // However, I'm immediately popping off the first occurrence - // and returning a PixelRGBA, so the count is also immediatly - // dropped by 1 - return Some(self.prev_pixel); - } - _ => panic!("bad op code{}", byte), - } - } - } - } -} - -#[cfg(test)] -mod test { - use super::*; - impl Decoder - where - I: Iterator, - { - // A hack to unit test the index lookup behavior. A partial test can be done - // to verify the basic indexing principles by preloading a known buffer and - // then extracting back-referenced data out of it. A complete test should - // feed in other valid operations that populate the backbuffer, and then index - // op codes to demonstrate the indexing operations. - fn new_with_backbuffer(bytes: I, back_buffer: [PixelRGBA; 64]) -> Self { - Self { - back_buffer, - prev_pixel: PixelRGBA { - r: 0, - g: 0, - b: 0, - a: 255, - }, - bytes, - run_len: 0, - } - } - - // A hack to unit test the run behavior. Same idea as the new_with_backbuffer() - // function, but for testing a run of pixels. - fn new_with_previous_pixel(bytes: I, prev_pixel: PixelRGBA) -> Self { - Self { - back_buffer: [PixelRGBA::zero(); 64], - prev_pixel, - bytes, - run_len: 0, - } - } - - fn peek_prev_pixel(&self) -> &PixelRGBA { - &self.prev_pixel - } - - fn peek_backbuffer(&self, idx: usize) -> &PixelRGBA { - &self.back_buffer[idx] - } - } - - #[test] // this is mostly just to drive the function. Make sure it wraps or crashes in debug. - fn test_backref_hash_function() { - let pixel = PixelRGBA { - r: 100, - g: 80, - b: 90, - a: 255, - }; - let expected = 39; - assert_eq!(codec_utils::hash(pixel), expected); - } - - #[test] - fn decoder_unpack_rgb() { - // compressed RGB values should be expanded back out to RGBA - // with an assumed alpha of 0xFF. - let compressed = [ - QOI_OP_RGB, 0xFF, 0xFF, 0xFF, QOI_OP_RGB, 0x7F, 0x00, 0xAD, QOI_OP_RGB, 0x00, 0x00, - 0x00, - ]; - - let expected = [ - PixelRGBA { - r: 0xFF, - g: 0xFF, - b: 0xFF, - a: 0xFF, - }, - PixelRGBA { - r: 0x7F, - g: 0x00, - b: 0xAD, - a: 0xFF, - }, - PixelRGBA { - r: 0x00, - g: 0x00, - b: 0x00, - a: 0xFF, - }, - ]; - - let decoder = Decoder::new(compressed.into_iter()); - let result = decoder.collect::>(); - assert_eq!(result, expected); - } - - #[test] - fn decoder_unpack_rgba() { - let compressed = [ - QOI_OP_RGBA, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - QOI_OP_RGBA, - 0x7F, - 0x7F, - 0x7F, - 0xFF, - QOI_OP_RGBA, - 0x10, - 0x20, - 0x30, - 0x40, - ]; - - let expected = [ - PixelRGBA { - r: 0xFF, - g: 0xFF, - b: 0xFF, - a: 0xFF, - }, - PixelRGBA { - r: 0x7f, - g: 0x7f, - b: 0x7f, - a: 0xFF, - }, - PixelRGBA { - r: 0x10, - g: 0x20, - b: 0x30, - a: 0x40, - }, - ]; - - let decoder = Decoder::new(compressed.into_iter()); - let result: Vec = decoder.collect(); - assert_eq!(result, expected); - } - - #[test] - fn decoder_unpack_index() { - let mut backbuffer = [PixelRGBA::zero(); 64]; - backbuffer[0] = PixelRGBA::new(255, 255, 255, 255); - backbuffer[1] = PixelRGBA::new(0, 255, 0, 255); - backbuffer[2] = PixelRGBA::new(255, 0, 255, 255); - backbuffer[3] = PixelRGBA::new(0, 0, 0, 0); - backbuffer[10] = PixelRGBA::new(10, 10, 10, 0); - backbuffer[11] = PixelRGBA::new(0xF0, 0x2C, 0xAF, 0xFF); - - let compressed = [ - (QOI_OP_INDEX | 0), - (QOI_OP_INDEX | 11), - (QOI_OP_INDEX | 1), - (QOI_OP_INDEX | 2), - (QOI_OP_INDEX | 3), - (QOI_OP_INDEX | 10), - (QOI_OP_INDEX | 42), - ]; - - let expected = [ - PixelRGBA::new(255, 255, 255, 255), - PixelRGBA::new(0xF0, 0x2C, 0xAF, 0xFF), - PixelRGBA::new(0, 0xFF, 0, 0xFF), - PixelRGBA::new(255, 0, 255, 255), - PixelRGBA::new(0, 0, 0, 0), - PixelRGBA::new(10, 10, 10, 0), - PixelRGBA::zero(), - ]; - - let decoder = Decoder::new_with_backbuffer(compressed.into_iter(), backbuffer); - let result: Vec = decoder.collect(); - assert_eq!(result, expected); - } - - #[test] - fn decoder_unpack_diff() { - // DIFF components are 2 bit values with a bias of 2. - // i.e. : 0b00 is -2, and 0b11 is +1 - let compressed = [ - (QOI_OP_DIFF | 0b0011_1111), // (1, 1, 1) - (QOI_OP_DIFF | 0b0011_1010), // (1, 0, 0) - (QOI_OP_DIFF | 0b0010_1110), // (0, 1, 0) - (QOI_OP_DIFF | 0b0010_1011), // (0, 0, 1) - (QOI_OP_DIFF | 0b0011_1011), // (1, 0, 1) - (QOI_OP_DIFF | 0b0001_1001), // (-1, 0, -1) - (QOI_OP_DIFF | 0b0000_0000), // (-2, -2, -2) - ]; - - // the codec begins with a pixel at (0, 0, 0, 255), so these results - // are diffs from that. - let expected = [ - PixelRGBA::new(1, 1, 1, 255), - PixelRGBA::new(2, 1, 1, 255), - PixelRGBA::new(2, 2, 1, 255), - PixelRGBA::new(2, 2, 2, 255), - PixelRGBA::new(3, 2, 3, 255), - PixelRGBA::new(2, 2, 2, 255), - PixelRGBA::new(0, 0, 0, 255), - ]; - - let decoder = Decoder::new(compressed.into_iter()); - let result: Vec = decoder.collect(); - assert_eq!(result, expected); - } - - #[test] - fn decoder_unpack_diff_rollover() { - let init_pixel = PixelRGBA::new(255, 255, 255, 255); - let compressed = [ - (QOI_OP_DIFF | 0b0011_1111), // +1s - (QOI_OP_DIFF | 0b0011_1111), // +1s - (QOI_OP_DIFF | 0b0010_1010), // +0s, could have been an index or a run, probably - ]; - let expected = [ - PixelRGBA::new(0, 0, 0, 255), // 255 rollover to 0 - PixelRGBA::new(1, 1, 1, 255), // +1s - PixelRGBA::new(1, 1, 1, 255), // holds at 1s - ]; - - let decoder = Decoder::new_with_previous_pixel(compressed.into_iter(), init_pixel); - let result: Vec = decoder.collect(); - assert_eq!(result, expected); - } - - #[test] - fn decoder_unpack_diff_rollunder() { - let init_pixel = PixelRGBA::new(0, 0, 0, 255); - let compressed = [ - (QOI_OP_DIFF | 0b0001_0101), // -1s - (QOI_OP_DIFF | 0b0001_0101), // -1s - (QOI_OP_DIFF | 0b0010_1010), // 0s - ]; - let expected = [ - PixelRGBA::new(255, 255, 255, 255), - PixelRGBA::new(254, 254, 254, 255), - PixelRGBA::new(254, 254, 254, 255), - ]; - - let decoder = Decoder::new_with_previous_pixel(compressed.into_iter(), init_pixel); - let result: Vec = decoder.collect(); - assert_eq!(result, expected); - } - - #[test] - fn decoder_unpack_luma() { - // red and blue diffs are relative to the green channel as (dr - dg) and (db - dg) - // Their finished diffs need to invert this operation. - // Diff(dg, dr-dg, db-dg) and Pix (dr, dg, db) - let compressed = [ - (QOI_OP_LUMA | 0b0011_1111), - (0b1111_1111), // Diff( 31, 7, 7) -> Pix (38, 31, 38) - (QOI_OP_LUMA | 0b0010_0000), - (0b1000_1000), // Diff( 0, 0, 0) -> Pix (0, 0, 0) - (QOI_OP_LUMA | 0b0010_0001), - (0b1111_1111), // Diff( 1, 7, 7) -> Pix (8, 1, 8) - (QOI_OP_LUMA | 0b0001_0011), - (0b1100_0011), // Diff(-13, 4, -5) -> Pix (-9, -13, -18) - ]; - - let expected = [ - PixelRGBA::new(38, 31, 38, 255), - PixelRGBA::new(38, 31, 38, 255), - PixelRGBA::new(46, 32, 46, 255), - PixelRGBA::new(37, 19, 28, 255), - ]; - - let decoder = Decoder::new(compressed.into_iter()); - let result: Vec = decoder.collect(); - assert_eq!(result, expected); - } - - #[test] - fn decoder_unpack_luma_rollover() { - let init_pixel = PixelRGBA::new(255, 255, 255, 255); - let compressed = [ - (QOI_OP_LUMA | 0b0011_1111), - (0b1111_1111), // Diff (31, 7, 7) -> Pix (38, 31, 38) - ]; - let expected = PixelRGBA::new(37, 30, 37, 255); - - let mut decoder = Decoder::new_with_previous_pixel(compressed.into_iter(), init_pixel); - let result = decoder - .next() - .expect("Oops, didn't get a Pixel back from the Decoder"); - assert_eq!(result, expected); - } - - #[test] - fn decoder_unpack_luma_rollunder() { - let init_pixel = PixelRGBA::new(0, 0, 0, 255); - let compressed = [ - (QOI_OP_LUMA | 0b0001_0011), - (0b1100_0011), // Diff(-13, 4, -5) -> Pix (-9, -13, -18) - ]; - let expected = PixelRGBA::new(247, 243, 238, 255); - - let mut decoder = Decoder::new_with_previous_pixel(compressed.into_iter(), init_pixel); - let result = decoder - .next() - .expect("Oops, didn't get a Pixel back from the Decoder"); - assert_eq!(result, expected); - } - - #[test] - fn decoder_unpack_run() { - let compressed = [ - (QOI_OP_RUN | 0b0000_0000), // 1 -- bias of -1, so all zeros is a run of 1 pixel - (QOI_OP_RUN | 0b0000_1100), // 13 // 0b1111? no it isn't. what? - ]; - - let init_pixel = PixelRGBA::new(50, 100, 150, 200); - let expected = [init_pixel; 14]; - - // the run instructions should really have been collapsed into just one, but - // lets pretend an encoder did this for some reason. The decoder can still - // unpack this correctly, it's just a sub-optimal compression is all. - - let decoder = Decoder::new_with_previous_pixel(compressed.into_iter(), init_pixel); - let result: Vec = decoder.collect(); - assert_eq!(result, expected); - } - - #[test] - fn decoder_prev_pixel_verify() { - let compressed = [ - QOI_OP_RGB, - 0x10, - 0x10, - 0x10, - QOI_OP_RGBA, - 0x20, - 0x20, - 0x20, - 0x20, - (QOI_OP_INDEX | 1), - (QOI_OP_DIFF | 0b0011_1111), - (QOI_OP_LUMA | 0b0011_1111), - 0b1111_1111, - (QOI_OP_RUN | 2), - QOI_OP_RGBA, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - ]; - - let expected = [ - PixelRGBA::new(0, 0, 0, 0xFF), // init - PixelRGBA::new(0x10, 0x10, 0x10, 0xFF), // RGB - PixelRGBA::new(0x20, 0x20, 0x20, 0x20), // RGBA - PixelRGBA::new(0, 0, 0, 0), // INDEX -- this doubles as a small test for the backbuffer operation - PixelRGBA::new(0x1, 0x1, 0x1, 0x0), // DIFF - PixelRGBA::new(0x27, 0x20, 0x27, 0x0), // LUMA - PixelRGBA::new(0x27, 0x20, 0x27, 0x0), // RUN 1 - PixelRGBA::new(0x27, 0x20, 0x27, 0x0), // RUN 2 - PixelRGBA::new(0x27, 0x20, 0x27, 0x0), // RUN 3 - PixelRGBA::new(0xFF, 0xFF, 0xFF, 0xFF), - ]; - - let mut decoder = Decoder::new(compressed.into_iter()); - - let mut result = Vec::::new(); - loop { - result.push(*decoder.peek_prev_pixel()); - if let None = decoder.next() { - break; - } - } - assert_eq!(result, expected); - } - - #[test] - fn decoder_backbuffer_verify() { - let compressed = [ - QOI_OP_RGB, - 0x10, - 0x10, - 0x10, - QOI_OP_RGBA, - 0x20, - 0x20, - 0x20, - 0x20, - QOI_OP_RGBA, - 0x03, - 0x01, - 0x01, - 0x04, // filler to populate backbuffer[1] for the next OP_INDEX - (QOI_OP_INDEX | 1), - (QOI_OP_DIFF | 0b0011_1111), // + Pix (1, 1, 1, 0) - (QOI_OP_LUMA | 0b0011_1111), - 0b1111_1111, // + Pix (38, 31, 38) - (QOI_OP_RUN | 2), - QOI_OP_RGBA, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - ]; - // these are the indices where we're expecting each pixel to land. - // Each pixel gets put into this backbuffer as it's en/de-coded. - // For RGB and RGBA, it'll simply assign a value into the index. - - // For INDEX, the write can be skipped, and the expected index will be - // the same as the one in the op code. - // *however* it's possible for an index to refer to a value that shouldn't - // hash to that location. The backbuffer initialization will cause this - // scenario. - // - // Without an OP_INDEX buffer write, this condition will cause codec corruption. - // If the Indexed value should have landed somewhere else, and that other location - // has a different value in it, then the next operation to reference that field - // will receive the wrong data. - - // OP_DIFF & OP_LUMA need to consider the value in the backbuffer, as - // they'll be using it to compute the new pixel. - let indices = [ - 37, // Pix (16, 16, 16, 255) - 0, // Pix (32, 32, 32, 32) - 1, // Pix (3, 1, 1, 4) - 1, // Pix (3, 1, 1, 4) - 16, // Pix (4, 2, 2, 4) - 39, // Pix (42, 33, 40, 4) - 39, // run x1 - 39, // run x2 - 39, // run x3 - 38, // final RGBA - ]; - let mut decoder = Decoder::new(compressed.into_iter()); - let mut iters = 0; - loop { - if let Some(pixel) = decoder.next() { - // pixel has been decompressed, so it should be in the backbuffer by this point - - // query it out: - let stored_px = decoder.peek_backbuffer(indices[iters]); - - // and compare it to the value returned from iteration - assert_eq!(&pixel, stored_px); - } else { - break; - } - iters += 1; - } - } -} diff --git a/src/pixel.rs b/src/pixel.rs new file mode 100644 index 0000000..fe4c69d --- /dev/null +++ b/src/pixel.rs @@ -0,0 +1,18 @@ + +#[derive(Clone, Copy, Default, Debug, PartialEq)] +pub struct PixelRGBA { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +impl PixelRGBA { + pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { + Self { r, g, b, a } + } + + pub fn zero() -> Self { + Self::new(0, 0, 0, 0) + } +} \ No newline at end of file