Decoder - cleanup, move some logic back into Pixel

This commit is contained in:
Ivan Smirnov 2022-01-02 19:14:12 +03:00
parent 6c7dad1e2c
commit 268953033b
2 changed files with 61 additions and 75 deletions

View file

@ -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_DIFF_END: u8 = QOI_OP_DIFF | 0x3f;
const QOI_OP_LUMA_END: u8 = QOI_OP_LUMA | 0x3f; const QOI_OP_LUMA_END: u8 = QOI_OP_LUMA | 0x3f;
#[inline(always)]
pub const fn hash_pixel<const N: usize>(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] #[inline]
fn qoi_decode_impl_slice<const N: usize, const RGBA: bool>( fn qoi_decode_impl_slice<const N: usize, const RGBA: bool>(
data: &[u8], out: &mut [u8], data: &[u8], out: &mut [u8],
@ -60,47 +29,41 @@ where
let data_len = data.len(); let data_len = data.len();
let mut data = data; let mut data = data;
let mut index = [[0_u8; N]; 256]; let mut index = [Pixel::<N>::new(); 256];
let mut px = [0_u8; N]; let mut px = Pixel::<N>::new().with_a(0xff);
if N == 4 {
px[3] = 0xff;
}
while let [px_out, ptail @ ..] = pixels { while let [px_out, ptail @ ..] = pixels {
pixels = ptail; pixels = ptail;
match data { match data {
[b1 @ QOI_OP_INDEX..=QOI_OP_INDEX_END, dtail @ ..] => { [b1 @ QOI_OP_INDEX..=QOI_OP_INDEX_END, dtail @ ..] => {
px = index[*b1 as usize]; px = index[*b1 as usize];
*px_out = px; *px_out = px.into();
data = dtail; data = dtail;
continue; continue;
} }
[QOI_OP_RGB, r, g, b, dtail @ ..] => { [QOI_OP_RGB, r, g, b, dtail @ ..] => {
decode!(rgb: *r, *g, *b => px); px.update_rgb(*r, *g, *b);
data = dtail; data = dtail;
} }
[QOI_OP_RGBA, r, g, b, a, dtail @ ..] if RGBA => { [QOI_OP_RGBA, r, g, b, a, dtail @ ..] if RGBA => {
decode!(rgb: *r, *g, *b => px); px.update_rgba(*r, *g, *b, *a);
if N == 4 {
px[3] = *a;
}
data = dtail; data = dtail;
} }
[b1 @ QOI_OP_RUN..=QOI_OP_RUN_END, 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 run = ((b1 & 0x3f) as usize).min(pixels.len());
let (phead, ptail) = pixels.split_at_mut(run); // can't panic let (phead, ptail) = pixels.split_at_mut(run); // can't panic
phead.fill(px); phead.fill(px.into());
pixels = ptail; pixels = ptail;
data = dtail; data = dtail;
continue; continue;
} }
[b1 @ QOI_OP_DIFF..=QOI_OP_DIFF_END, dtail @ ..] => { [b1 @ QOI_OP_DIFF..=QOI_OP_DIFF_END, dtail @ ..] => {
decode!(diff: b1 => px); px.update_diff(*b1);
data = dtail; data = dtail;
} }
[b1 @ QOI_OP_LUMA..=QOI_OP_LUMA_END, b2, dtail @ ..] => { [b1 @ QOI_OP_LUMA..=QOI_OP_LUMA_END, b2, dtail @ ..] => {
decode!(luma: b1, b2 => px); px.update_luma(*b1, *b2);
data = dtail; data = dtail;
} }
_ => { _ => {
@ -111,8 +74,8 @@ where
} }
} }
index[hash_pixel(px) as usize] = px; index[px.hash_index() as usize] = px;
*px_out = px; *px_out = px.into();
} }
if unlikely(data.len() < QOI_PADDING_SIZE) { if unlikely(data.len() < QOI_PADDING_SIZE) {
@ -231,11 +194,8 @@ where
{ {
let mut pixels = cast_slice_mut::<_, [u8; N]>(out); let mut pixels = cast_slice_mut::<_, [u8; N]>(out);
let mut index = [[0_u8; N]; 256]; let mut index = [Pixel::<N>::new(); 256];
let mut px = [0_u8; N]; let mut px = Pixel::<N>::new().with_a(0xff);
if N == 4 {
px[3] = 0xff;
}
while let [px_out, ptail @ ..] = pixels { while let [px_out, ptail @ ..] = pixels {
pixels = ptail; pixels = ptail;
@ -245,46 +205,43 @@ where
match b1 { match b1 {
QOI_OP_INDEX..=QOI_OP_INDEX_END => { QOI_OP_INDEX..=QOI_OP_INDEX_END => {
px = index[b1 as usize]; px = index[b1 as usize];
*px_out = px; *px_out = px.into();
continue; continue;
} }
QOI_OP_RGB => { QOI_OP_RGB => {
let mut p = [0; 3]; let mut p = [0; 3];
data.read_exact(&mut p)?; 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 => { QOI_OP_RGBA if RGBA => {
let mut p = [0; 4]; let mut p = [0; 4];
data.read_exact(&mut p)?; data.read_exact(&mut p)?;
decode!(rgb: p[0], p[1], p[2] => px); px.update_rgba(p[0], p[1], p[2], p[3]);
if N == 4 {
px[3] = p[3];
}
} }
QOI_OP_RUN..=QOI_OP_RUN_END => { QOI_OP_RUN..=QOI_OP_RUN_END => {
*px_out = px; *px_out = px.into();
let run = ((b1 & 0x3f) as usize).min(pixels.len()); let run = ((b1 & 0x3f) as usize).min(pixels.len());
let (phead, ptail) = pixels.split_at_mut(run); // can't panic let (phead, ptail) = pixels.split_at_mut(run); // can't panic
phead.fill(px); phead.fill(px.into());
pixels = ptail; pixels = ptail;
continue; continue;
} }
QOI_OP_DIFF..=QOI_OP_DIFF_END => { QOI_OP_DIFF..=QOI_OP_DIFF_END => {
decode!(diff: b1 => px); px.update_diff(b1);
} }
QOI_OP_LUMA..=QOI_OP_LUMA_END => { QOI_OP_LUMA..=QOI_OP_LUMA_END => {
let mut p = [0]; let mut p = [0];
data.read_exact(&mut p)?; data.read_exact(&mut p)?;
let [b2] = p; let [b2] = p;
decode!(luma: b1, b2 => px); px.update_luma(b1, b2);
} }
_ => { _ => {
cold(); cold();
} }
} }
index[hash_pixel(px) as usize] = px; index[px.hash_index() as usize] = px;
*px_out = px; *px_out = px.into();
} }
let mut p = [0_u8; QOI_PADDING_SIZE]; let mut p = [0_u8; QOI_PADDING_SIZE];
@ -298,7 +255,7 @@ where
#[inline] #[inline]
fn qoi_decode_impl_stream_all<R: Read>( fn qoi_decode_impl_stream_all<R: Read>(
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<()> { ) -> Result<()> {
match (channels, src_channels) { match (channels, src_channels) {
(3, 3) => qoi_decode_impl_stream::<_, 3, false>(data, out), (3, 3) => qoi_decode_impl_stream::<_, 3, false>(data, out),
@ -354,18 +311,12 @@ impl<R: Read> QoiStreamDecoder<R> {
#[inline] #[inline]
pub fn decode_to_buf(&mut self, mut buf: impl AsMut<[u8]>) -> Result<usize> { pub fn decode_to_buf(&mut self, mut buf: impl AsMut<[u8]>) -> Result<usize> {
let mut buf = buf.as_mut(); let buf = buf.as_mut();
let size = self.header.n_pixels() * self.channels as usize; let size = self.header.n_pixels() * self.channels as usize;
if unlikely(buf.len() < size) { if unlikely(buf.len() < size) {
return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size }); return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size });
} }
qoi_decode_impl_stream_all( qoi_decode_impl_stream_all(&mut self.reader, buf, self.channels, self.header.channels)?;
&mut self.reader,
buf,
self.channels,
self.header.channels,
self.header.n_pixels(),
)?;
Ok(size) Ok(size)
} }

View file

@ -1,6 +1,6 @@
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
#[repr(transparent)] #[repr(transparent)]
pub struct Pixel<const N: usize>([u8; N]); pub struct Pixel<const N: usize>(pub [u8; N]);
impl<const N: usize> Pixel<N> { impl<const N: usize> Pixel<N> {
#[inline] #[inline]
@ -17,6 +17,41 @@ impl<const N: usize> Pixel<N> {
} }
} }
#[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] #[inline]
pub const fn as_rgba(self, with_a: u8) -> Pixel<4> { pub const fn as_rgba(self, with_a: u8) -> Pixel<4> {
let mut i = 0; let mut i = 0;