1use super::{
6 additional_decode_scratch_bytes, jpeg_profile_stages_enabled, output_format_from_parts,
7 scaled_dimensions, scaled_rect_covering, validate_buffer, DecodeOutcome, Decoder, Downscale,
8 DownscaleFactor, Instant, JpegError, LosslessRegionRequest, LosslessRgbRegionFallback,
9 LosslessRgbaAlpha, OutputFormat, PixelFormat, Rect, ScratchPool, DEFAULT_SCRATCH,
10};
11
12mod dispatch;
13mod owned_output;
14mod profile;
15
16#[cfg(test)]
17mod tests;
18
19use self::dispatch::OutputRoute;
20use self::profile::DecodeProfileRecord;
21
22impl Decoder<'_> {
23 pub fn decode_into(
32 &self,
33 out: &mut [u8],
34 stride: usize,
35 fmt: PixelFormat,
36 ) -> Result<DecodeOutcome, JpegError> {
37 DEFAULT_SCRATCH
38 .with(|pool| self.decode_into_with_scratch(&mut pool.borrow_mut(), out, stride, fmt))
39 }
40
41 pub fn decode_scaled_into(
48 &self,
49 out: &mut [u8],
50 stride: usize,
51 fmt: PixelFormat,
52 scale: Downscale,
53 ) -> Result<DecodeOutcome, JpegError> {
54 DEFAULT_SCRATCH.with(|pool| {
55 self.decode_scaled_into_with_scratch(&mut pool.borrow_mut(), out, stride, fmt, scale)
56 })
57 }
58
59 pub fn decode_into_with_scratch(
69 &self,
70 pool: &mut ScratchPool,
71 out: &mut [u8],
72 stride: usize,
73 fmt: PixelFormat,
74 ) -> Result<DecodeOutcome, JpegError> {
75 self.decode_scaled_into_with_scratch(pool, out, stride, fmt, Downscale::None)
76 }
77
78 pub(super) fn decode_lossless_output_format_region_scaled(
79 &self,
80 out: &mut [u8],
81 stride: usize,
82 fmt: OutputFormat,
83 roi: Rect,
84 downscale: DownscaleFactor,
85 external_live_bytes: usize,
86 ) -> Option<Result<DecodeOutcome, JpegError>> {
87 self.lossless_plan.as_ref()?;
88 let result = match fmt {
89 OutputFormat::Rgb8 | OutputFormat::Rgb8Scaled { .. } => {
90 LosslessRgbRegionFallback::for_color_space_8(self.info.color_space)
91 .decode_rgb_region_scaled_into(
92 self,
93 LosslessRegionRequest {
94 out,
95 stride,
96 roi,
97 downscale,
98 external_live_bytes,
99 },
100 )
101 }
102 OutputFormat::Rgba8 { alpha } | OutputFormat::Rgba8Scaled { alpha, .. } => {
103 LosslessRgbRegionFallback::for_color_space_8(self.info.color_space)
104 .decode_rgba_region_scaled_into(
105 self,
106 LosslessRegionRequest {
107 out,
108 stride,
109 roi,
110 downscale,
111 external_live_bytes,
112 },
113 LosslessRgbaAlpha::U8(alpha),
114 )
115 }
116 OutputFormat::Gray8 | OutputFormat::Gray8Scaled { .. } => self
117 .decode_lossless_gray8_region_scaled_into(
118 out,
119 stride,
120 roi,
121 downscale,
122 external_live_bytes,
123 ),
124 OutputFormat::Gray16 | OutputFormat::Gray16Scaled { .. } => self
125 .decode_lossless_gray16_region_scaled_into(
126 out,
127 stride,
128 roi,
129 downscale,
130 external_live_bytes,
131 ),
132 OutputFormat::Rgb16 | OutputFormat::Rgb16Scaled { .. } => {
133 LosslessRgbRegionFallback::for_color_space_16(self.info.color_space)
134 .decode_rgb_region_scaled_into(
135 self,
136 LosslessRegionRequest {
137 out,
138 stride,
139 roi,
140 downscale,
141 external_live_bytes,
142 },
143 )
144 }
145 OutputFormat::Rgba16 { alpha } | OutputFormat::Rgba16Scaled { alpha, .. } => {
146 LosslessRgbRegionFallback::for_color_space_16(self.info.color_space)
147 .decode_rgba_region_scaled_into(
148 self,
149 LosslessRegionRequest {
150 out,
151 stride,
152 roi,
153 downscale,
154 external_live_bytes,
155 },
156 LosslessRgbaAlpha::U16(alpha),
157 )
158 }
159 };
160 Some(result)
161 }
162
163 pub(super) fn decode_into_output_format_with_scratch(
164 &self,
165 pool: &mut ScratchPool,
166 out: &mut [u8],
167 stride: usize,
168 fmt: OutputFormat,
169 ) -> Result<DecodeOutcome, JpegError> {
170 self.decode_into_output_format_with_scratch_and_external(pool, out, stride, fmt, 0)
171 }
172
173 fn decode_into_output_format_with_scratch_and_external(
174 &self,
175 pool: &mut ScratchPool,
176 out: &mut [u8],
177 stride: usize,
178 fmt: OutputFormat,
179 external_live_bytes: usize,
180 ) -> Result<DecodeOutcome, JpegError> {
181 let profile_enabled = jpeg_profile_stages_enabled();
182 let total_start = profile_enabled.then(Instant::now);
183 let downscale = fmt.downscale();
184 let (w, h) = scaled_dimensions(self.info.dimensions, downscale);
185 let full_roi = Rect::full(self.info.dimensions);
186 let additional_scratch = additional_decode_scratch_bytes(
187 self.info.sof_kind,
188 self.info.dimensions,
189 fmt,
190 full_roi,
191 Rect::full((w, h)),
192 downscale,
193 )?;
194 let scratch_bytes = self.prepare_decode_workspace_with_additional(
195 pool,
196 external_live_bytes,
197 additional_scratch,
198 )?;
199 let bpp = fmt.bytes_per_pixel();
200 validate_buffer(out, stride, w, h, bpp)?;
201 if let Some(result) = self.decode_lossless_output_format_region_scaled(
202 out,
203 stride,
204 fmt,
205 full_roi,
206 downscale,
207 external_live_bytes,
208 ) {
209 return result;
210 }
211 let decode_start = profile_enabled.then(Instant::now);
212 let output_rect = Rect::full((w, h));
213 let result = self.dispatch_full_output(OutputRoute {
214 pool,
215 out,
216 stride,
217 fmt,
218 source_roi: full_roi,
219 output_rect,
220 downscale,
221 external_live_bytes,
222 });
223 if let (Some(total_start), Some(decode_start), Ok(outcome)) =
224 (total_start, decode_start, &result)
225 {
226 DecodeProfileRecord {
227 total_start,
228 decode_start,
229 source_dimensions: self.info.dimensions,
230 output_rect,
231 stride,
232 bytes_per_pixel: bpp,
233 scratch_bytes,
234 fmt,
235 downscale,
236 source_roi: None,
237 }
238 .emit(outcome);
239 }
240 result
241 }
242
243 pub fn decode_scaled_into_with_scratch(
249 &self,
250 pool: &mut ScratchPool,
251 out: &mut [u8],
252 stride: usize,
253 fmt: PixelFormat,
254 scale: Downscale,
255 ) -> Result<DecodeOutcome, JpegError> {
256 self.decode_into_output_format_with_scratch(
257 pool,
258 out,
259 stride,
260 output_format_from_parts(self.info.sof_kind, fmt, scale)?,
261 )
262 }
263 pub fn decode_region_into(
274 &self,
275 out: &mut [u8],
276 stride: usize,
277 fmt: PixelFormat,
278 roi: Rect,
279 ) -> Result<DecodeOutcome, JpegError> {
280 DEFAULT_SCRATCH.with(|pool| {
281 self.decode_region_into_with_scratch(&mut pool.borrow_mut(), out, stride, fmt, roi)
282 })
283 }
284
285 pub(crate) fn decode_region_into_with_scratch(
287 &self,
288 pool: &mut ScratchPool,
289 out: &mut [u8],
290 stride: usize,
291 fmt: PixelFormat,
292 roi: Rect,
293 ) -> Result<DecodeOutcome, JpegError> {
294 self.decode_region_scaled_into_with_scratch(pool, out, stride, fmt, roi, Downscale::None)
295 }
296
297 pub(super) fn decode_region_into_output_format_with_scratch(
298 &self,
299 pool: &mut ScratchPool,
300 out: &mut [u8],
301 stride: usize,
302 fmt: OutputFormat,
303 roi: Rect,
304 ) -> Result<DecodeOutcome, JpegError> {
305 self.decode_region_into_output_format_with_scratch_and_external(
306 pool, out, stride, fmt, roi, 0,
307 )
308 }
309
310 fn decode_region_into_output_format_with_scratch_and_external(
311 &self,
312 pool: &mut ScratchPool,
313 out: &mut [u8],
314 stride: usize,
315 fmt: OutputFormat,
316 roi: Rect,
317 external_live_bytes: usize,
318 ) -> Result<DecodeOutcome, JpegError> {
319 let profile_enabled = jpeg_profile_stages_enabled();
320 let total_start = profile_enabled.then(Instant::now);
321 if !roi.is_within(self.info.dimensions) {
322 return Err(JpegError::RectOutOfBounds {
323 rect: roi,
324 width: self.info.dimensions.0,
325 height: self.info.dimensions.1,
326 });
327 }
328
329 if roi == Rect::full(self.info.dimensions) {
330 return self.decode_into_output_format_with_scratch_and_external(
331 pool,
332 out,
333 stride,
334 fmt,
335 external_live_bytes,
336 );
337 }
338
339 let downscale = fmt.downscale();
340 let scaled_roi = scaled_rect_covering(roi, downscale)?;
341 let additional_scratch = additional_decode_scratch_bytes(
342 self.info.sof_kind,
343 self.info.dimensions,
344 fmt,
345 roi,
346 scaled_roi,
347 downscale,
348 )?;
349 let scratch_bytes = self.prepare_decode_workspace_with_additional(
350 pool,
351 external_live_bytes,
352 additional_scratch,
353 )?;
354 validate_buffer(
355 out,
356 stride,
357 scaled_roi.w,
358 scaled_roi.h,
359 fmt.bytes_per_pixel(),
360 )?;
361 if let Some(result) = self.decode_lossless_output_format_region_scaled(
362 out,
363 stride,
364 fmt,
365 roi,
366 downscale,
367 external_live_bytes,
368 ) {
369 return result;
370 }
371
372 let decode_start = profile_enabled.then(Instant::now);
373 let result = self.dispatch_region_output(OutputRoute {
374 pool,
375 out,
376 stride,
377 fmt,
378 source_roi: roi,
379 output_rect: scaled_roi,
380 downscale,
381 external_live_bytes,
382 });
383 if let (Some(total_start), Some(decode_start), Ok(outcome)) =
384 (total_start, decode_start, &result)
385 {
386 DecodeProfileRecord {
387 total_start,
388 decode_start,
389 source_dimensions: self.info.dimensions,
390 output_rect: scaled_roi,
391 stride,
392 bytes_per_pixel: fmt.bytes_per_pixel(),
393 scratch_bytes,
394 fmt,
395 downscale,
396 source_roi: Some(roi),
397 }
398 .emit(outcome);
399 }
400 result
401 }
402
403 pub fn decode_region_scaled_into(
411 &self,
412 out: &mut [u8],
413 stride: usize,
414 fmt: PixelFormat,
415 roi: Rect,
416 scale: Downscale,
417 ) -> Result<DecodeOutcome, JpegError> {
418 DEFAULT_SCRATCH.with(|pool| {
419 self.decode_region_scaled_into_with_scratch(
420 &mut pool.borrow_mut(),
421 out,
422 stride,
423 fmt,
424 roi,
425 scale,
426 )
427 })
428 }
429
430 pub fn decode_region_scaled_into_with_scratch(
437 &self,
438 pool: &mut ScratchPool,
439 out: &mut [u8],
440 stride: usize,
441 fmt: PixelFormat,
442 roi: Rect,
443 scale: Downscale,
444 ) -> Result<DecodeOutcome, JpegError> {
445 self.decode_region_into_output_format_with_scratch(
446 pool,
447 out,
448 stride,
449 output_format_from_parts(self.info.sof_kind, fmt, scale)?,
450 roi,
451 )
452 }
453}