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}
121