1#![allow(unknown_lints, clippy::chunks_exact_to_as_chunks)]
57#![cfg_attr(docsrs, feature(doc_cfg))]
61
62use denise::Size;
63use denise_render::blend::premultiply;
64
65pub const MAX_PIXELS: u64 = 32 * 1024 * 1024;
70
71#[derive(Clone, Debug, PartialEq, Eq)]
73pub struct Picture {
74 pixels: Vec<u32>,
75 size: Size,
76}
77
78impl Picture {
79 fn checked(pixels: Vec<u32>, size: Size) -> Result<Self, DecodeError> {
89 let expected = size.width as usize * size.height as usize;
90 if pixels.len() != expected {
91 return Err(DecodeError::Malformed(format!(
92 "the decoder produced {} pixels for a {}x{} image, which needs {expected}",
93 pixels.len(),
94 size.width,
95 size.height,
96 )));
97 }
98 Ok(Self { pixels, size })
99 }
100
101 #[inline]
103 pub const fn size(&self) -> Size {
104 self.size
105 }
106
107 #[inline]
109 pub fn pixels(&self) -> &[u32] {
110 &self.pixels
111 }
112
113 #[inline]
115 pub fn into_parts(self) -> (Vec<u32>, Size) {
116 (self.pixels, self.size)
117 }
118}
119
120#[derive(Clone, Debug, PartialEq, Eq)]
122#[non_exhaustive]
123pub enum DecodeError {
124 Unrecognised,
126 Disabled(&'static str),
129 Malformed(String),
132 TooLarge {
135 width: u32,
137 height: u32,
139 },
140 Unsupported(&'static str),
143}
144
145impl core::fmt::Display for DecodeError {
146 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
147 match self {
148 Self::Unrecognised => write!(f, "not a PNG, JPEG, GIF or BMP"),
149 Self::Disabled(feature) => write!(
150 f,
151 "recognised the format, but the `{feature}` feature of denise-image is compiled out"
152 ),
153 Self::Malformed(why) => write!(f, "malformed image: {why}"),
154 Self::TooLarge { width, height } => write!(
155 f,
156 "{width}x{height} exceeds the {MAX_PIXELS}-pixel decode limit"
157 ),
158 Self::Unsupported(what) => write!(f, "unsupported image variant: {what}"),
159 }
160 }
161}
162
163impl std::error::Error for DecodeError {}
164
165pub fn decode(bytes: &[u8]) -> Result<Picture, DecodeError> {
171 if bytes.starts_with(&[0x89, b'P', b'N', b'G']) {
172 #[cfg(feature = "png")]
173 return decode_png(bytes);
174 #[cfg(not(feature = "png"))]
175 return Err(DecodeError::Disabled("png"));
176 }
177 if bytes.starts_with(&[0xFF, 0xD8, 0xFF]) {
178 #[cfg(feature = "jpeg")]
179 return decode_jpeg(bytes);
180 #[cfg(not(feature = "jpeg"))]
181 return Err(DecodeError::Disabled("jpeg"));
182 }
183 if bytes.starts_with(b"GIF87a") || bytes.starts_with(b"GIF89a") {
184 #[cfg(feature = "gif")]
185 return decode_gif(bytes);
186 #[cfg(not(feature = "gif"))]
187 return Err(DecodeError::Disabled("gif"));
188 }
189 if bytes.starts_with(b"BM") {
190 return decode_bmp(bytes);
191 }
192 Err(DecodeError::Unrecognised)
193}
194
195fn checked_size(width: u32, height: u32) -> Result<Size, DecodeError> {
198 if width == 0 || height == 0 {
199 return Err(DecodeError::Malformed("zero-sized image".into()));
200 }
201 if width as u64 * height as u64 > MAX_PIXELS {
202 return Err(DecodeError::TooLarge { width, height });
203 }
204 Ok(Size::new(width, height))
205}
206
207#[cfg(feature = "png")]
209fn from_rgba(data: &[u8], size: Size) -> Result<Picture, DecodeError> {
210 let mut pixels: Vec<u32> = data
211 .chunks_exact(4)
212 .map(|px| u32::from_be_bytes([px[3], px[0], px[1], px[2]]))
213 .collect();
214 premultiply(&mut pixels);
215 Picture::checked(pixels, size)
216}
217
218#[cfg(any(feature = "png", feature = "jpeg"))]
220fn from_rgb(data: &[u8], size: Size) -> Result<Picture, DecodeError> {
221 let pixels = data
222 .chunks_exact(3)
223 .map(|px| u32::from_be_bytes([0xFF, px[0], px[1], px[2]]))
224 .collect();
225 Picture::checked(pixels, size)
226}
227
228#[cfg(feature = "png")]
231pub fn decode_png(bytes: &[u8]) -> Result<Picture, DecodeError> {
232 let malformed = |e: png::DecodingError| DecodeError::Malformed(e.to_string());
233
234 let mut decoder = png::Decoder::new(std::io::Cursor::new(bytes));
235 decoder.set_transformations(png::Transformations::EXPAND | png::Transformations::STRIP_16);
236 let mut reader = decoder.read_info().map_err(malformed)?;
237 let info = reader.info();
238 let size = checked_size(info.width, info.height)?;
239
240 let buffer_size = reader.output_buffer_size().ok_or(DecodeError::TooLarge {
241 width: size.width,
242 height: size.height,
243 })?;
244 let mut buf = vec![0u8; buffer_size];
245 let out = reader.next_frame(&mut buf).map_err(malformed)?;
246 let data = &buf[..out.buffer_size()];
247
248 let size = checked_size(out.width, out.height)?;
255
256 Ok(match out.color_type {
257 png::ColorType::Rgba => from_rgba(data, size)?,
258 png::ColorType::Rgb => from_rgb(data, size)?,
259 png::ColorType::Grayscale => {
260 let pixels = data
261 .iter()
262 .map(|&g| u32::from_be_bytes([0xFF, g, g, g]))
263 .collect();
264 Picture::checked(pixels, size)?
265 }
266 png::ColorType::GrayscaleAlpha => {
267 let mut pixels: Vec<u32> = data
268 .chunks_exact(2)
269 .map(|px| u32::from_be_bytes([px[1], px[0], px[0], px[0]]))
270 .collect();
271 premultiply(&mut pixels);
272 Picture::checked(pixels, size)?
273 }
274 png::ColorType::Indexed => {
276 return Err(DecodeError::Malformed(
277 "the decoder returned indexed pixels it promised to expand".into(),
278 ));
279 }
280 })
281}
282
283#[cfg(feature = "jpeg")]
285pub fn decode_jpeg(bytes: &[u8]) -> Result<Picture, DecodeError> {
286 use zune_jpeg::JpegDecoder;
287 use zune_jpeg::zune_core::bytestream::ZCursor;
288 use zune_jpeg::zune_core::colorspace::ColorSpace;
289 use zune_jpeg::zune_core::options::DecoderOptions;
290
291 let options = DecoderOptions::default().jpeg_set_out_colorspace(ColorSpace::RGB);
292 let mut decoder = JpegDecoder::new_with_options(ZCursor::new(bytes), options);
293 decoder
294 .decode_headers()
295 .map_err(|e| DecodeError::Malformed(e.to_string()))?;
296 let (width, height) = decoder
297 .dimensions()
298 .ok_or_else(|| DecodeError::Malformed("no dimensions in the JPEG header".into()))?;
299 let size = checked_size(width as u32, height as u32)?;
300
301 let data = decoder
302 .decode()
303 .map_err(|e| DecodeError::Malformed(e.to_string()))?;
304 from_rgb(&data, size)
305}
306
307#[cfg(feature = "gif")]
311pub fn decode_gif(bytes: &[u8]) -> Result<Picture, DecodeError> {
312 let malformed = |e: gif::DecodingError| DecodeError::Malformed(e.to_string());
313
314 let mut options = gif::DecodeOptions::new();
315 options.set_color_output(gif::ColorOutput::RGBA);
316 let mut decoder = options.read_info(bytes).map_err(malformed)?;
317 let size = checked_size(decoder.width() as u32, decoder.height() as u32)?;
318
319 let frame = decoder
320 .read_next_frame()
321 .map_err(malformed)?
322 .ok_or_else(|| DecodeError::Malformed("a GIF with no frames".into()))?;
323
324 let mut pixels = vec![0u32; (size.width * size.height) as usize];
325 let (left, top) = (frame.left as u32, frame.top as u32);
326 for y in 0..frame.height as u32 {
327 for x in 0..frame.width as u32 {
328 let (dx, dy) = (left + x, top + y);
329 if dx >= size.width || dy >= size.height {
330 continue;
331 }
332 let i = ((y * frame.width as u32 + x) * 4) as usize;
333 let Some(px) = frame.buffer.get(i..i + 4) else {
337 continue;
338 };
339 pixels[(dy * size.width + dx) as usize] =
340 u32::from_be_bytes([px[3], px[0], px[1], px[2]]);
341 }
342 }
343 premultiply(&mut pixels);
344 Picture::checked(pixels, size)
345}
346
347pub fn decode_bmp(bytes: &[u8]) -> Result<Picture, DecodeError> {
355 fn u16at(bytes: &[u8], at: usize) -> Result<u16, DecodeError> {
356 Ok(u16::from_le_bytes(field::<2>(bytes, at)?))
357 }
358 fn u32at(bytes: &[u8], at: usize) -> Result<u32, DecodeError> {
359 Ok(u32::from_le_bytes(field::<4>(bytes, at)?))
360 }
361 fn field<const N: usize>(bytes: &[u8], at: usize) -> Result<[u8; N], DecodeError> {
362 bytes
363 .get(at..at + N)
364 .and_then(|b| b.try_into().ok())
365 .ok_or_else(|| DecodeError::Malformed("truncated BMP header".into()))
366 }
367
368 if !bytes.starts_with(b"BM") {
369 return Err(DecodeError::Malformed("not a BMP".into()));
370 }
371 let data_offset = u32at(bytes, 10)? as usize;
372 if u32at(bytes, 14)? < 40 {
373 return Err(DecodeError::Unsupported("BMP with a BITMAPCOREHEADER"));
374 }
375 let raw_width = u32at(bytes, 18)? as i32;
376 let raw_height = u32at(bytes, 22)? as i32;
377 let bpp = u16at(bytes, 28)?;
378 let compression = u32at(bytes, 30)?;
379
380 if compression != 0 {
381 return Err(DecodeError::Unsupported("compressed BMP"));
382 }
383 if bpp != 24 && bpp != 32 {
384 return Err(DecodeError::Unsupported("BMP that is not 24- or 32-bit"));
385 }
386 if raw_width <= 0 || raw_height == 0 || raw_height == i32::MIN {
387 return Err(DecodeError::Malformed("BMP dimensions out of range".into()));
388 }
389 let top_down = raw_height < 0;
391 let size = checked_size(raw_width as u32, raw_height.unsigned_abs())?;
392
393 let bytes_per_px = bpp as usize / 8;
394 let stride = (size.width as usize * bytes_per_px).next_multiple_of(4);
396 let data = bytes
397 .get(data_offset..data_offset + stride * size.height as usize)
398 .ok_or_else(|| DecodeError::Malformed("truncated BMP pixel data".into()))?;
399
400 let mut pixels = Vec::with_capacity((size.width * size.height) as usize);
401 let mut alpha_seen = false;
402 for y in 0..size.height as usize {
403 let row = if top_down {
404 y
405 } else {
406 size.height as usize - 1 - y
407 };
408 let row = &data[row * stride..];
409 for x in 0..size.width as usize {
410 let px = &row[x * bytes_per_px..];
411 let a = if bpp == 32 { px[3] } else { 0xFF };
412 alpha_seen |= bpp == 32 && a != 0;
413 pixels.push(u32::from_be_bytes([a, px[2], px[1], px[0]]));
414 }
415 }
416 if bpp == 32 {
417 if alpha_seen {
418 premultiply(&mut pixels);
419 } else {
420 for px in &mut pixels {
422 *px |= 0xFF00_0000;
423 }
424 }
425 }
426 Picture::checked(pixels, size)
427}
428
429#[cfg(doctest)]
432#[doc = include_str!("../README.md")]
433struct Readme;