qoi/
header.rs

1use core::convert::TryInto;
2
3use bytemuck::cast_slice;
4
5use crate::consts::{QOI_HEADER_SIZE, QOI_MAGIC, QOI_PIXELS_MAX};
6use crate::encode_max_len;
7use crate::error::{Error, Result};
8use crate::types::{Channels, ColorSpace};
9use crate::utils::unlikely;
10
11/// Image header: dimensions, channels, color space.
12///
13/// ### Notes
14/// A valid image header must satisfy the following conditions:
15/// * Both width and height must be non-zero.
16/// * Maximum number of pixels is 400Mp (=4e8 pixels).
17#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
18pub struct Header {
19    /// Image width in pixels
20    pub width: u32,
21    /// Image height in pixels
22    pub height: u32,
23    /// Number of 8-bit channels per pixel
24    pub channels: Channels,
25    /// Color space (informative field, doesn't affect encoding)
26    pub colorspace: ColorSpace,
27}
28
29impl Default for Header {
30    #[inline]
31    fn default() -> Self {
32        Self {
33            width: 1,
34            height: 1,
35            channels: Channels::default(),
36            colorspace: ColorSpace::default(),
37        }
38    }
39}
40
41impl Header {
42    /// Creates a new header and validates image dimensions.
43    #[inline]
44    pub const fn try_new(
45        width: u32, height: u32, channels: Channels, colorspace: ColorSpace,
46    ) -> Result<Self> {
47        let n_pixels = (width as usize).saturating_mul(height as usize);
48        if unlikely(n_pixels == 0 || n_pixels > QOI_PIXELS_MAX) {
49            return Err(Error::InvalidImageDimensions { width, height });
50        }
51        Ok(Self { width, height, channels, colorspace })
52    }
53
54    /// Creates a new header with modified channels.
55    #[inline]
56    pub const fn with_channels(mut self, channels: Channels) -> Self {
57        self.channels = channels;
58        self
59    }
60
61    /// Creates a new header with modified color space.
62    #[inline]
63    pub const fn with_colorspace(mut self, colorspace: ColorSpace) -> Self {
64        self.colorspace = colorspace;
65        self
66    }
67
68    /// Serializes the header into a bytes array.
69    #[inline]
70    pub(crate) fn encode(&self) -> [u8; QOI_HEADER_SIZE] {
71        let mut out = [0; QOI_HEADER_SIZE];
72        out[..4].copy_from_slice(&QOI_MAGIC.to_be_bytes());
73        out[4..8].copy_from_slice(&self.width.to_be_bytes());
74        out[8..12].copy_from_slice(&self.height.to_be_bytes());
75        out[12] = self.channels.into();
76        out[13] = self.colorspace.into();
77        out
78    }
79
80    /// Deserializes the header from a byte array.
81    #[inline]
82    pub(crate) fn decode(data: impl AsRef<[u8]>) -> Result<Self> {
83        let data = data.as_ref();
84        if unlikely(data.len() < QOI_HEADER_SIZE) {
85            return Err(Error::UnexpectedBufferEnd);
86        }
87        let v = cast_slice::<_, [u8; 4]>(&data[..12]);
88        let magic = u32::from_be_bytes(v[0]);
89        let width = u32::from_be_bytes(v[1]);
90        let height = u32::from_be_bytes(v[2]);
91        let channels = data[12].try_into()?;
92        let colorspace = data[13].try_into()?;
93        if unlikely(magic != QOI_MAGIC) {
94            return Err(Error::InvalidMagic { magic });
95        }
96        Self::try_new(width, height, channels, colorspace)
97    }
98
99    /// Returns a number of pixels in the image.
100    #[inline]
101    pub const fn n_pixels(&self) -> usize {
102        (self.width as usize).saturating_mul(self.height as usize)
103    }
104
105    /// Returns the total number of bytes in the raw pixel array.
106    ///
107    /// This may come useful when pre-allocating a buffer to decode the image into.
108    #[inline]
109    pub const fn n_bytes(&self) -> usize {
110        self.n_pixels() * self.channels.as_u8() as usize
111    }
112
113    /// The maximum number of bytes the encoded image will take.
114    ///
115    /// Can be used to pre-allocate the buffer to encode the image into.
116    #[inline]
117    pub fn encode_max_len(&self) -> usize {
118        encode_max_len(self.width, self.height, self.channels)
119    }
120}