diff --git a/src/main.rs b/src/main.rs index e3b6ec1..8217c73 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,10 +11,10 @@ struct PixelRGBA { } impl PixelRGBA { - fn new(r: u8, g: u8, b: u8, a: u8 ) -> Self { - Self {r, g, b, a } + 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) } @@ -27,7 +27,7 @@ const QOI_OP_DIFF: u8 = 0b0100_0000; const QOI_OP_LUMA: u8 = 0b1000_0000; const QOI_OP_RUN: u8 = 0b1100_0000; -struct Decoder> { +struct Decoder> { // QOI codec state information back_buffer: [PixelRGBA; 64], prev_pixel: PixelRGBA, @@ -36,7 +36,10 @@ struct Decoder> { run_len: u8, } -impl Decoder where I: Iterator { +impl Decoder +where + I: Iterator, +{ fn new(bytes: I) -> Self { Self { back_buffer: [PixelRGBA::zero(); 64], @@ -60,7 +63,10 @@ impl Decoder where I: Iterator { Self { back_buffer, prev_pixel: PixelRGBA { - r: 0, g: 0, b: 0, a: 255, + r: 0, + g: 0, + b: 0, + a: 255, }, bytes, run_len: 0, @@ -91,7 +97,10 @@ mod codec_utils { } } -impl<'input, I> Iterator for Decoder where I: Iterator { +impl<'input, I> Iterator for Decoder +where + I: Iterator, +{ type Item = PixelRGBA; fn next(&mut self) -> Option { @@ -99,65 +108,67 @@ impl<'input, I> Iterator for Decoder where I: Iterator { self.run_len -= 1; return Some(self.prev_pixel); } else { - let byte = self.bytes.next()?; - match byte { - QOI_OP_RGB => { - return Some(PixelRGBA { - r: self.bytes.next()?, - g: self.bytes.next()?, - b: self.bytes.next()?, - a: self.prev_pixel.a, - }); - }, - QOI_OP_RGBA => { - return Some(PixelRGBA { - r: self.bytes.next()?, - g: self.bytes.next()?, - b: self.bytes.next()?, - a: self.bytes.next()?, - }); - }, - QOI_OP_INDEX => { - let idx = (byte | 0b0011_1111) as usize; - return Some(self.back_buffer[idx]); - }, - QOI_OP_DIFF => { - let dr = ((byte | 0b0011_0000) >> 4) - 2; - let dg = ((byte | 0b0000_1100) >> 2) - 2; - let db = (byte | 0b0000_0011) - 2; - return Some(PixelRGBA { - r: self.prev_pixel.r + dr, - g: self.prev_pixel.g + dg, - b: self.prev_pixel.b + db, - a: self.prev_pixel.a - }); - }, - QOI_OP_LUMA => { - let dg = (byte | 0b0011_1111) - 32; - let packed = self.bytes.next()?; - let drdg = ((packed | 0b1111_0000) >> 4) - 8; - let dbdg = (packed | 0b0000_1111) - 8; - let dr = drdg + dg; - let db = dbdg + dg; - return Some(PixelRGBA { - r: self.prev_pixel.r + dr, - g: self.prev_pixel.g + dg, - b: self.prev_pixel.b + db, - a: self.prev_pixel.a - }); - }, - QOI_OP_RUN => { - self.run_len = byte | 0b0011_1111; - // 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")} + let byte = self.bytes.next()?; + match byte { + QOI_OP_RGB => { + return Some(PixelRGBA { + r: self.bytes.next()?, + g: self.bytes.next()?, + b: self.bytes.next()?, + a: self.prev_pixel.a, + }); + } + QOI_OP_RGBA => { + return Some(PixelRGBA { + r: self.bytes.next()?, + g: self.bytes.next()?, + b: self.bytes.next()?, + a: self.bytes.next()?, + }); + } + QOI_OP_INDEX => { + let idx = (byte | 0b0011_1111) as usize; + return Some(self.back_buffer[idx]); + } + QOI_OP_DIFF => { + let dr = ((byte | 0b0011_0000) >> 4) - 2; + let dg = ((byte | 0b0000_1100) >> 2) - 2; + let db = (byte | 0b0000_0011) - 2; + return Some(PixelRGBA { + r: self.prev_pixel.r + dr, + g: self.prev_pixel.g + dg, + b: self.prev_pixel.b + db, + a: self.prev_pixel.a, + }); + } + QOI_OP_LUMA => { + let dg = (byte | 0b0011_1111) - 32; + let packed = self.bytes.next()?; + let drdg = ((packed | 0b1111_0000) >> 4) - 8; + let dbdg = (packed | 0b0000_1111) - 8; + let dr = drdg + dg; + let db = dbdg + dg; + return Some(PixelRGBA { + r: self.prev_pixel.r + dr, + g: self.prev_pixel.g + dg, + b: self.prev_pixel.b + db, + a: self.prev_pixel.a, + }); + } + QOI_OP_RUN => { + self.run_len = byte | 0b0011_1111; + // 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") + } + } } } - } } #[cfg(test)] @@ -181,15 +192,29 @@ mod test { // 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 + 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} + 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()); @@ -200,15 +225,42 @@ mod test { #[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, + 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 }, + 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()); @@ -225,7 +277,7 @@ mod test { 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), @@ -233,7 +285,7 @@ mod test { (QOI_OP_INDEX | 2), (QOI_OP_INDEX | 3), (QOI_OP_INDEX | 10), - (QOI_OP_INDEX | 42) + (QOI_OP_INDEX | 42), ]; let expected = [ @@ -264,7 +316,7 @@ mod test { (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 = [ @@ -284,15 +336,18 @@ mod test { #[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 | 0b0011_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) + (QOI_OP_LUMA | 0b0011_1111), + (0b1111_1111), // Diff( 31, 7, 7) -> Pix (38, 31, 38) + (QOI_OP_LUMA | 0b0011_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 = [ @@ -313,14 +368,14 @@ mod test { (QOI_OP_RUN | 0b0000_0000), // 1 -- bias of -1, so all zeros is a run of 1 pixel (QOI_OP_RUN | 0b0000_1111), // 13 ]; - + 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);