1#![allow(
5 clippy::indexing_slicing,
6 reason = "planes are the canvas size, so every pixel index is in range"
7)]
8
9use otf_pixels_core::{
10 Animation, Codec, Decoder, Format, ImageDescriptor, Limits, Orientation, PixelFormat,
11 PixelsError, Result, Source,
12};
13
14use crate::decode::{container, header::ExtraKind, render};
15
16const MAX_COMPRESSED: usize = 256 * 1024 * 1024;
18
19#[derive(Debug)]
21pub struct JxlDecoder {
22 descriptor: ImageDescriptor,
23 codestream: Vec<u8>,
25 pixels: Option<Vec<u8>>,
27 row: u32,
28 orientation: Orientation,
29 icc: Option<Vec<u8>>,
30 exif: Option<Vec<u8>>,
31 animation: Option<Animation>,
32}
33
34impl JxlDecoder {
35 pub fn new<S: Source>(mut source: S, limits: Limits) -> Result<Self> {
43 let mut bytes = Vec::new();
44 let mut chunk = [0_u8; 64 * 1024];
45 loop {
46 if bytes.len() > MAX_COMPRESSED {
47 return Err(PixelsError::malformed(
48 "jxl",
49 format!("stream exceeds {MAX_COMPRESSED} bytes"),
50 ));
51 }
52 match source.read(&mut chunk)? {
53 0 => break,
54 read => bytes.extend_from_slice(chunk.get(..read).unwrap_or(&[])),
55 }
56 }
57 let contents = container::parse(&bytes)?;
58 let exif = contents.exif;
59 let codestream = contents.codestream;
60 let (header, icc, durations) = render::probe(&codestream)?;
61 let (width, height) = (header.width, header.height);
62 let alpha = header.alpha().is_some();
63 let wide = header.bit_depth.bits > 8;
64 let pixel = match (header.grey(), alpha, wide, header.bit_depth.float) {
65 (_, false, _, true) => PixelFormat::RgbF32,
66 (_, true, _, true) => PixelFormat::RgbaF32,
67 (true, false, false, _) => PixelFormat::Gray8,
68 (true, false, true, _) => PixelFormat::Gray16,
69 (true, true, false, _) => PixelFormat::GrayA8,
70 (false, false, false, _) => PixelFormat::Rgb8,
71 (false, false, true, _) => PixelFormat::Rgb16,
72 (false, true, false, _) => PixelFormat::Rgba8,
73 (_, true, true, _) => PixelFormat::Rgba16,
74 };
75 let descriptor = ImageDescriptor::with_limits(width, height, pixel, &limits)?;
76 let animation = header.animation.and_then(|a| {
77 if durations.len() < 2 || a.tps_numerator == 0 {
78 return None;
79 }
80 let ms = durations
81 .iter()
82 .map(|&t| {
83 (u64::from(t) * 1000 * u64::from(a.tps_denominator)
84 / u64::from(a.tps_numerator)) as u32
85 })
86 .collect();
87 Animation::new(ms, a.num_loops)
88 });
89 let orientation = Orientation::from_exif(header.orientation as u16).unwrap_or_default();
91 let icc = if header.xyb { None } else { icc };
93 Ok(Self {
94 descriptor,
95 codestream,
96 pixels: None,
97 row: 0,
98 orientation,
99 icc,
100 exif,
101 animation,
102 })
103 }
104
105 fn decode_pixels(&self) -> Result<Vec<u8>> {
106 let image = render::decode(&self.codestream)?;
107 let canvas = &image.canvas;
108 let alpha = image
109 .header
110 .extra
111 .iter()
112 .position(|e| matches!(e.kind, ExtraKind::Alpha { .. }))
113 .and_then(|i| canvas.extra.get(i));
114 let n = canvas.width * canvas.height;
115 let pixel = self.descriptor.pixel;
116 let channels: Vec<&[f32]> = if pixel.channels() <= 2 {
117 vec![&canvas.colour[0][..n]]
118 } else {
119 canvas.colour.iter().map(|p| &p[..n]).collect()
120 };
121 let mut planes = channels;
122 if pixel.has_alpha() {
123 planes.push(alpha.map_or(&[][..], |a| &a[..n]));
124 }
125 let mut out = Vec::with_capacity(self.descriptor.row_bytes() * canvas.height);
126 for i in 0..n {
127 for p in &planes {
128 let v = p.get(i).copied().unwrap_or(1.0);
129 match pixel {
130 PixelFormat::RgbF32 | PixelFormat::RgbaF32 => {
131 out.extend_from_slice(&v.to_ne_bytes())
132 }
133 PixelFormat::Gray16 | PixelFormat::Rgb16 | PixelFormat::Rgba16 => {
134 out.extend_from_slice(
135 &((v.clamp(0.0, 1.0) * 65535.0 + 0.5) as u16).to_ne_bytes(),
136 );
137 }
138 _ => out.push((v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8),
139 }
140 }
141 }
142 Ok(out)
143 }
144}
145
146impl Decoder for JxlDecoder {
147 fn descriptor(&self) -> ImageDescriptor {
148 self.descriptor
149 }
150
151 fn orientation(&self) -> Orientation {
152 self.orientation
153 }
154
155 fn icc_profile(&self) -> Option<&[u8]> {
156 self.icc.as_deref()
157 }
158
159 fn exif(&self) -> Option<&[u8]> {
160 self.exif.as_deref()
161 }
162
163 fn animation(&self) -> Option<Animation> {
164 self.animation.clone()
165 }
166
167 fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
168 if self.row >= self.descriptor.height {
169 return Err(PixelsError::invalid_argument(
170 "out",
171 format!("all {} rows have already been read", self.descriptor.height),
172 ));
173 }
174 let row_bytes = self.descriptor.row_bytes();
175 if out.len() != row_bytes {
176 return Err(PixelsError::invalid_argument(
177 "out",
178 format!("row buffer is {} bytes, expected {row_bytes}", out.len()),
179 ));
180 }
181 if self.pixels.is_none() {
182 self.pixels = Some(self.decode_pixels()?);
183 self.codestream = Vec::new();
184 }
185 let start = self.row as usize * row_bytes;
186 let row = self
187 .pixels
188 .as_deref()
189 .and_then(|p| p.get(start..start + row_bytes))
190 .ok_or_else(|| PixelsError::malformed("jxl", "decoded image is short"))?;
191 out.copy_from_slice(row);
192 self.row += 1;
193 Ok(())
194 }
195}
196
197#[must_use]
199pub fn probe(prefix: &[u8]) -> bool {
200 container::probe(prefix)
201}
202
203#[derive(Debug, Clone, Copy, Default)]
205pub struct JxlCodec;
206
207impl Codec for JxlCodec {
208 fn format(&self) -> Format {
209 Format::Jxl
210 }
211
212 fn magic_len(&self) -> usize {
213 12
214 }
215
216 fn probe(&self, prefix: &[u8]) -> bool {
217 probe(prefix)
218 }
219}