Introduce Channels and finish encoder refactor
This commit is contained in:
parent
0b76352b22
commit
b461af81ac
6 changed files with 205 additions and 102 deletions
|
@ -10,6 +10,7 @@ use crate::consts::{
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::header::Header;
|
use crate::header::Header;
|
||||||
use crate::pixel::{Pixel, SupportedChannels};
|
use crate::pixel::{Pixel, SupportedChannels};
|
||||||
|
use crate::types::Channels;
|
||||||
use crate::utils::{cold, unlikely};
|
use crate::utils::{cold, unlikely};
|
||||||
|
|
||||||
const QOI_OP_INDEX_END: u8 = QOI_OP_INDEX | 0x3f;
|
const QOI_OP_INDEX_END: u8 = QOI_OP_INDEX | 0x3f;
|
||||||
|
@ -126,7 +127,7 @@ pub fn qoi_decode_header(data: impl AsRef<[u8]>) -> Result<Header> {
|
||||||
pub struct QoiDecoder<'a> {
|
pub struct QoiDecoder<'a> {
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
header: Header,
|
header: Header,
|
||||||
channels: u8,
|
channels: Channels,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> QoiDecoder<'a> {
|
impl<'a> QoiDecoder<'a> {
|
||||||
|
@ -139,13 +140,13 @@ impl<'a> QoiDecoder<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn with_channels(mut self, channels: u8) -> Self {
|
pub const fn with_channels(mut self, channels: Channels) -> Self {
|
||||||
self.channels = channels;
|
self.channels = channels;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn channels(&self) -> u8 {
|
pub const fn channels(&self) -> Channels {
|
||||||
self.channels
|
self.channels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,24 +163,23 @@ impl<'a> QoiDecoder<'a> {
|
||||||
#[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 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_u8() 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 });
|
||||||
}
|
}
|
||||||
let n_read =
|
let n_read = qoi_decode_impl_slice_all(
|
||||||
qoi_decode_impl_slice_all(self.data, buf, self.channels, self.header.channels)?;
|
self.data,
|
||||||
|
buf,
|
||||||
|
self.channels.as_u8(),
|
||||||
|
self.header.channels.as_u8(),
|
||||||
|
)?;
|
||||||
self.data = &self.data[n_read..]; // can't panic
|
self.data = &self.data[n_read..]; // can't panic
|
||||||
Ok(size)
|
Ok(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn decode_to_vec(&mut self) -> Result<Vec<u8>> {
|
pub fn decode_to_vec(&mut self) -> Result<Vec<u8>> {
|
||||||
if unlikely(self.channels > 4) {
|
let mut out = vec![0; self.header.n_pixels() * self.channels.as_u8() as usize];
|
||||||
// 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)
|
self.decode_to_buf(&mut out).map(|_| out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,7 +272,7 @@ fn qoi_decode_impl_stream_all<R: Read>(
|
||||||
pub struct QoiStreamDecoder<R> {
|
pub struct QoiStreamDecoder<R> {
|
||||||
reader: R,
|
reader: R,
|
||||||
header: Header,
|
header: Header,
|
||||||
channels: u8,
|
channels: Channels,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read> QoiStreamDecoder<R> {
|
impl<R: Read> QoiStreamDecoder<R> {
|
||||||
|
@ -284,13 +284,13 @@ impl<R: Read> QoiStreamDecoder<R> {
|
||||||
Ok(Self { reader, header, channels: header.channels })
|
Ok(Self { reader, header, channels: header.channels })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_channels(mut self, channels: u8) -> Self {
|
pub fn with_channels(mut self, channels: Channels) -> Self {
|
||||||
self.channels = channels;
|
self.channels = channels;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn channels(&self) -> u8 {
|
pub fn channels(&self) -> Channels {
|
||||||
self.channels
|
self.channels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,22 +312,22 @@ 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 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_u8() 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(&mut self.reader, buf, self.channels, self.header.channels)?;
|
qoi_decode_impl_stream_all(
|
||||||
|
&mut self.reader,
|
||||||
|
buf,
|
||||||
|
self.channels.as_u8(),
|
||||||
|
self.header.channels.as_u8(),
|
||||||
|
)?;
|
||||||
Ok(size)
|
Ok(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn decode_to_vec(&mut self) -> Result<Vec<u8>> {
|
pub fn decode_to_vec(&mut self) -> Result<Vec<u8>> {
|
||||||
if unlikely(self.channels > 4) {
|
let mut out = vec![0; self.header.n_pixels() * self.channels.as_u8() as usize];
|
||||||
// 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];
|
|
||||||
let _ = self.decode_to_buf(&mut out)?;
|
let _ = self.decode_to_buf(&mut out)?;
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
145
src/encode.rs
145
src/encode.rs
|
@ -1,46 +1,25 @@
|
||||||
use std::convert::TryInto;
|
use crate::consts::{QOI_HEADER_SIZE, QOI_OP_INDEX, QOI_OP_RUN, QOI_PADDING, QOI_PADDING_SIZE};
|
||||||
|
|
||||||
use crate::colorspace::ColorSpace;
|
|
||||||
use crate::consts::{
|
|
||||||
QOI_HEADER_SIZE, QOI_OP_INDEX, QOI_OP_RUN, QOI_PADDING, QOI_PADDING_SIZE, QOI_PIXELS_MAX,
|
|
||||||
};
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::header::Header;
|
use crate::header::Header;
|
||||||
use crate::pixel::{Pixel, SupportedChannels};
|
use crate::pixel::{Pixel, SupportedChannels};
|
||||||
|
use crate::types::{Channels, ColorSpace};
|
||||||
use crate::utils::{unlikely, BytesMut};
|
use crate::utils::{unlikely, BytesMut};
|
||||||
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
fn qoi_encode_impl<const N: usize>(
|
fn qoi_encode_impl<const N: usize>(out: &mut [u8], data: &[u8]) -> Result<usize>
|
||||||
out: &mut [u8], data: &[u8], width: u32, height: u32, colorspace: ColorSpace,
|
|
||||||
) -> Result<usize>
|
|
||||||
where
|
where
|
||||||
Pixel<N>: SupportedChannels,
|
Pixel<N>: SupportedChannels,
|
||||||
{
|
{
|
||||||
let max_len = encode_size_required(width, height, N as u8);
|
|
||||||
if unlikely(out.len() < max_len) {
|
|
||||||
return Err(Error::OutputBufferTooSmall { size: out.len(), required: max_len });
|
|
||||||
}
|
|
||||||
|
|
||||||
let n_pixels = (width as usize) * (height as usize);
|
|
||||||
if unlikely(data.is_empty()) {
|
|
||||||
return Err(Error::EmptyImage { width, height });
|
|
||||||
} else if unlikely(n_pixels > QOI_PIXELS_MAX) {
|
|
||||||
return Err(Error::ImageTooLarge { width, height });
|
|
||||||
} else if unlikely(n_pixels * N != data.len()) {
|
|
||||||
return Err(Error::BadEncodingDataSize { size: data.len(), expected: n_pixels * N });
|
|
||||||
}
|
|
||||||
|
|
||||||
let out_size = out.len();
|
let out_size = out.len();
|
||||||
let mut buf = BytesMut::new(out);
|
let mut buf = BytesMut::new(out);
|
||||||
|
|
||||||
let header = Header { width, height, channels: N as u8, colorspace };
|
|
||||||
buf = buf.write_many(&header.encode());
|
|
||||||
|
|
||||||
let mut index = [Pixel::new(); 256];
|
let mut index = [Pixel::new(); 256];
|
||||||
let mut px_prev = Pixel::new().with_a(0xff);
|
let mut px_prev = Pixel::new().with_a(0xff);
|
||||||
let mut run = 0_u8;
|
let mut run = 0_u8;
|
||||||
let mut px = Pixel::<N>::new().with_a(0xff);
|
let mut px = Pixel::<N>::new().with_a(0xff);
|
||||||
|
|
||||||
|
let n_pixels = data.len() / N;
|
||||||
|
|
||||||
for (i, chunk) in data.chunks_exact(N).enumerate() {
|
for (i, chunk) in data.chunks_exact(N).enumerate() {
|
||||||
px.read(chunk);
|
px.read(chunk);
|
||||||
if px == px_prev {
|
if px == px_prev {
|
||||||
|
@ -72,44 +51,94 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn qoi_encode_to_buf<O, D, C>(
|
fn qoi_encode_impl_all(out: &mut [u8], data: &[u8], channels: Channels) -> Result<usize> {
|
||||||
mut out: O, data: D, width: u32, height: u32, channels: u8, colorspace: C,
|
|
||||||
) -> Result<usize>
|
|
||||||
where
|
|
||||||
O: AsMut<[u8]>,
|
|
||||||
D: AsRef<[u8]>,
|
|
||||||
C: TryInto<ColorSpace>,
|
|
||||||
Error: From<C::Error>,
|
|
||||||
{
|
|
||||||
let out = out.as_mut();
|
|
||||||
let data = data.as_ref();
|
|
||||||
let colorspace = colorspace.try_into()?;
|
|
||||||
match channels {
|
match channels {
|
||||||
3 => qoi_encode_impl::<3>(out, data, width, height, colorspace),
|
Channels::Rgb => qoi_encode_impl::<3>(out, data),
|
||||||
4 => qoi_encode_impl::<4>(out, data, width, height, colorspace),
|
Channels::Rgba => qoi_encode_impl::<4>(out, data),
|
||||||
_ => Err(Error::InvalidChannels { channels }),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn qoi_encode_to_vec<D, C>(
|
pub fn encoded_size_limit(width: u32, height: u32, channels: impl Into<u8>) -> usize {
|
||||||
data: D, width: u32, height: u32, channels: u8, colorspace: C,
|
let (width, height) = (width as usize, height as usize);
|
||||||
) -> Result<Vec<u8>>
|
let n_pixels = width.saturating_mul(height);
|
||||||
where
|
QOI_HEADER_SIZE
|
||||||
D: AsRef<[u8]>,
|
+ n_pixels.saturating_mul(channels.into() as usize)
|
||||||
C: TryInto<ColorSpace>,
|
+ n_pixels
|
||||||
Error: From<C::Error>,
|
+ QOI_PADDING_SIZE
|
||||||
{
|
}
|
||||||
let size = encode_size_required(width, height, channels);
|
|
||||||
let mut out = vec![0; size]; // note: we could save time here but that won't be safe anymore
|
#[inline]
|
||||||
let size = qoi_encode_to_buf(&mut out, data, width, height, channels, colorspace)?;
|
pub fn qoi_encode_to_buf(
|
||||||
|
buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>, width: u32, height: u32,
|
||||||
|
) -> Result<usize> {
|
||||||
|
QoiEncoder::new(&data, width, height)?.encode_to_buf(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn qoi_encode_to_vec(data: impl AsRef<[u8]>, width: u32, height: u32) -> Result<Vec<u8>> {
|
||||||
|
QoiEncoder::new(&data, width, height)?.encode_to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct QoiEncoder<'a> {
|
||||||
|
data: &'a [u8],
|
||||||
|
header: Header,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> QoiEncoder<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized), width: u32, height: u32) -> Result<Self> {
|
||||||
|
let data = data.as_ref();
|
||||||
|
let mut header =
|
||||||
|
Header::try_new(width, height, Channels::default(), ColorSpace::default())?;
|
||||||
|
let size = data.len();
|
||||||
|
let n_channels = size / header.n_pixels();
|
||||||
|
if header.n_pixels() * n_channels != size {
|
||||||
|
return Err(Error::InvalidImageLength { size, width, height });
|
||||||
|
}
|
||||||
|
header.channels = Channels::try_from(n_channels.min(0xff) as u8)?;
|
||||||
|
Ok(Self { data, header })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub const fn with_colorspace(mut self, colorspace: ColorSpace) -> Self {
|
||||||
|
self.header = self.header.with_colorspace(colorspace);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub const fn channels(&self) -> Channels {
|
||||||
|
self.header.channels
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub const fn header(&self) -> &Header {
|
||||||
|
&self.header
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn encoded_size_limit(&self) -> usize {
|
||||||
|
self.header.encoded_size_limit()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn encode_to_buf(&self, mut buf: impl AsMut<[u8]>) -> Result<usize> {
|
||||||
|
let buf = buf.as_mut();
|
||||||
|
let size_required = self.encoded_size_limit();
|
||||||
|
if unlikely(buf.len() < size_required) {
|
||||||
|
return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size_required });
|
||||||
|
}
|
||||||
|
let (head, tail) = buf.split_at_mut(QOI_HEADER_SIZE); // can't panic
|
||||||
|
head.copy_from_slice(&self.header.encode());
|
||||||
|
let n_written = qoi_encode_impl_all(tail, self.data, self.header.channels)?;
|
||||||
|
Ok(QOI_HEADER_SIZE + n_written)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn encode_to_vec(&self) -> Result<Vec<u8>> {
|
||||||
|
let mut out = vec![0_u8; self.encoded_size_limit()];
|
||||||
|
let size = self.encode_to_buf(&mut out)?;
|
||||||
out.truncate(size);
|
out.truncate(size);
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn encode_size_required(width: u32, height: u32, channels: u8) -> usize {
|
|
||||||
let (width, height) = (width as usize, height as usize);
|
|
||||||
let n_pixels = width.saturating_mul(height);
|
|
||||||
QOI_HEADER_SIZE + n_pixels.saturating_mul(usize::from(channels)) + n_pixels + QOI_PADDING_SIZE
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub enum Error {
|
||||||
InvalidChannels { channels: u8 },
|
InvalidChannels { channels: u8 },
|
||||||
EmptyImage { width: u32, height: u32 },
|
EmptyImage { width: u32, height: u32 },
|
||||||
ImageTooLarge { width: u32, height: u32 },
|
ImageTooLarge { width: u32, height: u32 },
|
||||||
BadEncodingDataSize { size: usize, expected: usize },
|
InvalidImageLength { size: usize, width: u32, height: u32 },
|
||||||
InputBufferTooSmall { size: usize, required: usize },
|
InputBufferTooSmall { size: usize, required: usize },
|
||||||
OutputBufferTooSmall { size: usize, required: usize },
|
OutputBufferTooSmall { size: usize, required: usize },
|
||||||
InvalidMagic { magic: u32 },
|
InvalidMagic { magic: u32 },
|
||||||
|
@ -36,8 +36,8 @@ impl Display for Error {
|
||||||
let mp = QOI_PIXELS_MAX / 1_000_000;
|
let mp = QOI_PIXELS_MAX / 1_000_000;
|
||||||
write!(f, "image is too large: {}x{} (max={}Mp)", width, height, mp)
|
write!(f, "image is too large: {}x{} (max={}Mp)", width, height, mp)
|
||||||
}
|
}
|
||||||
Self::BadEncodingDataSize { size, expected } => {
|
Self::InvalidImageLength { size, width, height } => {
|
||||||
write!(f, "bad data size when encoding: {} (expected: {})", size, expected)
|
write!(f, "invalid image length: {} for {}x{}", size, width, height)
|
||||||
}
|
}
|
||||||
Self::InputBufferTooSmall { size, required } => {
|
Self::InputBufferTooSmall { size, required } => {
|
||||||
write!(f, "input buffer size too small: {} (minimum required: {})", size, required)
|
write!(f, "input buffer size too small: {} (minimum required: {})", size, required)
|
||||||
|
|
|
@ -1,29 +1,50 @@
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use bytemuck::cast_slice;
|
use bytemuck::cast_slice;
|
||||||
|
|
||||||
use crate::colorspace::ColorSpace;
|
|
||||||
use crate::consts::{QOI_HEADER_SIZE, QOI_MAGIC, QOI_PIXELS_MAX};
|
use crate::consts::{QOI_HEADER_SIZE, QOI_MAGIC, QOI_PIXELS_MAX};
|
||||||
|
use crate::encoded_size_limit;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
use crate::types::{Channels, ColorSpace};
|
||||||
use crate::utils::unlikely;
|
use crate::utils::unlikely;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Header {
|
pub struct Header {
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
pub channels: u8,
|
pub channels: Channels,
|
||||||
pub colorspace: ColorSpace,
|
pub colorspace: ColorSpace,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Header {
|
impl Default for Header {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { width: 1, height: 1, channels: 3, colorspace: ColorSpace::default() }
|
Self {
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
channels: Channels::default(),
|
||||||
|
colorspace: ColorSpace::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Header {
|
impl Header {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn new(width: u32, height: u32, channels: u8) -> Self {
|
pub const fn try_new(
|
||||||
Self { width, height, channels, colorspace: ColorSpace::Srgb }
|
width: u32, height: u32, channels: Channels, colorspace: ColorSpace,
|
||||||
|
) -> Result<Self> {
|
||||||
|
if unlikely(height == 0 || width == 0) {
|
||||||
|
return Err(Error::EmptyImage { width, height });
|
||||||
|
} else if unlikely((width as usize).saturating_mul(height as usize) > QOI_PIXELS_MAX) {
|
||||||
|
return Err(Error::ImageTooLarge { width, height });
|
||||||
|
}
|
||||||
|
Ok(Self { width, height, channels, colorspace })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub const fn with_channels(mut self, channels: Channels) -> Self {
|
||||||
|
self.channels = channels;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -43,7 +64,7 @@ impl Header {
|
||||||
out[..4].copy_from_slice(&QOI_MAGIC.to_be_bytes());
|
out[..4].copy_from_slice(&QOI_MAGIC.to_be_bytes());
|
||||||
out[4..8].copy_from_slice(&self.width.to_be_bytes());
|
out[4..8].copy_from_slice(&self.width.to_be_bytes());
|
||||||
out[8..12].copy_from_slice(&self.height.to_be_bytes());
|
out[8..12].copy_from_slice(&self.height.to_be_bytes());
|
||||||
out[12] = self.channels;
|
out[12] = self.channels.into();
|
||||||
out[13] = self.colorspace.into();
|
out[13] = self.colorspace.into();
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
@ -58,22 +79,26 @@ impl Header {
|
||||||
let magic = u32::from_be_bytes(v[0]);
|
let magic = u32::from_be_bytes(v[0]);
|
||||||
let width = u32::from_be_bytes(v[1]);
|
let width = u32::from_be_bytes(v[1]);
|
||||||
let height = u32::from_be_bytes(v[2]);
|
let height = u32::from_be_bytes(v[2]);
|
||||||
let channels = data[12];
|
let channels = data[12].try_into()?;
|
||||||
let colorspace = ColorSpace::try_from(data[13])?;
|
let colorspace = data[13].try_into()?;
|
||||||
if unlikely(magic != QOI_MAGIC) {
|
if unlikely(magic != QOI_MAGIC) {
|
||||||
return Err(Error::InvalidMagic { magic });
|
return Err(Error::InvalidMagic { magic });
|
||||||
} else if unlikely(height == 0 || width == 0) {
|
|
||||||
return Err(Error::EmptyImage { width, height });
|
|
||||||
} else if unlikely((width as usize) * (height as usize) > QOI_PIXELS_MAX) {
|
|
||||||
return Err(Error::ImageTooLarge { width, height });
|
|
||||||
} else if unlikely(channels != 3 && channels != 4) {
|
|
||||||
return Err(Error::InvalidChannels { channels });
|
|
||||||
}
|
}
|
||||||
Ok(Self { width, height, channels, colorspace })
|
Self::try_new(width, height, channels, colorspace)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn n_pixels(&self) -> usize {
|
pub const fn n_pixels(&self) -> usize {
|
||||||
(self.width as usize).saturating_mul(self.height as usize)
|
(self.width as usize).saturating_mul(self.height as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub const fn n_bytes(&self) -> usize {
|
||||||
|
self.n_pixels() * self.channels.as_u8() as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn encoded_size_limit(&self) -> usize {
|
||||||
|
encoded_size_limit(self.width, self.height, self.channels)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,21 +9,21 @@
|
||||||
clippy::cargo_common_metadata
|
clippy::cargo_common_metadata
|
||||||
)]
|
)]
|
||||||
|
|
||||||
mod colorspace;
|
|
||||||
mod decode;
|
mod decode;
|
||||||
mod encode;
|
mod encode;
|
||||||
mod error;
|
mod error;
|
||||||
mod header;
|
mod header;
|
||||||
mod pixel;
|
mod pixel;
|
||||||
|
mod types;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod consts;
|
pub mod consts;
|
||||||
|
|
||||||
pub use crate::colorspace::ColorSpace;
|
|
||||||
pub use crate::decode::{
|
pub use crate::decode::{
|
||||||
qoi_decode_header, qoi_decode_to_buf, qoi_decode_to_vec, QoiDecoder, QoiStreamDecoder,
|
qoi_decode_header, qoi_decode_to_buf, qoi_decode_to_vec, QoiDecoder, QoiStreamDecoder,
|
||||||
};
|
};
|
||||||
pub use crate::encode::{encode_size_required, qoi_encode_to_buf, qoi_encode_to_vec};
|
pub use crate::encode::{encoded_size_limit, qoi_encode_to_buf, qoi_encode_to_vec, QoiEncoder};
|
||||||
pub use crate::error::{Error, Result};
|
pub use crate::error::{Error, Result};
|
||||||
pub use crate::header::Header;
|
pub use crate::header::Header;
|
||||||
|
pub use crate::types::{Channels, ColorSpace};
|
||||||
|
|
|
@ -51,3 +51,52 @@ impl TryFrom<u8> for ColorSpace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Channels {
|
||||||
|
/// Three 8-bit channels (RGB)
|
||||||
|
Rgb = 3,
|
||||||
|
/// Four 8-bit channels (RGBA)
|
||||||
|
Rgba = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Channels {
|
||||||
|
pub const fn is_rgb(self) -> bool {
|
||||||
|
matches!(self, Self::Rgb)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn is_rgba(self) -> bool {
|
||||||
|
matches!(self, Self::Rgba)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn as_u8(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Channels {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Rgb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Channels> for u8 {
|
||||||
|
#[inline]
|
||||||
|
fn from(channels: Channels) -> Self {
|
||||||
|
channels as Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for Channels {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_from(channels: u8) -> Result<Self> {
|
||||||
|
if unlikely(channels != 3 && channels != 4) {
|
||||||
|
Err(Error::InvalidChannels { channels })
|
||||||
|
} else {
|
||||||
|
Ok(if channels == 3 { Self::Rgb } else { Self::Rgba })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue