1use flashraw_core::{Error, Limits, Result};
4
5pub struct Decoded8 {
7 pub width: usize,
8 pub height: usize,
9 pub components: usize,
10 pub data: Vec<u8>,
11}
12
13pub fn decode(data: &[u8], limits: &Limits) -> Result<Decoded8> {
17 use zune_jpeg::zune_core::colorspace::ColorSpace;
18 use zune_jpeg::zune_core::options::DecoderOptions;
19 let cap = limits.max_dimension.min(65535) as usize;
21 let options = |cs| DecoderOptions::default().jpeg_set_out_colorspace(cs).set_max_width(cap).set_max_height(cap);
22 let mut d = zune_jpeg::JpegDecoder::new_with_options(data, options(ColorSpace::RGB));
23 d.decode_headers().map_err(|e| Error::corrupt(format!("JPEG: {e:?}")))?;
24 let info = d.info().ok_or_else(|| Error::corrupt("JPEG: no header info"))?;
25 let gray = info.components == 1;
26 limits.check_dims(u32::from(info.width), u32::from(info.height), 3)?;
27 if gray {
28 d = zune_jpeg::JpegDecoder::new_with_options(data, options(ColorSpace::Luma));
30 }
31 let pixels = d.decode().map_err(|e| Error::corrupt(format!("JPEG: {e:?}")))?;
32 let (w, h) = d.dimensions().ok_or_else(|| Error::corrupt("JPEG: no dimensions"))?;
33 Ok(Decoded8 { width: w, height: h, components: if gray { 1 } else { 3 }, data: pixels })
34}
35
36pub fn dimensions(data: &[u8]) -> Option<(u32, u32)> {
38 let mut p = 2usize;
39 if data.get(0..2)? != [0xFF, 0xD8] {
40 return None;
41 }
42 while p + 9 < data.len() {
43 if data[p] != 0xFF {
44 p += 1;
45 continue;
46 }
47 let m = data[p + 1];
48 if m == 0xFF {
49 p += 1;
50 continue;
51 }
52 if matches!(m, 0xD8 | 0x01 | 0xD0..=0xD7) {
53 p += 2;
54 continue;
55 }
56 let len = u16::from_be_bytes([data[p + 2], data[p + 3]]) as usize;
57 if matches!(m, 0xC0..=0xC3 | 0xC5..=0xC7 | 0xC9..=0xCB | 0xCD..=0xCF) {
58 let h = u16::from_be_bytes([data[p + 5], data[p + 6]]) as u32;
59 let w = u16::from_be_bytes([data[p + 7], data[p + 8]]) as u32;
60 return Some((w, h));
61 }
62 if m == 0xDA || len < 2 {
63 return None;
64 }
65 p += 2 + len;
66 }
67 None
68}