1use crate::error::DecodeError;
2use crate::filter;
3use crate::format::{self, ChunkType, Header};
4use crate::simd;
5
6#[derive(Debug, Clone)]
11pub struct Image {
12 pub width: u32,
14 pub height: u32,
16 pub pixels: Vec<u8>,
18}
19
20impl Image {
21 #[must_use]
23 pub fn new(width: u32, height: u32) -> Self {
24 Self {
25 width,
26 height,
27 pixels: vec![0; (width as usize) * (height as usize) * 4],
28 }
29 }
30
31 #[must_use]
33 pub const fn len(&self) -> usize {
34 (self.width as usize) * (self.height as usize)
35 }
36
37 #[must_use]
39 pub const fn is_empty(&self) -> bool {
40 self.width == 0 || self.height == 0
41 }
42
43 #[must_use]
45 pub const fn byte_len(&self) -> usize {
46 self.pixels.len()
47 }
48
49 #[must_use]
51 pub const fn contains(&self, x: u32, y: u32) -> bool {
52 x < self.width && y < self.height
53 }
54
55 #[must_use]
61 pub fn get_pixel(&self, x: u32, y: u32) -> [u8; 4] {
62 self.try_get_pixel(x, y)
63 .expect("pixel coordinate out of bounds")
64 }
65
66 #[must_use]
68 pub fn try_get_pixel(&self, x: u32, y: u32) -> Option<[u8; 4]> {
69 if !self.contains(x, y) {
70 return None;
71 }
72 let idx = ((y * self.width + x) as usize) * 4;
73 Some([
74 self.pixels[idx],
75 self.pixels[idx + 1],
76 self.pixels[idx + 2],
77 self.pixels[idx + 3],
78 ])
79 }
80
81 pub fn set_pixel(&mut self, x: u32, y: u32, rgba: [u8; 4]) {
87 self.try_set_pixel(x, y, rgba)
88 .expect("pixel coordinate out of bounds");
89 }
90
91 pub fn try_set_pixel(&mut self, x: u32, y: u32, rgba: [u8; 4]) -> Option<()> {
93 if !self.contains(x, y) {
94 return None;
95 }
96 let idx = ((y * self.width + x) as usize) * 4;
97 self.pixels[idx] = rgba[0];
98 self.pixels[idx + 1] = rgba[1];
99 self.pixels[idx + 2] = rgba[2];
100 self.pixels[idx + 3] = rgba[3];
101 Some(())
102 }
103}
104
105pub fn decode(data: &[u8]) -> Result<Image, DecodeError> {
114 let header = Header::parse(data)?;
116 let expected_bytes = header.expected_pixel_bytes();
117
118 let mut offset = format::HEADER_SIZE;
120 let mut pixels: Option<Vec<u8>> = None;
121
122 while offset < data.len() {
123 let chunk_header = format::ChunkHeader::parse(&data[offset..])?;
124 offset += format::CHUNK_HEADER_SIZE;
125
126 let chunk_data_end = offset
127 .checked_add(chunk_header.compressed_size as usize)
128 .filter(|&end| end <= data.len())
129 .ok_or(DecodeError::TruncatedChunk)?;
130 let compressed = &data[offset..chunk_data_end];
131 offset = chunk_data_end;
132
133 match chunk_header.chunk_type {
134 ChunkType::PixelData => {
135 if pixels.is_some() {
136 return Err(DecodeError::MultiplePixelData);
137 }
138 let decompressed = zstd::decode_all(std::io::Cursor::new(compressed))
139 .map_err(DecodeError::ZstdError)?;
140
141 let planar = if header.flags & format::FLAG_FILTERED != 0 {
144 let width = header.width as usize;
145 let height = header.height as usize;
146 let expected_filtered = expected_bytes + filter::overhead_bytes(height, 4);
147 if decompressed.len() != expected_filtered {
148 return Err(DecodeError::SizeMismatch {
149 expected: expected_filtered,
150 actual: decompressed.len(),
151 });
152 }
153 filter::unfilter_planes(&decompressed, width, height, 4).ok_or(
154 DecodeError::SizeMismatch {
155 expected: expected_bytes,
156 actual: decompressed.len(),
157 },
158 )?
159 } else {
160 if decompressed.len() != expected_bytes {
161 return Err(DecodeError::SizeMismatch {
162 expected: expected_bytes,
163 actual: decompressed.len(),
164 });
165 }
166 decompressed
167 };
168
169 let rgba = if header.flags & format::FLAG_PLANAR != 0 {
171 let n_pixels = header.width as usize * header.height as usize;
172 simd::reinterleave_rgba(&planar, n_pixels)
173 } else {
174 planar
175 };
176 pixels = Some(rgba);
177 }
178 ChunkType::Metadata | ChunkType::IccProfile => {
179 }
181 }
182 }
183
184 let pixels = pixels.ok_or(DecodeError::MissingPixelData)?;
185
186 Ok(Image {
187 width: header.width,
188 height: header.height,
189 pixels,
190 })
191}