Rework ColorSpace to match the latest spec (0 | 1)
This commit is contained in:
parent
c39844fb98
commit
2dcbdd19c2
5 changed files with 81 additions and 95 deletions
|
@ -1,70 +1,53 @@
|
|||
use std::fmt::{self, Debug};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ColorSpace {
|
||||
pub r_linear: bool,
|
||||
pub g_linear: bool,
|
||||
pub b_linear: bool,
|
||||
pub a_linear: bool,
|
||||
use crate::error::{Error, Result};
|
||||
use crate::utils::unlikely;
|
||||
|
||||
/// Image color space.
|
||||
///
|
||||
/// Note: the color space is purely informative. Although it is saved to the
|
||||
/// file header, it does not affect encoding/decoding in any way.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum ColorSpace {
|
||||
/// sRGB with linear alpha
|
||||
Srgb = 0,
|
||||
/// All channels are linear
|
||||
Linear = 1,
|
||||
}
|
||||
|
||||
impl ColorSpace {
|
||||
pub const SRGB: Self = Self::new(false, false, false, false);
|
||||
pub const LINEAR: Self = Self::new(true, true, true, true);
|
||||
pub const SRGB_LINEAR_ALPHA: Self = Self::new(false, false, false, true);
|
||||
|
||||
pub const fn new(r_linear: bool, g_linear: bool, b_linear: bool, a_linear: bool) -> Self {
|
||||
Self { r_linear, g_linear, b_linear, a_linear }
|
||||
}
|
||||
|
||||
pub const fn is_srgb(self) -> bool {
|
||||
!self.r_linear && !self.g_linear && !self.b_linear && !self.a_linear
|
||||
matches!(self, Self::Srgb)
|
||||
}
|
||||
|
||||
pub const fn is_linear(self) -> bool {
|
||||
self.r_linear && self.g_linear && self.b_linear && self.a_linear
|
||||
}
|
||||
|
||||
pub const fn is_srgb_linear_alpha(self) -> bool {
|
||||
!self.r_linear && !self.g_linear && !self.b_linear && self.a_linear
|
||||
}
|
||||
|
||||
pub const fn to_u8(self) -> u8 {
|
||||
(self.r_linear as u8) << 3
|
||||
| (self.g_linear as u8) << 2
|
||||
| (self.b_linear as u8) << 1
|
||||
| (self.a_linear as u8)
|
||||
}
|
||||
|
||||
pub const fn from_u8(bits: u8) -> Self {
|
||||
Self::new(bits & 0x08 != 0, bits & 0x04 != 0, bits & 0x02 != 0, bits & 0x01 != 0)
|
||||
matches!(self, Self::Linear)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ColorSpace {
|
||||
fn default() -> Self {
|
||||
Self::SRGB
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for ColorSpace {
|
||||
fn from(value: u8) -> Self {
|
||||
Self::from_u8(value)
|
||||
Self::Srgb
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ColorSpace> for u8 {
|
||||
fn from(value: ColorSpace) -> Self {
|
||||
value.to_u8()
|
||||
#[inline]
|
||||
fn from(colorspace: ColorSpace) -> Self {
|
||||
colorspace as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ColorSpace {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"ColorSpace({}{}{}{})",
|
||||
self.r_linear as u8, self.g_linear as u8, self.b_linear as u8, self.a_linear as u8
|
||||
)
|
||||
impl TryFrom<u8> for ColorSpace {
|
||||
type Error = Error;
|
||||
|
||||
#[inline]
|
||||
fn try_from(colorspace: u8) -> Result<Self> {
|
||||
if unlikely(colorspace | 1 != 1) {
|
||||
Err(Error::InvalidColorSpace { colorspace })
|
||||
} else {
|
||||
Ok(if colorspace == 0 { Self::Srgb } else { Self::Linear })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,8 +123,7 @@ pub fn qoi_decode_to_vec(
|
|||
data: impl AsRef<[u8]>, channels: impl MaybeChannels,
|
||||
) -> Result<(Header, Vec<u8>)> {
|
||||
let data = data.as_ref();
|
||||
let header = qoi_decode_header(data)?;
|
||||
header.validate()?;
|
||||
let header = Header::decode(data)?;
|
||||
let channels = channels.maybe_channels().unwrap_or(header.channels);
|
||||
match (channels, header.channels) {
|
||||
(3, 3) => Ok((header, qoi_decode_impl::<3, false>(data, header.n_pixels())?)),
|
||||
|
@ -137,11 +136,5 @@ pub fn qoi_decode_to_vec(
|
|||
|
||||
#[inline]
|
||||
pub fn qoi_decode_header(data: impl AsRef<[u8]>) -> Result<Header> {
|
||||
let data = data.as_ref();
|
||||
if unlikely(data.len() < QOI_HEADER_SIZE) {
|
||||
return Err(Error::InputBufferTooSmall { size: data.len(), required: QOI_HEADER_SIZE });
|
||||
}
|
||||
let mut bytes = [0_u8; QOI_HEADER_SIZE];
|
||||
bytes.copy_from_slice(&data[..QOI_HEADER_SIZE]);
|
||||
Ok(Header::from_bytes(bytes))
|
||||
Header::decode(data)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::convert::TryInto;
|
||||
|
||||
use crate::colorspace::ColorSpace;
|
||||
use crate::consts::{
|
||||
QOI_HEADER_SIZE, QOI_OP_DIFF, QOI_OP_INDEX, QOI_OP_LUMA, QOI_OP_RGB, QOI_OP_RGBA, QOI_OP_RUN,
|
||||
|
@ -66,7 +68,7 @@ where
|
|||
|
||||
let header =
|
||||
Header { width, height, channels: CHANNELS as u8, colorspace, ..Header::default() };
|
||||
buf = buf.write(header.to_bytes());
|
||||
buf = buf.write(header.encode());
|
||||
|
||||
let mut index = [Pixel::new(); 256];
|
||||
let mut px_prev = Pixel::new().with_a(0xff);
|
||||
|
@ -129,13 +131,18 @@ where
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn qoi_encode_to_buf(
|
||||
mut out: impl AsMut<[u8]>, data: impl AsRef<[u8]>, width: u32, height: u32, channels: u8,
|
||||
colorspace: impl Into<ColorSpace>,
|
||||
) -> Result<usize> {
|
||||
pub fn qoi_encode_to_buf<O, D, C>(
|
||||
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.into();
|
||||
let colorspace = colorspace.try_into()?;
|
||||
match channels {
|
||||
3 => qoi_encode_impl::<3>(out, data, width, height, colorspace),
|
||||
4 => qoi_encode_impl::<4>(out, data, width, height, colorspace),
|
||||
|
@ -144,12 +151,14 @@ pub fn qoi_encode_to_buf(
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn qoi_encode_to_vec(
|
||||
data: impl AsRef<[u8]>, width: u32, height: u32, channels: u8,
|
||||
colorspace: impl Into<ColorSpace>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let data = data.as_ref();
|
||||
let colorspace = colorspace.into();
|
||||
pub fn qoi_encode_to_vec<D, C>(
|
||||
data: D, width: u32, height: u32, channels: u8, colorspace: C,
|
||||
) -> Result<Vec<u8>>
|
||||
where
|
||||
D: AsRef<[u8]>,
|
||||
C: TryInto<ColorSpace>,
|
||||
Error: From<C::Error>,
|
||||
{
|
||||
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
|
||||
let size = qoi_encode_to_buf(&mut out, data, width, height, channels, colorspace)?;
|
||||
|
|
|
@ -15,7 +15,7 @@ pub enum Error {
|
|||
OutputBufferTooSmall { size: usize, required: usize },
|
||||
InvalidMagic { magic: u32 },
|
||||
UnexpectedBufferEnd,
|
||||
// TODO: invalid colorspace
|
||||
InvalidColorSpace { colorspace: u8 },
|
||||
}
|
||||
|
||||
pub type Result<T> = StdResult<T, Error>;
|
||||
|
@ -48,6 +48,9 @@ impl Display for Error {
|
|||
Self::UnexpectedBufferEnd => {
|
||||
write!(f, "unexpected input buffer end while decoding")
|
||||
}
|
||||
Self::InvalidColorSpace { colorspace } => {
|
||||
write!(f, "invalid color space: {} (expected 0 or 1)", colorspace)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,12 +43,12 @@ const fn u32_from_be(v: &[u8]) -> u32 {
|
|||
impl Header {
|
||||
#[inline]
|
||||
pub const fn new(width: u32, height: u32, channels: u8) -> Self {
|
||||
Self { magic: QOI_MAGIC, width, height, channels, colorspace: ColorSpace::from_u8(0) }
|
||||
Self { magic: QOI_MAGIC, width, height, channels, colorspace: ColorSpace::Srgb }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_colorspace(mut self, colorspace: impl Into<ColorSpace>) -> Self {
|
||||
self.colorspace = colorspace.into();
|
||||
pub fn with_colorspace(mut self, colorspace: ColorSpace) -> Self {
|
||||
self.colorspace = colorspace;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ impl Header {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn to_bytes(&self) -> [u8; QOI_HEADER_SIZE] {
|
||||
pub(crate) fn encode(&self) -> [u8; QOI_HEADER_SIZE] {
|
||||
let mut out = [0; QOI_HEADER_SIZE];
|
||||
out[..4].copy_from_slice(&u32_to_be(self.magic));
|
||||
out[4..8].copy_from_slice(&u32_to_be(self.width));
|
||||
|
@ -69,32 +69,30 @@ impl Header {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn from_bytes(v: [u8; QOI_HEADER_SIZE]) -> Self {
|
||||
Self {
|
||||
magic: u32_from_be(&v[..4]),
|
||||
width: u32_from_be(&v[4..8]),
|
||||
height: u32_from_be(&v[8..12]),
|
||||
channels: v[12],
|
||||
colorspace: v[13].into(),
|
||||
pub(crate) fn decode(data: impl AsRef<[u8]>) -> Result<Self> {
|
||||
let data = data.as_ref();
|
||||
if unlikely(data.len() < QOI_HEADER_SIZE) {
|
||||
return Err(Error::InputBufferTooSmall { size: data.len(), required: QOI_HEADER_SIZE });
|
||||
}
|
||||
let magic = u32_from_be(&data[..4]);
|
||||
let width = u32_from_be(&data[4..8]);
|
||||
let height = u32_from_be(&data[8..12]);
|
||||
let channels = data[12];
|
||||
let colorspace = ColorSpace::try_from(data[13])?;
|
||||
if unlikely(magic != QOI_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 { magic, width, height, channels, colorspace })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn n_pixels(&self) -> usize {
|
||||
(self.width as usize).saturating_mul(self.height as usize)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn validate(&self) -> Result<()> {
|
||||
if unlikely(self.magic != QOI_MAGIC) {
|
||||
return Err(Error::InvalidMagic { magic: self.magic });
|
||||
} else if unlikely(self.height == 0 || self.width == 0) {
|
||||
return Err(Error::EmptyImage { width: self.width, height: self.height });
|
||||
} else if unlikely((self.height as usize) * (self.width as usize) > QOI_PIXELS_MAX) {
|
||||
return Err(Error::ImageTooLarge { width: self.width, height: self.height });
|
||||
} else if unlikely(self.channels < 3 || self.channels > 4) {
|
||||
return Err(Error::InvalidChannels { channels: self.channels });
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue