use std::convert::TryInto; use bytemuck::cast_slice; use crate::consts::{QOI_HEADER_SIZE, QOI_MAGIC, QOI_PIXELS_MAX}; use crate::encoded_size_limit; use crate::error::{Error, Result}; use crate::types::{Channels, ColorSpace}; use crate::utils::unlikely; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Header { pub width: u32, pub height: u32, pub channels: Channels, pub colorspace: ColorSpace, } impl Default for Header { #[inline] fn default() -> Self { Self { width: 1, height: 1, channels: Channels::default(), colorspace: ColorSpace::default(), } } } impl Header { #[inline] pub const fn try_new( width: u32, height: u32, channels: Channels, colorspace: ColorSpace, ) -> Result { 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] pub const fn with_colorspace(mut self, colorspace: ColorSpace) -> Self { self.colorspace = colorspace; self } #[inline] pub const fn encoded_size() -> usize { QOI_HEADER_SIZE } #[inline] pub(crate) fn encode(&self) -> [u8; QOI_HEADER_SIZE] { let mut out = [0; QOI_HEADER_SIZE]; out[..4].copy_from_slice(&QOI_MAGIC.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[12] = self.channels.into(); out[13] = self.colorspace.into(); out } #[inline] pub(crate) fn decode(data: impl AsRef<[u8]>) -> Result { let data = data.as_ref(); if unlikely(data.len() < QOI_HEADER_SIZE) { return Err(Error::InputBufferTooSmall { size: data.len(), required: QOI_HEADER_SIZE }); } let v = cast_slice::<_, [u8; 4]>(&data[..12]); let magic = u32::from_be_bytes(v[0]); let width = u32::from_be_bytes(v[1]); let height = u32::from_be_bytes(v[2]); let channels = data[12].try_into()?; let colorspace = data[13].try_into()?; if unlikely(magic != QOI_MAGIC) { return Err(Error::InvalidMagic { magic }); } Self::try_new(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 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) } }