Add QoiDecoder, handle padding properly + refactor
This commit is contained in:
parent
acdd29060c
commit
0eb8a7ade7
4 changed files with 87 additions and 45 deletions
|
@ -126,8 +126,8 @@ impl Codec for CodecQoiFast {
|
||||||
Ok(qoi_fast::qoi_encode_to_vec(&img.data, img.width, img.height, img.channels, 0)?)
|
Ok(qoi_fast::qoi_encode_to_vec(&img.data, img.width, img.height, img.channels, 0)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode(data: &[u8], img: &Image) -> Result<Vec<u8>> {
|
fn decode(data: &[u8], _img: &Image) -> Result<Vec<u8>> {
|
||||||
Ok(qoi_fast::qoi_decode_to_vec(data, img.channels)?.1)
|
Ok(qoi_fast::qoi_decode_to_vec(data)?.1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
122
src/decode.rs
122
src/decode.rs
|
@ -1,9 +1,9 @@
|
||||||
// TODO: can be removed once https://github.com/rust-lang/rust/issues/74985 is stable
|
// TODO: can be removed once https://github.com/rust-lang/rust/issues/74985 is stable
|
||||||
use bytemuck::{cast_slice_mut, Pod};
|
use bytemuck::{cast_slice, cast_slice_mut, Pod};
|
||||||
|
|
||||||
use crate::consts::{
|
use crate::consts::{
|
||||||
QOI_HEADER_SIZE, QOI_OP_DIFF, QOI_OP_INDEX, QOI_OP_LUMA, QOI_OP_RGB, QOI_OP_RGBA, QOI_OP_RUN,
|
QOI_HEADER_SIZE, QOI_OP_DIFF, QOI_OP_INDEX, QOI_OP_LUMA, QOI_OP_RGB, QOI_OP_RGBA, QOI_OP_RUN,
|
||||||
QOI_PADDING_SIZE,
|
QOI_PADDING, QOI_PADDING_SIZE,
|
||||||
};
|
};
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::header::Header;
|
use crate::header::Header;
|
||||||
|
@ -49,12 +49,13 @@ macro_rules! decode {
|
||||||
#[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],
|
||||||
) -> Result<()>
|
) -> Result<usize>
|
||||||
where
|
where
|
||||||
Pixel<N>: SupportedChannels,
|
Pixel<N>: SupportedChannels,
|
||||||
[u8; N]: Pod,
|
[u8; N]: Pod,
|
||||||
{
|
{
|
||||||
let mut pixels = cast_slice_mut::<_, [u8; N]>(out);
|
let mut pixels = cast_slice_mut::<_, [u8; N]>(out);
|
||||||
|
let data_len = data.len();
|
||||||
let mut data = data;
|
let mut data = data;
|
||||||
|
|
||||||
let mut index = [[0_u8; N]; 256];
|
let mut index = [[0_u8; N]; 256];
|
||||||
|
@ -112,31 +113,19 @@ where
|
||||||
*px_out = px;
|
*px_out = px;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
if unlikely(data.len() < QOI_PADDING_SIZE) {
|
||||||
}
|
return Err(Error::UnexpectedBufferEnd);
|
||||||
|
} else if unlikely(cast_slice::<_, [u8; QOI_PADDING_SIZE]>(data)[0] != QOI_PADDING) {
|
||||||
pub trait MaybeChannels {
|
return Err(Error::InvalidPadding);
|
||||||
fn maybe_channels(self) -> Option<u8>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MaybeChannels for u8 {
|
|
||||||
#[inline]
|
|
||||||
fn maybe_channels(self) -> Option<u8> {
|
|
||||||
Some(self)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl MaybeChannels for Option<u8> {
|
Ok(data_len.saturating_sub(data.len()).saturating_sub(QOI_PADDING_SIZE))
|
||||||
#[inline]
|
|
||||||
fn maybe_channels(self) -> Option<u8> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn qoi_decode_impl_slice_all(
|
fn qoi_decode_impl_slice_all(
|
||||||
data: &[u8], out: &mut [u8], channels: u8, src_channels: u8,
|
data: &[u8], out: &mut [u8], channels: u8, src_channels: u8,
|
||||||
) -> Result<()> {
|
) -> Result<usize> {
|
||||||
match (channels, src_channels) {
|
match (channels, src_channels) {
|
||||||
(3, 3) => qoi_decode_impl_slice::<3, false>(data, out),
|
(3, 3) => qoi_decode_impl_slice::<3, false>(data, out),
|
||||||
(3, 4) => qoi_decode_impl_slice::<3, true>(data, out),
|
(3, 4) => qoi_decode_impl_slice::<3, true>(data, out),
|
||||||
|
@ -150,33 +139,82 @@ fn qoi_decode_impl_slice_all(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn qoi_decode_to_buf(
|
pub fn qoi_decode_to_buf(buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>) -> Result<Header> {
|
||||||
mut out: impl AsMut<[u8]>, data: impl AsRef<[u8]>, channels: impl MaybeChannels,
|
let mut decoder = QoiDecoder::new(&data)?;
|
||||||
) -> Result<Header> {
|
decoder.decode_to_buf(buf)?;
|
||||||
let (out, data) = (out.as_mut(), data.as_ref());
|
Ok(*decoder.header())
|
||||||
let header = Header::decode(data)?;
|
|
||||||
let channels = channels.maybe_channels().unwrap_or(header.channels);
|
|
||||||
let size = header.n_pixels() * channels as usize;
|
|
||||||
if unlikely(out.len() < size) {
|
|
||||||
return Err(Error::OutputBufferTooSmall { size: out.len(), required: size });
|
|
||||||
}
|
|
||||||
let data = &data[QOI_HEADER_SIZE..]; // can't panic
|
|
||||||
qoi_decode_impl_slice_all(data, out, header.channels, channels).map(|_| header)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn qoi_decode_to_vec(
|
pub fn qoi_decode_to_vec(data: impl AsRef<[u8]>) -> Result<(Header, Vec<u8>)> {
|
||||||
data: impl AsRef<[u8]>, channels: impl MaybeChannels,
|
let mut decoder = QoiDecoder::new(&data)?;
|
||||||
) -> Result<(Header, Vec<u8>)> {
|
let out = decoder.decode_to_vec()?;
|
||||||
let data = data.as_ref();
|
Ok((*decoder.header(), out))
|
||||||
let header = Header::decode(data)?;
|
|
||||||
let channels = channels.maybe_channels().unwrap_or(header.channels);
|
|
||||||
let mut out = vec![0; header.n_pixels() * channels as usize];
|
|
||||||
let data = &data[QOI_HEADER_SIZE..]; // can't panic
|
|
||||||
qoi_decode_impl_slice_all(data, &mut out, header.channels, channels).map(|_| (header, out))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn qoi_decode_header(data: impl AsRef<[u8]>) -> Result<Header> {
|
pub fn qoi_decode_header(data: impl AsRef<[u8]>) -> Result<Header> {
|
||||||
Header::decode(data)
|
Header::decode(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct QoiDecoder<'a> {
|
||||||
|
data: &'a [u8],
|
||||||
|
header: Header,
|
||||||
|
channels: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> QoiDecoder<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized)) -> Result<Self> {
|
||||||
|
let data = data.as_ref();
|
||||||
|
let header = Header::decode(data)?;
|
||||||
|
let data = &data[QOI_HEADER_SIZE..]; // can't panic
|
||||||
|
Ok(Self { data, header, channels: header.channels })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn with_channels(mut self, channels: u8) -> Self {
|
||||||
|
self.channels = channels;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn channels(&self) -> u8 {
|
||||||
|
self.channels
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn header(&self) -> &Header {
|
||||||
|
&self.header
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn data(self) -> &'a [u8] {
|
||||||
|
self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn decode_to_buf(&mut self, mut buf: impl AsMut<[u8]>) -> Result<()> {
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
let n_read =
|
||||||
|
qoi_decode_impl_slice_all(self.data, buf, self.channels, self.header.channels)?;
|
||||||
|
self.data = &self.data[n_read..]; // can't panic
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn decode_to_vec(&mut self) -> Result<Vec<u8>> {
|
||||||
|
if unlikely(self.channels > 4) {
|
||||||
|
// prevent accidental over-allocations
|
||||||
|
cold();
|
||||||
|
return Err(Error::InvalidChannels { channels: self.channels });
|
||||||
|
}
|
||||||
|
let mut out = vec![0; self.header.n_pixels() * self.channels as usize];
|
||||||
|
self.decode_to_buf(&mut out).map(|_| out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ pub enum Error {
|
||||||
InvalidMagic { magic: u32 },
|
InvalidMagic { magic: u32 },
|
||||||
UnexpectedBufferEnd,
|
UnexpectedBufferEnd,
|
||||||
InvalidColorSpace { colorspace: u8 },
|
InvalidColorSpace { colorspace: u8 },
|
||||||
|
InvalidPadding,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = StdResult<T, Error>;
|
pub type Result<T> = StdResult<T, Error>;
|
||||||
|
@ -51,6 +52,9 @@ impl Display for Error {
|
||||||
Self::InvalidColorSpace { colorspace } => {
|
Self::InvalidColorSpace { colorspace } => {
|
||||||
write!(f, "invalid color space: {} (expected 0 or 1)", colorspace)
|
write!(f, "invalid color space: {} (expected 0 or 1)", colorspace)
|
||||||
}
|
}
|
||||||
|
Self::InvalidPadding => {
|
||||||
|
write!(f, "invalid padding (stream end marker)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@ fn test_reference_images() -> Result<()> {
|
||||||
let encoded = qoi_encode_to_vec(&img.data, img.width, img.height, img.channels, 0)?;
|
let encoded = qoi_encode_to_vec(&img.data, img.width, img.height, img.channels, 0)?;
|
||||||
let expected = fs::read(qoi_path)?;
|
let expected = fs::read(qoi_path)?;
|
||||||
compare_slices(&png_name, "encoding", &encoded, &expected)?;
|
compare_slices(&png_name, "encoding", &encoded, &expected)?;
|
||||||
let (_header, decoded) = qoi_decode_to_vec(&expected, img.channels)?;
|
let (_header, decoded) = qoi_decode_to_vec(&expected)?;
|
||||||
compare_slices(&png_name, "decoding", &decoded, &img.data)?;
|
compare_slices(&png_name, "decoding", &decoded, &img.data)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue