diff --git a/src/decode.rs b/src/decode.rs index 4d86a5b..197f2ec 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -17,37 +17,6 @@ const QOI_OP_RUN_END: u8 = QOI_OP_RUN | 0x3d; // <- note, 0x3d (not 0x3f) const QOI_OP_DIFF_END: u8 = QOI_OP_DIFF | 0x3f; const QOI_OP_LUMA_END: u8 = QOI_OP_LUMA | 0x3f; -#[inline(always)] -pub const fn hash_pixel(px: [u8; N]) -> u8 { - let r = px[0].wrapping_mul(3); - let g = px[1].wrapping_mul(5); - let b = px[2].wrapping_mul(7); - let a = (if N == 4 { px[3] } else { 0xff }).wrapping_mul(11); - r.wrapping_add(g).wrapping_add(b).wrapping_add(a) & 0x3f -} - -macro_rules! decode { - (rgb: $r:expr, $g:expr, $b:expr => $px:expr) => { - $px[0] = $r; - $px[1] = $g; - $px[2] = $b; - }; - (diff: $b1:expr => $px:expr) => { - $px[0] = $px[0].wrapping_add(($b1 >> 4) & 0x03).wrapping_sub(2); - $px[1] = $px[1].wrapping_add(($b1 >> 2) & 0x03).wrapping_sub(2); - $px[2] = $px[2].wrapping_add($b1 & 0x03).wrapping_sub(2); - }; - (luma: $b1:expr, $b2:expr => $px:expr) => { - let vg = ($b1 & 0x3f).wrapping_sub(32); - let vg_8 = vg.wrapping_sub(8); - let vr = vg_8.wrapping_add(($b2 >> 4) & 0x0f); - let vb = vg_8.wrapping_add($b2 & 0x0f); - $px[0] = $px[0].wrapping_add(vr); - $px[1] = $px[1].wrapping_add(vg); - $px[2] = $px[2].wrapping_add(vb); - }; -} - #[inline] fn qoi_decode_impl_slice( data: &[u8], out: &mut [u8], @@ -60,47 +29,41 @@ where let data_len = data.len(); let mut data = data; - let mut index = [[0_u8; N]; 256]; - let mut px = [0_u8; N]; - if N == 4 { - px[3] = 0xff; - } + let mut index = [Pixel::::new(); 256]; + let mut px = Pixel::::new().with_a(0xff); while let [px_out, ptail @ ..] = pixels { pixels = ptail; match data { [b1 @ QOI_OP_INDEX..=QOI_OP_INDEX_END, dtail @ ..] => { px = index[*b1 as usize]; - *px_out = px; + *px_out = px.into(); data = dtail; continue; } [QOI_OP_RGB, r, g, b, dtail @ ..] => { - decode!(rgb: *r, *g, *b => px); + px.update_rgb(*r, *g, *b); data = dtail; } [QOI_OP_RGBA, r, g, b, a, dtail @ ..] if RGBA => { - decode!(rgb: *r, *g, *b => px); - if N == 4 { - px[3] = *a; - } + px.update_rgba(*r, *g, *b, *a); data = dtail; } [b1 @ QOI_OP_RUN..=QOI_OP_RUN_END, dtail @ ..] => { - *px_out = px; + *px_out = px.into(); let run = ((b1 & 0x3f) as usize).min(pixels.len()); let (phead, ptail) = pixels.split_at_mut(run); // can't panic - phead.fill(px); + phead.fill(px.into()); pixels = ptail; data = dtail; continue; } [b1 @ QOI_OP_DIFF..=QOI_OP_DIFF_END, dtail @ ..] => { - decode!(diff: b1 => px); + px.update_diff(*b1); data = dtail; } [b1 @ QOI_OP_LUMA..=QOI_OP_LUMA_END, b2, dtail @ ..] => { - decode!(luma: b1, b2 => px); + px.update_luma(*b1, *b2); data = dtail; } _ => { @@ -111,8 +74,8 @@ where } } - index[hash_pixel(px) as usize] = px; - *px_out = px; + index[px.hash_index() as usize] = px; + *px_out = px.into(); } if unlikely(data.len() < QOI_PADDING_SIZE) { @@ -231,11 +194,8 @@ where { let mut pixels = cast_slice_mut::<_, [u8; N]>(out); - let mut index = [[0_u8; N]; 256]; - let mut px = [0_u8; N]; - if N == 4 { - px[3] = 0xff; - } + let mut index = [Pixel::::new(); 256]; + let mut px = Pixel::::new().with_a(0xff); while let [px_out, ptail @ ..] = pixels { pixels = ptail; @@ -245,46 +205,43 @@ where match b1 { QOI_OP_INDEX..=QOI_OP_INDEX_END => { px = index[b1 as usize]; - *px_out = px; + *px_out = px.into(); continue; } QOI_OP_RGB => { let mut p = [0; 3]; data.read_exact(&mut p)?; - decode!(rgb: p[0], p[1], p[2] => px); + px.update_rgb(p[0], p[1], p[2]); } QOI_OP_RGBA if RGBA => { let mut p = [0; 4]; data.read_exact(&mut p)?; - decode!(rgb: p[0], p[1], p[2] => px); - if N == 4 { - px[3] = p[3]; - } + px.update_rgba(p[0], p[1], p[2], p[3]); } QOI_OP_RUN..=QOI_OP_RUN_END => { - *px_out = px; + *px_out = px.into(); let run = ((b1 & 0x3f) as usize).min(pixels.len()); let (phead, ptail) = pixels.split_at_mut(run); // can't panic - phead.fill(px); + phead.fill(px.into()); pixels = ptail; continue; } QOI_OP_DIFF..=QOI_OP_DIFF_END => { - decode!(diff: b1 => px); + px.update_diff(b1); } QOI_OP_LUMA..=QOI_OP_LUMA_END => { let mut p = [0]; data.read_exact(&mut p)?; let [b2] = p; - decode!(luma: b1, b2 => px); + px.update_luma(b1, b2); } _ => { cold(); } } - index[hash_pixel(px) as usize] = px; - *px_out = px; + index[px.hash_index() as usize] = px; + *px_out = px.into(); } let mut p = [0_u8; QOI_PADDING_SIZE]; @@ -298,7 +255,7 @@ where #[inline] fn qoi_decode_impl_stream_all( - data: &mut R, out: &mut [u8], channels: u8, src_channels: u8, n_pixels: usize, + data: &mut R, out: &mut [u8], channels: u8, src_channels: u8, ) -> Result<()> { match (channels, src_channels) { (3, 3) => qoi_decode_impl_stream::<_, 3, false>(data, out), @@ -354,18 +311,12 @@ impl QoiStreamDecoder { #[inline] pub fn decode_to_buf(&mut self, mut buf: impl AsMut<[u8]>) -> Result { - let mut buf = buf.as_mut(); + let buf = buf.as_mut(); let size = self.header.n_pixels() * self.channels as usize; if unlikely(buf.len() < size) { return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size }); } - qoi_decode_impl_stream_all( - &mut self.reader, - buf, - self.channels, - self.header.channels, - self.header.n_pixels(), - )?; + qoi_decode_impl_stream_all(&mut self.reader, buf, self.channels, self.header.channels)?; Ok(size) } diff --git a/src/pixel.rs b/src/pixel.rs index aa13476..aac3390 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -1,6 +1,6 @@ #[derive(Copy, Clone, PartialEq, Eq)] #[repr(transparent)] -pub struct Pixel([u8; N]); +pub struct Pixel(pub [u8; N]); impl Pixel { #[inline] @@ -17,6 +17,41 @@ impl Pixel { } } + #[inline] + pub fn update_rgb(&mut self, r: u8, g: u8, b: u8) { + self.0[0] = r; + self.0[1] = g; + self.0[2] = b; + } + + #[inline] + pub fn update_rgba(&mut self, r: u8, g: u8, b: u8, a: u8) { + self.0[0] = r; + self.0[1] = g; + self.0[2] = b; + if N >= 4 { + self.0[3] = a; + } + } + + #[inline] + pub fn update_diff(&mut self, b1: u8) { + self.0[0] = self.0[0].wrapping_add((b1 >> 4) & 0x03).wrapping_sub(2); + self.0[1] = self.0[1].wrapping_add((b1 >> 2) & 0x03).wrapping_sub(2); + self.0[2] = self.0[2].wrapping_add(b1 & 0x03).wrapping_sub(2); + } + + #[inline] + pub fn update_luma(&mut self, b1: u8, b2: u8) { + let vg = (b1 & 0x3f).wrapping_sub(32); + let vg_8 = vg.wrapping_sub(8); + let vr = vg_8.wrapping_add((b2 >> 4) & 0x0f); + let vb = vg_8.wrapping_add(b2 & 0x0f); + self.0[0] = self.0[0].wrapping_add(vr); + self.0[1] = self.0[1].wrapping_add(vg); + self.0[2] = self.0[2].wrapping_add(vb); + } + #[inline] pub const fn as_rgba(self, with_a: u8) -> Pixel<4> { let mut i = 0;