1use crate::backend::Backend;
6use crate::context::DecoderContext;
7use crate::entropy::block::{decode_block_with_activity, BlockActivity, CoefficientBlock};
8use crate::entropy::huffman::HuffmanTable;
9use crate::entropy::progressive::{
10 decode_progressive, decode_progressive_dct_blocks, PreparedProgressiveComponentPlan,
11 PreparedProgressivePlan, PreparedProgressiveScan, PreparedProgressiveScanComponent,
12};
13use crate::entropy::sequential::{
14 decode_scan_baseline, decode_scan_baseline_rgb, decode_scan_fast_rgb_444,
15 decode_scan_fast_tile_rgb, decode_scan_fast_tile_rgb_region,
16 decode_scan_fast_tile_rgb_region_scaled, fast_tile_region_first_decode_mcu, finish_scan,
17 stripe_region_layout, PreparedComponentPlan, PreparedDecodePlan,
18};
19use crate::entropy::ZIGZAG;
20use crate::error::{JpegError, MarkerKind, Warning};
21use crate::info::{
22 ColorSpace, DecodeOptions, DownscaleFactor, Info, OutputFormat, Rect, RestartIndex,
23 RestartSegment, SofKind,
24};
25use crate::internal::bit_reader::BitReader;
26use crate::internal::checkpoint::{checkpoint_before_mcu, CpuCheckpointCache, DeviceCheckpoint};
27use crate::internal::scratch::{ScratchPool, SinkRows};
28use crate::lossless::{lossless_predict, LosslessSample};
29use crate::output::{
30 validate_buffer, Gray8Writer, InterleavedRgbWriter, OutputWriter, Rgb8Writer, Rgba8Writer,
31};
32use crate::parse::header::{parse_header, parse_info, ParsedHeader};
33use crate::parse::tables::{HuffmanValues, RawHuffmanTable};
34use crate::profile::{duration_us_string, emit_jpeg_profile_row, jpeg_profile_stages_enabled};
35use crate::segment::PreparedJpeg;
36use crate::JpegCodec;
37use alloc::sync::Arc;
38use alloc::vec::Vec;
39use core::cell::RefCell;
40pub use j2k_core::TileBatchOptions;
41use j2k_core::{
42 CompressedPayloadKind, CompressedTransferSyntax, DecodeOutcome as CoreDecodeOutcome,
43 DecodeRowsError, DecoderContext as CoreDecoderContext, Downscale, ImageCodec, ImageDecode,
44 ImageDecodeRows, PassthroughCandidate, PixelFormat, RowSink, TileBatchDecode,
45};
46use std::sync::Mutex;
47use std::time::{Duration, Instant};
48
49const DEFAULT_MAX_DECODE_BYTES: usize = 512 * 1024 * 1024;
50const CPU_ROI_CHECKPOINT_CADENCE_MCUS: u32 = 1024;
51const CPU_ROI_CHECKPOINT_MIN_TARGET_MCUS: u32 = 4096;
52
53std::thread_local! {
54 static DEFAULT_SCRATCH: RefCell<ScratchPool> = RefCell::new(ScratchPool::new());
55 static DEFAULT_CONTEXT: RefCell<DecoderContext> = RefCell::new(DecoderContext::new());
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct DecodeOutcome {
65 pub decoded: Rect,
69 pub warnings: Vec<Warning>,
72}
73
74impl From<DecodeOutcome> for CoreDecodeOutcome<Warning> {
75 fn from(outcome: DecodeOutcome) -> Self {
76 Self {
77 decoded: outcome.decoded.into(),
78 warnings: outcome.warnings,
79 }
80 }
81}
82
83pub type TileDecodeJob<'i, 'o> = j2k_core::TileDecodeJob<'i, 'o>;
85
86pub struct PreparedJpegTileJob<'i, 'o> {
89 pub input: PreparedJpeg<'i>,
91 pub out: &'o mut [u8],
93 pub stride: usize,
95 pub options: DecodeOptions,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct DecodedTile {
102 pub dimensions: (u32, u32),
104 pub decoded: Rect,
106 pub warnings: Vec<Warning>,
108}
109
110pub type TileScaledDecodeJob<'i, 'o> = j2k_core::TileScaledDecodeJob<'i, 'o>;
112
113pub type TileRegionScaledDecodeJob<'i, 'o> = j2k_core::TileRegionScaledDecodeJob<'i, 'o>;
116
117pub type TileBatchError = j2k_core::TileBatchError<JpegError>;
120
121pub trait ComponentRowWriter {
124 fn write_gray_row(&mut self, y: u32, gray_row: &[u8]) -> Result<(), JpegError>;
126
127 fn write_ycbcr_row(
129 &mut self,
130 y: u32,
131 y_row: &[u8],
132 cb_row: &[u8],
133 cr_row: &[u8],
134 ) -> Result<(), JpegError>;
135
136 fn write_rgb_row(
138 &mut self,
139 y: u32,
140 r_row: &[u8],
141 g_row: &[u8],
142 b_row: &[u8],
143 ) -> Result<(), JpegError>;
144}
145
146#[derive(Debug)]
148pub struct JpegView<'a> {
149 bytes: &'a [u8],
150 header: ParsedHeader,
151 info: Info,
152 options: DecodeOptions,
153}
154
155impl<'a> JpegView<'a> {
156 pub fn parse(input: &'a [u8]) -> Result<Self, JpegError> {
158 Self::parse_with_options(input, DecodeOptions::default())
159 }
160
161 pub fn parse_with_options(input: &'a [u8], options: DecodeOptions) -> Result<Self, JpegError> {
163 let header = parse_header(input)?;
164 let mut info = header.info();
165 options.apply_to_info(&mut info);
166 Ok(Self {
167 bytes: input,
168 header,
169 info,
170 options,
171 })
172 }
173
174 pub fn info(&self) -> &Info {
176 &self.info
177 }
178
179 pub fn bytes(&self) -> &'a [u8] {
181 self.bytes
182 }
183
184 pub fn passthrough_candidate(&self) -> Option<PassthroughCandidate<'a>> {
191 jpeg_passthrough_syntax(&self.info).map(|transfer_syntax| {
192 PassthroughCandidate::new(
193 self.bytes,
194 transfer_syntax,
195 CompressedPayloadKind::JpegInterchange,
196 self.info.to_core_info(),
197 )
198 })
199 }
200
201 pub fn restart_index(&self) -> Result<Option<RestartIndex>, JpegError> {
206 restart_index_for_stream(
207 self.bytes,
208 self.header.sos_offset,
209 &self.info,
210 self.info.restart_interval,
211 )
212 }
213
214 pub(crate) fn has_lossless_subsampled_color_capability_shape(&self) -> bool {
215 if self.info.sof_kind != SofKind::Lossless
216 || !matches!(self.info.color_space, ColorSpace::Rgb | ColorSpace::YCbCr)
217 || !matches!(self.info.bit_depth, 8 | 16)
218 || self.info.sampling.len() != 3
219 || !self
220 .info
221 .sampling
222 .components()
223 .iter()
224 .any(|&(h, v)| h != 1 || v != 1)
225 || self.header.scan_count != 1
226 {
227 return false;
228 }
229
230 let Some(scan) = self.header.scan.as_ref() else {
231 return false;
232 };
233 if !(1..=7).contains(&scan.ss)
234 || scan.se != 0
235 || scan.ah != 0
236 || scan.al != 0
237 || scan.components.len() != 3
238 {
239 return false;
240 }
241
242 scan.components.iter().all(|scan_component| {
243 find_component_index(&self.header.component_ids, scan_component.id).is_some()
244 && self.header.huffman_tables.dc[scan_component.dc_table as usize].is_some()
245 })
246 }
247}
248
249#[derive(Debug)]
252pub struct Decoder<'a> {
253 pub(crate) bytes: &'a [u8],
254 pub(crate) info: Info,
255 pub(crate) warnings: Arc<[Warning]>,
256 pub(crate) backend: Backend,
257 pub(crate) plan: PreparedDecodePlan,
258 pub(crate) progressive_plan: Option<PreparedProgressivePlan>,
259 lossless_plan: Option<PreparedLosslessPlan>,
260 pub(crate) cpu_entropy_checkpoints: Mutex<CpuCheckpointCache>,
261}
262
263#[derive(Debug, Clone)]
264struct PreparedLosslessPlan {
265 predictor: u8,
266 bit_depth: u8,
267 dc_table: Arc<HuffmanTable>,
268 dimensions: (u32, u32),
269 scan_offset: usize,
270}
271
272#[derive(Clone, Copy, Debug, Eq, PartialEq)]
273enum LosslessColorSampling {
274 S444,
275 S422,
276 S420,
277}
278
279impl<'a> Decoder<'a> {
280 pub fn inspect(input: &'a [u8]) -> Result<Info, JpegError> {
288 Self::inspect_with_options(input, DecodeOptions::default())
289 }
290
291 pub fn inspect_with_options(
296 input: &'a [u8],
297 options: DecodeOptions,
298 ) -> Result<Info, JpegError> {
299 let mut info = parse_info(input)?;
300 options.apply_to_info(&mut info);
301 Ok(info)
302 }
303
304 pub fn new_with_options(input: &'a [u8], options: DecodeOptions) -> Result<Self, JpegError> {
306 let view = JpegView::parse_with_options(input, options)?;
307 DEFAULT_CONTEXT.with(|ctx| Self::from_view_in_context(view, &mut ctx.borrow_mut()))
308 }
309
310 pub fn new(input: &'a [u8]) -> Result<Self, JpegError> {
322 Self::new_with_options(input, DecodeOptions::default())
323 }
324
325 pub fn from_view(view: JpegView<'a>) -> Result<Self, JpegError> {
327 DEFAULT_CONTEXT.with(|ctx| Self::from_view_in_context(view, &mut ctx.borrow_mut()))
328 }
329
330 pub fn from_view_in_context(
333 view: JpegView<'a>,
334 ctx: &mut DecoderContext,
335 ) -> Result<Self, JpegError> {
336 let JpegView {
337 bytes,
338 header,
339 info,
340 options,
341 } = view;
342 let backend = Backend::detect();
343 let (info, warnings, plan, progressive_plan, lossless_plan) = if matches!(
344 info.sof_kind,
345 SofKind::Progressive8 | SofKind::Progressive12
346 ) {
347 let progressive_plan = Self::build_progressive_plan(&header, &info, ctx)?;
348 let plan = Self::build_progressive_host_output_plan(&header, &info, ctx)?;
349 (
350 info,
351 Arc::<[Warning]>::from(header.warnings.as_slice()),
352 plan,
353 Some(progressive_plan),
354 None,
355 )
356 } else if info.sof_kind == SofKind::Lossless {
357 let (plan, lossless_plan) = Self::build_lossless_plan(&header, &info, ctx)?;
358 (
359 info,
360 Arc::<[Warning]>::from(header.warnings.as_slice()),
361 plan,
362 None,
363 Some(lossless_plan),
364 )
365 } else if options == DecodeOptions::default() {
366 if let Some(scan_offset) = header.sos_offset {
367 let header_prefix = &bytes[..scan_offset];
368 let (info, warnings, plan) = ctx.resolve_decode_plan(header_prefix, |ctx| {
369 let plan = Self::build_prepared_plan(&header, &info, ctx)?;
370 Ok((
371 info.clone(),
372 Arc::<[Warning]>::from(header.warnings.as_slice()),
373 plan,
374 ))
375 })?;
376 (info, warnings, plan, None, None)
377 } else {
378 let plan = Self::build_prepared_plan(&header, &info, ctx)?;
379 (
380 info,
381 Arc::<[Warning]>::from(header.warnings.as_slice()),
382 plan,
383 None,
384 None,
385 )
386 }
387 } else {
388 let plan = Self::build_prepared_plan(&header, &info, ctx)?;
389 (
390 info,
391 Arc::<[Warning]>::from(header.warnings.as_slice()),
392 plan,
393 None,
394 None,
395 )
396 };
397 Ok(Self {
398 bytes,
399 info,
400 warnings,
401 backend,
402 plan,
403 progressive_plan,
404 lossless_plan,
405 cpu_entropy_checkpoints: Mutex::new(CpuCheckpointCache::default()),
406 })
407 }
408
409 fn build_prepared_plan(
410 header: &ParsedHeader,
411 info: &Info,
412 ctx: &mut DecoderContext,
413 ) -> Result<PreparedDecodePlan, JpegError> {
414 match info.sof_kind {
415 SofKind::Baseline8 | SofKind::Extended8 | SofKind::Extended12 => {}
416 other => return Err(JpegError::NotImplemented { sof: other }),
417 }
418 if info.sof_kind == SofKind::Extended12
419 && !matches!(
420 info.color_space,
421 ColorSpace::Grayscale
422 | ColorSpace::YCbCr
423 | ColorSpace::Rgb
424 | ColorSpace::Cmyk
425 | ColorSpace::Ycck
426 )
427 {
428 return Err(JpegError::NotImplemented { sof: info.sof_kind });
429 }
430 match info.color_space {
431 ColorSpace::Grayscale
432 | ColorSpace::YCbCr
433 | ColorSpace::Rgb
434 | ColorSpace::Cmyk
435 | ColorSpace::Ycck => {}
436 }
437
438 let mut dc_tables: [Option<Arc<HuffmanTable>>; 4] = Default::default();
439 let mut ac_tables: [Option<Arc<HuffmanTable>>; 4] = Default::default();
440 let scan = header.scan.as_ref().ok_or(JpegError::MissingMarker {
441 marker: MarkerKind::Sos,
442 })?;
443 if header.scan_count != 1 {
444 return Err(JpegError::InvalidSequentialScanCount {
445 sof: info.sof_kind,
446 count: header.scan_count,
447 });
448 }
449 validate_leading_component_sampling(header, info)?;
450 for (h, v) in header.sampling.iter() {
456 if h == 0 || v == 0 || h > 4 || v > 4 {
457 return Err(JpegError::NotImplemented { sof: info.sof_kind });
458 }
459 if !header.sampling.max_h.is_multiple_of(h) || !header.sampling.max_v.is_multiple_of(v)
460 {
461 return Err(JpegError::NotImplemented { sof: info.sof_kind });
462 }
463 }
464 for comp in &scan.components {
465 let di = comp.dc_table as usize;
466 let ai = comp.ac_table as usize;
467 if dc_tables[di].is_none() {
468 let raw = header.huffman_tables.dc[di].as_ref().ok_or(
469 JpegError::MissingHuffmanTable {
470 component: comp.id,
471 class: 0,
472 id: comp.dc_table,
473 },
474 )?;
475 dc_tables[di] = Some(ctx.resolve_huffman_table(raw)?);
476 }
477 if ac_tables[ai].is_none() {
478 let raw = header.huffman_tables.ac[ai].as_ref().ok_or(
479 JpegError::MissingHuffmanTable {
480 component: comp.id,
481 class: 1,
482 id: comp.ac_table,
483 },
484 )?;
485 ac_tables[ai] = Some(ctx.resolve_huffman_table(raw)?);
486 }
487 }
488
489 build_decode_plan(header, info, &dc_tables, &ac_tables, ctx)
490 }
491
492 fn build_lossless_plan(
493 header: &ParsedHeader,
494 info: &Info,
495 ctx: &mut DecoderContext,
496 ) -> Result<(PreparedDecodePlan, PreparedLosslessPlan), JpegError> {
497 if info.sof_kind != SofKind::Lossless {
498 return Err(JpegError::NotImplemented { sof: info.sof_kind });
499 }
500 if header.scan_count != 1 {
501 return Err(JpegError::NotImplemented { sof: info.sof_kind });
502 }
503 let scan = header.scan.as_ref().ok_or(JpegError::MissingMarker {
504 marker: MarkerKind::Sos,
505 })?;
506 if !(1..=7).contains(&scan.ss) {
507 return Err(JpegError::UnsupportedPredictor { predictor: scan.ss });
508 }
509 if scan.se != 0 || scan.ah != 0 || scan.al != 0 {
510 return Err(JpegError::NotImplemented { sof: info.sof_kind });
511 }
512 let expected_components = match (info.color_space, info.bit_depth) {
513 (ColorSpace::Grayscale, 8 | 16) => 1,
514 (ColorSpace::Rgb, 8 | 16) => 3,
515 (ColorSpace::YCbCr, 8 | 16) => 3,
516 _ => return Err(JpegError::NotImplemented { sof: info.sof_kind }),
517 };
518 if scan.components.len() != expected_components {
519 return Err(JpegError::UnsupportedComponentCount {
520 count: scan.components.len() as u8,
521 });
522 }
523 let empty_raw = RawHuffmanTable {
524 bits: [0; 16],
525 values: HuffmanValues::default(),
526 };
527 let empty_huffman = ctx.resolve_huffman_table(&empty_raw)?;
528 let mut components = Vec::with_capacity(scan.components.len());
529 let mut first_dc_table = None;
530 for scan_component in scan.components.iter().copied() {
531 let component_index = find_component_index(&header.component_ids, scan_component.id)
532 .ok_or(JpegError::UnknownScanComponent {
533 offset: header.sos_offset.unwrap_or_default(),
534 component: scan_component.id,
535 })?;
536 let (h, v) =
537 header
538 .sampling
539 .component(component_index)
540 .ok_or(JpegError::MissingMarker {
541 marker: MarkerKind::Sof,
542 })?;
543 let raw_dc = header.huffman_tables.dc[scan_component.dc_table as usize]
544 .as_ref()
545 .ok_or(JpegError::MissingHuffmanTable {
546 component: scan_component.id,
547 class: 0,
548 id: scan_component.dc_table,
549 })?;
550 let dc_table = ctx.resolve_huffman_table(raw_dc)?;
551 first_dc_table.get_or_insert_with(|| Arc::clone(&dc_table));
552 components.push(PreparedComponentPlan {
553 h,
554 v,
555 output_index: component_index,
556 quant: ctx.resolve_quant_table([1; 64]),
557 dc_table,
558 ac_table: Arc::clone(&empty_huffman),
559 });
560 }
561 if matches!(info.color_space, ColorSpace::Rgb | ColorSpace::YCbCr)
562 && lossless_color_sampling(info).is_none()
563 {
564 return Err(JpegError::NotImplemented { sof: info.sof_kind });
565 }
566 let plan = PreparedDecodePlan {
567 components,
568 sampling: info.sampling,
569 color_space: info.color_space,
570 restart_interval: header.restart_interval,
571 dimensions: info.dimensions,
572 scan_offset: header.sos_offset.ok_or(JpegError::MissingMarker {
573 marker: MarkerKind::Sos,
574 })?,
575 scratch_bytes: compute_lossless_scratch_bytes(info, DEFAULT_MAX_DECODE_BYTES)?,
576 };
577 let lossless = PreparedLosslessPlan {
578 predictor: scan.ss,
579 bit_depth: info.bit_depth,
580 dc_table: first_dc_table.ok_or(JpegError::MissingMarker {
581 marker: MarkerKind::Sos,
582 })?,
583 dimensions: info.dimensions,
584 scan_offset: plan.scan_offset,
585 };
586 Ok((plan, lossless))
587 }
588
589 fn build_progressive_plan(
590 header: &ParsedHeader,
591 info: &Info,
592 ctx: &mut DecoderContext,
593 ) -> Result<PreparedProgressivePlan, JpegError> {
594 if !matches!(
595 info.sof_kind,
596 SofKind::Progressive8 | SofKind::Progressive12
597 ) {
598 return Err(JpegError::NotImplemented { sof: info.sof_kind });
599 }
600 match (info.sof_kind, info.color_space) {
601 (
602 SofKind::Progressive8 | SofKind::Progressive12,
603 ColorSpace::Grayscale | ColorSpace::YCbCr | ColorSpace::Rgb,
604 ) => {}
605 (SofKind::Progressive12, ColorSpace::Cmyk | ColorSpace::Ycck) => {}
606 (_, color_space) => return Err(JpegError::UnsupportedColorSpace { color_space }),
607 }
608 validate_sampling_factors(header, info)?;
609 if header.progressive_scans.is_empty() {
610 return Err(JpegError::MissingMarker {
611 marker: MarkerKind::Sos,
612 });
613 }
614
615 let max_h = u32::from(header.sampling.max_h);
616 let max_v = u32::from(header.sampling.max_v);
617 let mcu_cols = info.dimensions.0.div_ceil(8 * max_h);
618 let mcu_rows = info.dimensions.1.div_ceil(8 * max_v);
619 let mut components = Vec::with_capacity(header.component_ids.len());
620 for (output_index, &id) in header.component_ids.iter().enumerate() {
621 let (h, v) =
622 header
623 .sampling
624 .component(output_index)
625 .ok_or(JpegError::MissingMarker {
626 marker: MarkerKind::Sof,
627 })?;
628 let quant_id =
629 *header
630 .quant_table_ids
631 .get(output_index)
632 .ok_or(JpegError::MissingMarker {
633 marker: MarkerKind::Sof,
634 })? as usize;
635 let quant = *header
636 .quant_tables
637 .entries
638 .get(quant_id)
639 .and_then(|q| q.as_ref())
640 .ok_or(JpegError::MissingQuantTable {
641 component: id,
642 table_id: quant_id as u8,
643 })?;
644 components.push(PreparedProgressiveComponentPlan {
645 h,
646 v,
647 output_index,
648 quant: ctx.resolve_quant_table(quant),
649 block_cols: mcu_cols * u32::from(h),
650 block_rows: mcu_rows * u32::from(v),
651 sample_width: info
652 .dimensions
653 .0
654 .saturating_mul(u32::from(h))
655 .div_ceil(max_h),
656 sample_height: info
657 .dimensions
658 .1
659 .saturating_mul(u32::from(v))
660 .div_ceil(max_v),
661 });
662 }
663
664 let mut scans = Vec::with_capacity(header.progressive_scans.len());
665 for parsed in &header.progressive_scans {
666 let mut scan_components = Vec::with_capacity(parsed.scan.components.len());
667 for component in &parsed.scan.components {
668 let component_index = find_component_index(&header.component_ids, component.id)
669 .ok_or(JpegError::UnknownScanComponent {
670 offset: parsed.entropy_offset,
671 component: component.id,
672 })?;
673 let quant_id = *header.quant_table_ids.get(component_index).ok_or(
674 JpegError::MissingMarker {
675 marker: MarkerKind::Sof,
676 },
677 )?;
678 let _ = parsed
679 .quant_tables
680 .entries
681 .get(quant_id as usize)
682 .and_then(|q| q.as_ref())
683 .ok_or(JpegError::MissingQuantTable {
684 component: component.id,
685 table_id: quant_id,
686 })?;
687 let dc_table = if parsed.scan.ss == 0 {
688 Some(resolve_progressive_huffman(
689 ctx,
690 &parsed.huffman_tables.dc,
691 component.id,
692 0,
693 component.dc_table,
694 )?)
695 } else {
696 None
697 };
698 let ac_table = if parsed.scan.ss > 0 {
699 Some(resolve_progressive_huffman(
700 ctx,
701 &parsed.huffman_tables.ac,
702 component.id,
703 1,
704 component.ac_table,
705 )?)
706 } else {
707 None
708 };
709 scan_components.push(PreparedProgressiveScanComponent {
710 component_index,
711 dc_table,
712 ac_table,
713 });
714 }
715 scans.push(PreparedProgressiveScan {
716 components: scan_components,
717 ss: parsed.scan.ss,
718 se: parsed.scan.se,
719 ah: parsed.scan.ah,
720 al: parsed.scan.al,
721 entropy_offset: parsed.entropy_offset,
722 restart_interval: parsed.restart_interval,
723 });
724 }
725
726 let scratch_bytes =
727 compute_progressive_scratch_bytes(&components, info.dimensions.0 as usize)?;
728 Ok(PreparedProgressivePlan {
729 components,
730 scans,
731 sampling: info.sampling,
732 color_space: info.color_space,
733 dimensions: info.dimensions,
734 mcu_cols,
735 mcu_rows,
736 scratch_bytes,
737 })
738 }
739
740 fn build_progressive_host_output_plan(
741 header: &ParsedHeader,
742 info: &Info,
743 ctx: &mut DecoderContext,
744 ) -> Result<PreparedDecodePlan, JpegError> {
745 let empty_raw = RawHuffmanTable {
746 bits: [0; 16],
747 values: HuffmanValues::default(),
748 };
749 let empty_huffman = ctx.resolve_huffman_table(&empty_raw)?;
750 let mut components = Vec::with_capacity(header.component_ids.len());
751 for (output_index, &id) in header.component_ids.iter().enumerate() {
752 let (h, v) =
753 header
754 .sampling
755 .component(output_index)
756 .ok_or(JpegError::MissingMarker {
757 marker: MarkerKind::Sof,
758 })?;
759 let quant_id =
760 *header
761 .quant_table_ids
762 .get(output_index)
763 .ok_or(JpegError::MissingMarker {
764 marker: MarkerKind::Sof,
765 })? as usize;
766 let quant = *header
767 .quant_tables
768 .entries
769 .get(quant_id)
770 .and_then(|q| q.as_ref())
771 .ok_or(JpegError::MissingQuantTable {
772 component: id,
773 table_id: quant_id as u8,
774 })?;
775 components.push(PreparedComponentPlan {
776 h,
777 v,
778 output_index,
779 quant: ctx.resolve_quant_table(quant),
780 dc_table: Arc::clone(&empty_huffman),
781 ac_table: Arc::clone(&empty_huffman),
782 });
783 }
784 Ok(PreparedDecodePlan {
785 components,
786 sampling: info.sampling,
787 color_space: info.color_space,
788 restart_interval: header.restart_interval,
789 dimensions: info.dimensions,
790 scan_offset: header.sos_offset.ok_or(JpegError::MissingMarker {
791 marker: MarkerKind::Sos,
792 })?,
793 scratch_bytes: compute_decode_scratch_bytes(
794 info.dimensions,
795 info.sampling,
796 DEFAULT_MAX_DECODE_BYTES,
797 )?,
798 })
799 }
800
801 pub fn info(&self) -> &Info {
803 &self.info
804 }
805
806 pub fn passthrough_candidate(&self) -> Option<PassthroughCandidate<'a>> {
808 jpeg_passthrough_syntax(&self.info).map(|transfer_syntax| {
809 PassthroughCandidate::new(
810 self.bytes,
811 transfer_syntax,
812 CompressedPayloadKind::JpegInterchange,
813 self.info.to_core_info(),
814 )
815 })
816 }
817
818 pub fn restart_index(&self) -> Result<Option<RestartIndex>, JpegError> {
823 restart_index_for_stream(
824 self.bytes,
825 Some(self.plan.scan_offset),
826 &self.info,
827 self.plan.restart_interval,
828 )
829 }
830
831 pub fn decode_into(
840 &self,
841 out: &mut [u8],
842 stride: usize,
843 fmt: PixelFormat,
844 ) -> Result<DecodeOutcome, JpegError> {
845 DEFAULT_SCRATCH
846 .with(|pool| self.decode_into_with_scratch(&mut pool.borrow_mut(), out, stride, fmt))
847 }
848
849 pub fn decode(&self, fmt: PixelFormat) -> Result<(Vec<u8>, DecodeOutcome), JpegError> {
856 DEFAULT_SCRATCH.with(|pool| self.decode_with_scratch(&mut pool.borrow_mut(), fmt))
857 }
858
859 pub fn decode_scaled_into(
862 &self,
863 out: &mut [u8],
864 stride: usize,
865 fmt: PixelFormat,
866 scale: Downscale,
867 ) -> Result<DecodeOutcome, JpegError> {
868 DEFAULT_SCRATCH.with(|pool| {
869 self.decode_scaled_into_with_scratch(&mut pool.borrow_mut(), out, stride, fmt, scale)
870 })
871 }
872
873 pub fn decode_scaled(
876 &self,
877 fmt: PixelFormat,
878 scale: Downscale,
879 ) -> Result<(Vec<u8>, DecodeOutcome), JpegError> {
880 DEFAULT_SCRATCH
881 .with(|pool| self.decode_scaled_with_scratch(&mut pool.borrow_mut(), fmt, scale))
882 }
883
884 pub fn decode_with_scratch(
886 &self,
887 pool: &mut ScratchPool,
888 fmt: PixelFormat,
889 ) -> Result<(Vec<u8>, DecodeOutcome), JpegError> {
890 self.decode_scaled_with_scratch(pool, fmt, Downscale::None)
891 }
892
893 pub fn decode_scaled_with_scratch(
895 &self,
896 pool: &mut ScratchPool,
897 fmt: PixelFormat,
898 scale: Downscale,
899 ) -> Result<(Vec<u8>, DecodeOutcome), JpegError> {
900 let legacy = output_format_from_parts(self.info.sof_kind, fmt, scale)?;
901 let downscale = legacy.downscale();
902 let (width, height) = scaled_dimensions(self.info.dimensions, downscale);
903 let (stride, len) = checked_output_geometry(width, height, legacy.bytes_per_pixel())?;
904 let mut out = allocate_output_buffer(len);
905 let outcome = self.decode_scaled_into_with_scratch(pool, &mut out, stride, fmt, scale)?;
906 Ok((out, outcome))
907 }
908
909 pub fn decode_into_with_scratch(
919 &self,
920 pool: &mut ScratchPool,
921 out: &mut [u8],
922 stride: usize,
923 fmt: PixelFormat,
924 ) -> Result<DecodeOutcome, JpegError> {
925 self.decode_scaled_into_with_scratch(pool, out, stride, fmt, Downscale::None)
926 }
927
928 fn decode_into_output_format_with_scratch(
929 &self,
930 pool: &mut ScratchPool,
931 out: &mut [u8],
932 stride: usize,
933 fmt: OutputFormat,
934 ) -> Result<DecodeOutcome, JpegError> {
935 let profile_enabled = jpeg_profile_stages_enabled();
936 let total_start = profile_enabled.then(Instant::now);
937 let downscale = fmt.downscale();
938 let (w, h) = scaled_dimensions(self.info.dimensions, downscale);
939 let scratch_bytes = self.decode_scratch_bytes(DEFAULT_MAX_DECODE_BYTES)?;
940 let bpp = fmt.bytes_per_pixel();
941 validate_buffer(out, stride, w, h, bpp)?;
942 let decode_start = profile_enabled.then(Instant::now);
943 let result = match fmt {
944 OutputFormat::Rgb8 | OutputFormat::Rgb8Scaled { .. } => {
945 if self.lossless_plan.is_some() {
946 return match self.info.color_space {
947 ColorSpace::YCbCr => self.decode_lossless_ycbcr8_region_scaled_into(
948 out,
949 stride,
950 Rect::full(self.info.dimensions),
951 downscale,
952 ),
953 _ => self.decode_lossless_rgb8_region_scaled_into(
954 out,
955 stride,
956 Rect::full(self.info.dimensions),
957 downscale,
958 ),
959 };
960 }
961 let mut writer = Rgb8Writer::new_with_backend(out, stride, w, self.backend);
962 self.decode_rgb_with_writer(
963 pool,
964 &mut writer,
965 downscale,
966 Rect::full(self.info.dimensions),
967 )
968 }
969 OutputFormat::Rgba8 { alpha } | OutputFormat::Rgba8Scaled { alpha, .. } => {
970 if self.lossless_plan.is_some() {
971 return self.decode_lossless_rgba8_region_into(
972 out,
973 stride,
974 Rect::full(self.info.dimensions),
975 downscale,
976 alpha,
977 );
978 }
979 let mut writer = Rgba8Writer::new_with_backend(out, stride, w, alpha, self.backend);
980 self.decode_with_writer(
981 pool,
982 &mut writer,
983 downscale,
984 Rect::full(self.info.dimensions),
985 )
986 }
987 OutputFormat::Gray8 | OutputFormat::Gray8Scaled { .. } => {
988 if self.lossless_plan.is_some() {
989 return self.decode_lossless_gray8_region_scaled_into(
990 out,
991 stride,
992 Rect::full(self.info.dimensions),
993 downscale,
994 );
995 }
996 let mut writer = Gray8Writer::new(out, stride, w);
997 self.decode_with_writer(
998 pool,
999 &mut writer,
1000 downscale,
1001 Rect::full(self.info.dimensions),
1002 )
1003 }
1004 OutputFormat::Gray16 => {
1005 if self.lossless_plan.is_some() {
1006 return self.decode_lossless_gray16_region_scaled_into(
1007 out,
1008 stride,
1009 Rect::full(self.info.dimensions),
1010 downscale,
1011 );
1012 }
1013 if self.info.sof_kind == SofKind::Progressive12 {
1014 return self.decode_progressive12_gray16_region_scaled_into(
1015 out,
1016 stride,
1017 Rect::full(self.info.dimensions),
1018 downscale,
1019 );
1020 }
1021 self.decode_extended12_gray16_into(out, stride)
1022 }
1023 OutputFormat::Gray16Scaled { .. } => {
1024 if self.lossless_plan.is_some() {
1025 return self.decode_lossless_gray16_region_scaled_into(
1026 out,
1027 stride,
1028 Rect::full(self.info.dimensions),
1029 downscale,
1030 );
1031 }
1032 if self.info.sof_kind == SofKind::Progressive12 {
1033 return self.decode_progressive12_gray16_region_scaled_into(
1034 out,
1035 stride,
1036 Rect::full(self.info.dimensions),
1037 downscale,
1038 );
1039 }
1040 self.decode_extended12_gray16_region_scaled_into(
1041 out,
1042 stride,
1043 Rect::full(self.info.dimensions),
1044 downscale,
1045 )
1046 }
1047 OutputFormat::Rgb16 => {
1048 if self.lossless_plan.is_some() {
1049 return match self.info.color_space {
1050 ColorSpace::YCbCr => self.decode_lossless_ycbcr16_region_scaled_into(
1051 out,
1052 stride,
1053 Rect::full(self.info.dimensions),
1054 downscale,
1055 ),
1056 _ => self.decode_lossless_rgb16_region_scaled_into(
1057 out,
1058 stride,
1059 Rect::full(self.info.dimensions),
1060 downscale,
1061 ),
1062 };
1063 }
1064 if self.info.sof_kind == SofKind::Progressive12 {
1065 return self.decode_progressive12_rgb16_region_scaled_into(
1066 out,
1067 stride,
1068 Rect::full(self.info.dimensions),
1069 downscale,
1070 );
1071 }
1072 self.decode_extended12_rgb16_into(out, stride)
1073 }
1074 OutputFormat::Rgb16Scaled { .. } => {
1075 if self.lossless_plan.is_some() {
1076 return match self.info.color_space {
1077 ColorSpace::YCbCr => self.decode_lossless_ycbcr16_region_scaled_into(
1078 out,
1079 stride,
1080 Rect::full(self.info.dimensions),
1081 downscale,
1082 ),
1083 _ => self.decode_lossless_rgb16_region_scaled_into(
1084 out,
1085 stride,
1086 Rect::full(self.info.dimensions),
1087 downscale,
1088 ),
1089 };
1090 }
1091 if self.info.sof_kind == SofKind::Progressive12 {
1092 return self.decode_progressive12_rgb16_region_scaled_into(
1093 out,
1094 stride,
1095 Rect::full(self.info.dimensions),
1096 downscale,
1097 );
1098 }
1099 self.decode_extended12_rgb16_region_scaled_into(
1100 out,
1101 stride,
1102 Rect::full(self.info.dimensions),
1103 downscale,
1104 )
1105 }
1106 OutputFormat::Rgba16 { alpha } | OutputFormat::Rgba16Scaled { alpha, .. } => {
1107 if self.lossless_plan.is_some() {
1108 return self.decode_lossless_rgba16_region_scaled_into(
1109 out,
1110 stride,
1111 Rect::full(self.info.dimensions),
1112 downscale,
1113 alpha,
1114 );
1115 }
1116 if matches!(
1117 self.info.sof_kind,
1118 SofKind::Extended12 | SofKind::Progressive12
1119 ) {
1120 return self.decode_12bit_rgba16_region_scaled_into(
1121 out,
1122 stride,
1123 Rect::full(self.info.dimensions),
1124 downscale,
1125 alpha,
1126 );
1127 }
1128 Err(JpegError::NotImplemented {
1129 sof: self.info.sof_kind,
1130 })
1131 }
1132 };
1133 if let (Some(total_start), Some(decode_start), Ok(outcome)) =
1134 (total_start, decode_start, &result)
1135 {
1136 let source_width_s = self.info.dimensions.0.to_string();
1137 let source_height_s = self.info.dimensions.1.to_string();
1138 let output_width_s = w.to_string();
1139 let output_height_s = h.to_string();
1140 let stride_s = stride.to_string();
1141 let bpp_s = bpp.to_string();
1142 let output_bytes_s = stride.saturating_mul(h as usize).to_string();
1143 let scratch_bytes_s = scratch_bytes.to_string();
1144 let warning_count_s = outcome.warnings.len().to_string();
1145 let decode_us = duration_us_string(decode_start.elapsed());
1146 let total_us = duration_us_string(total_start.elapsed());
1147 emit_jpeg_profile_row(
1148 "decode",
1149 "cpu",
1150 &[
1151 ("mode", "full"),
1152 ("fmt", output_format_profile_name(fmt)),
1153 ("downscale", downscale_profile_name(downscale)),
1154 ("source_width", source_width_s.as_str()),
1155 ("source_height", source_height_s.as_str()),
1156 ("output_width", output_width_s.as_str()),
1157 ("output_height", output_height_s.as_str()),
1158 ("stride", stride_s.as_str()),
1159 ("bpp", bpp_s.as_str()),
1160 ("scratch_bytes", scratch_bytes_s.as_str()),
1161 ("output_bytes", output_bytes_s.as_str()),
1162 ("decode_us", decode_us.as_str()),
1163 ("total_us", total_us.as_str()),
1164 ("warnings", warning_count_s.as_str()),
1165 ],
1166 );
1167 }
1168 result
1169 }
1170
1171 pub fn decode_scaled_into_with_scratch(
1173 &self,
1174 pool: &mut ScratchPool,
1175 out: &mut [u8],
1176 stride: usize,
1177 fmt: PixelFormat,
1178 scale: Downscale,
1179 ) -> Result<DecodeOutcome, JpegError> {
1180 self.decode_into_output_format_with_scratch(
1181 pool,
1182 out,
1183 stride,
1184 output_format_from_parts(self.info.sof_kind, fmt, scale)?,
1185 )
1186 }
1187
1188 pub fn decode_rows<S>(&self, sink: &mut S) -> Result<DecodeOutcome, JpegError>
1194 where
1195 S: RowSink<u8, Error = JpegError>,
1196 {
1197 DEFAULT_SCRATCH.with(|pool| self.decode_rows_with_scratch(&mut pool.borrow_mut(), sink))
1198 }
1199
1200 pub fn decode_rows_with_scratch<S>(
1203 &self,
1204 pool: &mut ScratchPool,
1205 sink: &mut S,
1206 ) -> Result<DecodeOutcome, JpegError>
1207 where
1208 S: RowSink<u8, Error = JpegError>,
1209 {
1210 if self.lossless_plan.is_some() {
1211 return self.decode_lossless_rows_with_scratch(pool, sink);
1212 }
1213 let width = self.info.dimensions.0 as usize;
1214 let rows = pool.take_sink_rows(width);
1215 let mut writer = SinkWriter::new(sink, rows, self.backend);
1216 let result = self.decode_rgb_with_writer(
1217 pool,
1218 &mut writer,
1219 DownscaleFactor::Full,
1220 Rect::full(self.info.dimensions),
1221 );
1222 pool.restore_sink_rows(writer.into_rows());
1223 result
1224 }
1225
1226 fn decode_lossless_rows_with_scratch<S>(
1227 &self,
1228 pool: &mut ScratchPool,
1229 sink: &mut S,
1230 ) -> Result<DecodeOutcome, JpegError>
1231 where
1232 S: RowSink<u8, Error = JpegError>,
1233 {
1234 let plan = self
1235 .lossless_plan
1236 .as_ref()
1237 .ok_or(JpegError::NotImplemented {
1238 sof: self.info.sof_kind,
1239 })?;
1240 if !(1..=7).contains(&plan.predictor) {
1241 return Err(JpegError::UnsupportedPredictor {
1242 predictor: plan.predictor,
1243 });
1244 }
1245
1246 let width = self.info.dimensions.0 as usize;
1247 match (self.info.color_space, plan.bit_depth) {
1248 (ColorSpace::Grayscale, 8) => {
1249 let mut rows = pool.take_sink_rows(width);
1250 let result = self.decode_lossless_gray8_rows(
1251 sink,
1252 &mut pool.lossless_prev_row,
1253 &mut pool.lossless_curr_row,
1254 &mut rows.top_row,
1255 );
1256 pool.restore_sink_rows(rows);
1257 result
1258 }
1259 (ColorSpace::Grayscale, 16) => self.decode_lossless_gray16_rows(
1260 sink,
1261 &mut pool.lossless_prev_row,
1262 &mut pool.lossless_curr_row,
1263 ),
1264 (ColorSpace::Rgb, 8) => self.decode_lossless_color8_rows(
1265 sink,
1266 &mut pool.lossless_prev_row,
1267 &mut pool.lossless_curr_row,
1268 None,
1269 ColorSpace::Rgb,
1270 ),
1271 (ColorSpace::YCbCr, 8) => {
1272 let mut rows = pool.take_sink_rows(width);
1273 let result = self.decode_lossless_color8_rows(
1274 sink,
1275 &mut pool.lossless_prev_row,
1276 &mut pool.lossless_curr_row,
1277 Some(&mut rows.top_row),
1278 ColorSpace::YCbCr,
1279 );
1280 pool.restore_sink_rows(rows);
1281 result
1282 }
1283 (ColorSpace::Rgb, 16) => self.decode_lossless_color16_rows(
1284 sink,
1285 &mut pool.lossless_prev_row,
1286 &mut pool.lossless_curr_row,
1287 None,
1288 ColorSpace::Rgb,
1289 ),
1290 (ColorSpace::YCbCr, 16) => {
1291 let mut rows = pool.take_sink_rows(width);
1292 rows.top_row.resize(width.saturating_mul(6), 0);
1293 let result = self.decode_lossless_color16_rows(
1294 sink,
1295 &mut pool.lossless_prev_row,
1296 &mut pool.lossless_curr_row,
1297 Some(&mut rows.top_row),
1298 ColorSpace::YCbCr,
1299 );
1300 pool.restore_sink_rows(rows);
1301 result
1302 }
1303 (_, depth) if depth != 8 && depth != 16 => {
1304 Err(JpegError::UnsupportedBitDepth { depth })
1305 }
1306 _ => Err(JpegError::NotImplemented {
1307 sof: self.info.sof_kind,
1308 }),
1309 }
1310 }
1311
1312 pub fn decode_component_rows_with_scratch<W>(
1314 &self,
1315 pool: &mut ScratchPool,
1316 writer: &mut W,
1317 ) -> Result<DecodeOutcome, JpegError>
1318 where
1319 W: ComponentRowWriter,
1320 {
1321 self.decode_region_component_rows_with_scratch(
1322 pool,
1323 writer,
1324 Rect::full(self.info.dimensions),
1325 Downscale::None,
1326 )
1327 }
1328
1329 pub fn decode_region_component_rows_with_scratch<W>(
1331 &self,
1332 pool: &mut ScratchPool,
1333 writer: &mut W,
1334 roi: Rect,
1335 scale: Downscale,
1336 ) -> Result<DecodeOutcome, JpegError>
1337 where
1338 W: ComponentRowWriter,
1339 {
1340 if !roi.is_within(self.info.dimensions) {
1341 return Err(JpegError::RectOutOfBounds {
1342 rect: roi,
1343 width: self.info.dimensions.0,
1344 height: self.info.dimensions.1,
1345 });
1346 }
1347
1348 let downscale = jpeg_downscale(scale);
1349 let scaled_roi = scaled_rect_covering(roi, downscale)?;
1350 let mut adapter = ComponentWriterAdapter { inner: writer };
1351
1352 if roi == Rect::full(self.info.dimensions) {
1353 self.decode_with_writer(pool, &mut adapter, downscale, roi)
1354 } else {
1355 let (source_x0, source_width) =
1356 self.source_window_for_output_rect(downscale, scaled_roi);
1357 let mut cropped = CroppedWriter::new(adapter, scaled_roi, source_x0, source_width);
1358 self.decode_with_writer(pool, &mut cropped, downscale, roi)
1359 }
1360 }
1361
1362 pub fn decode_region_into(
1368 &self,
1369 out: &mut [u8],
1370 stride: usize,
1371 fmt: PixelFormat,
1372 roi: Rect,
1373 ) -> Result<DecodeOutcome, JpegError> {
1374 DEFAULT_SCRATCH.with(|pool| {
1375 self.decode_region_into_with_scratch(&mut pool.borrow_mut(), out, stride, fmt, roi)
1376 })
1377 }
1378
1379 pub fn decode_region(
1381 &self,
1382 fmt: PixelFormat,
1383 roi: Rect,
1384 ) -> Result<(Vec<u8>, DecodeOutcome), JpegError> {
1385 DEFAULT_SCRATCH
1386 .with(|pool| self.decode_region_with_scratch(&mut pool.borrow_mut(), fmt, roi))
1387 }
1388
1389 pub fn decode_region_scaled(
1392 &self,
1393 fmt: PixelFormat,
1394 roi: Rect,
1395 scale: Downscale,
1396 ) -> Result<(Vec<u8>, DecodeOutcome), JpegError> {
1397 DEFAULT_SCRATCH.with(|pool| {
1398 self.decode_region_scaled_with_scratch(&mut pool.borrow_mut(), fmt, roi, scale)
1399 })
1400 }
1401
1402 pub fn decode_region_with_scratch(
1404 &self,
1405 pool: &mut ScratchPool,
1406 fmt: PixelFormat,
1407 roi: Rect,
1408 ) -> Result<(Vec<u8>, DecodeOutcome), JpegError> {
1409 self.decode_region_scaled_with_scratch(pool, fmt, roi, Downscale::None)
1410 }
1411
1412 pub fn decode_region_scaled_with_scratch(
1414 &self,
1415 pool: &mut ScratchPool,
1416 fmt: PixelFormat,
1417 roi: Rect,
1418 scale: Downscale,
1419 ) -> Result<(Vec<u8>, DecodeOutcome), JpegError> {
1420 let legacy = output_format_from_parts(self.info.sof_kind, fmt, scale)?;
1421 let scaled_roi = scaled_rect_covering(roi, legacy.downscale())?;
1422 let (stride, len) =
1423 checked_output_geometry(scaled_roi.w, scaled_roi.h, legacy.bytes_per_pixel())?;
1424 let mut out = allocate_output_buffer(len);
1425 let outcome =
1426 self.decode_region_scaled_into_with_scratch(pool, &mut out, stride, fmt, roi, scale)?;
1427 Ok((out, outcome))
1428 }
1429
1430 pub fn decode_region_into_with_scratch(
1432 &self,
1433 pool: &mut ScratchPool,
1434 out: &mut [u8],
1435 stride: usize,
1436 fmt: PixelFormat,
1437 roi: Rect,
1438 ) -> Result<DecodeOutcome, JpegError> {
1439 self.decode_region_scaled_into_with_scratch(pool, out, stride, fmt, roi, Downscale::None)
1440 }
1441
1442 fn decode_region_into_output_format_with_scratch(
1443 &self,
1444 pool: &mut ScratchPool,
1445 out: &mut [u8],
1446 stride: usize,
1447 fmt: OutputFormat,
1448 roi: Rect,
1449 ) -> Result<DecodeOutcome, JpegError> {
1450 let profile_enabled = jpeg_profile_stages_enabled();
1451 let total_start = profile_enabled.then(Instant::now);
1452 if !roi.is_within(self.info.dimensions) {
1453 return Err(JpegError::RectOutOfBounds {
1454 rect: roi,
1455 width: self.info.dimensions.0,
1456 height: self.info.dimensions.1,
1457 });
1458 }
1459
1460 if roi == Rect::full(self.info.dimensions) {
1461 return self.decode_into_output_format_with_scratch(pool, out, stride, fmt);
1462 }
1463
1464 let downscale = fmt.downscale();
1465 let scaled_roi = scaled_rect_covering(roi, downscale)?;
1466 let scratch_bytes = self.decode_scratch_bytes(DEFAULT_MAX_DECODE_BYTES)?;
1467 validate_buffer(
1468 out,
1469 stride,
1470 scaled_roi.w,
1471 scaled_roi.h,
1472 fmt.bytes_per_pixel(),
1473 )?;
1474
1475 let decode_start = profile_enabled.then(Instant::now);
1476 let result = match fmt {
1477 OutputFormat::Rgb8 | OutputFormat::Rgb8Scaled { .. } => {
1478 if self.lossless_plan.is_some() {
1479 return match self.info.color_space {
1480 ColorSpace::YCbCr => self
1481 .decode_lossless_ycbcr8_region_scaled_into(out, stride, roi, downscale),
1482 _ => self
1483 .decode_lossless_rgb8_region_scaled_into(out, stride, roi, downscale),
1484 };
1485 }
1486 if fmt == OutputFormat::Rgb8
1487 && downscale == DownscaleFactor::Full
1488 && self.progressive_plan.is_none()
1489 && self.plan.matches_fast_tile_shape()
1490 {
1491 let mut writer =
1492 Rgb8Writer::new_with_backend(out, stride, scaled_roi.w, self.backend);
1493 let scan_bytes = &self.bytes[self.plan.scan_offset..];
1494 let checkpoint = self.checkpoint_for_mcu(
1495 scan_bytes,
1496 fast_tile_region_first_decode_mcu(&self.plan, roi, DownscaleFactor::Full),
1497 )?;
1498 let scan_warnings = decode_scan_fast_tile_rgb_region(
1499 &self.plan,
1500 self.backend,
1501 scan_bytes,
1502 pool,
1503 &mut writer,
1504 roi,
1505 checkpoint.as_ref(),
1506 )?;
1507 Ok(DecodeOutcome {
1508 decoded: roi,
1509 warnings: merged_warnings(&self.warnings, scan_warnings),
1510 })
1511 } else if matches!(fmt, OutputFormat::Rgb8Scaled { .. })
1512 && self.progressive_plan.is_none()
1513 && self.plan.matches_fast_tile_shape()
1514 {
1515 let mut writer =
1516 Rgb8Writer::new_with_backend(out, stride, scaled_roi.w, self.backend);
1517 let scan_bytes = &self.bytes[self.plan.scan_offset..];
1518 let checkpoint = self.checkpoint_for_mcu(
1519 scan_bytes,
1520 fast_tile_region_first_decode_mcu(&self.plan, scaled_roi, downscale),
1521 )?;
1522 let scan_warnings = decode_scan_fast_tile_rgb_region_scaled(
1523 &self.plan,
1524 self.backend,
1525 scan_bytes,
1526 pool,
1527 &mut writer,
1528 scaled_roi,
1529 downscale,
1530 checkpoint.as_ref(),
1531 )?;
1532 Ok(DecodeOutcome {
1533 decoded: scaled_roi,
1534 warnings: merged_warnings(&self.warnings, scan_warnings),
1535 })
1536 } else {
1537 let base =
1538 Rgb8Writer::new_with_backend(out, stride, scaled_roi.w, self.backend);
1539 let (source_x0, source_width) =
1540 self.source_window_for_output_rect(downscale, scaled_roi);
1541 let mut writer = CroppedWriter::new(base, scaled_roi, source_x0, source_width);
1542 self.decode_rgb_with_writer(pool, &mut writer, downscale, roi)
1543 }
1544 }
1545 OutputFormat::Rgba8 { alpha } | OutputFormat::Rgba8Scaled { alpha, .. } => {
1546 if self.lossless_plan.is_some() {
1547 return self
1548 .decode_lossless_rgba8_region_into(out, stride, roi, downscale, alpha);
1549 }
1550 let base =
1551 Rgba8Writer::new_with_backend(out, stride, scaled_roi.w, alpha, self.backend);
1552 let (source_x0, source_width) =
1553 self.source_window_for_output_rect(downscale, scaled_roi);
1554 let mut writer = CroppedWriter::new(base, scaled_roi, source_x0, source_width);
1555 self.decode_with_writer(pool, &mut writer, downscale, roi)
1556 }
1557 OutputFormat::Gray8 | OutputFormat::Gray8Scaled { .. } => {
1558 if self.lossless_plan.is_some() {
1559 return self
1560 .decode_lossless_gray8_region_scaled_into(out, stride, roi, downscale);
1561 }
1562 let base = Gray8Writer::new(out, stride, scaled_roi.w);
1563 let (source_x0, source_width) =
1564 self.source_window_for_output_rect(downscale, scaled_roi);
1565 let mut writer = CroppedWriter::new(base, scaled_roi, source_x0, source_width);
1566 self.decode_with_writer(pool, &mut writer, downscale, roi)
1567 }
1568 OutputFormat::Gray16 => {
1569 if self.lossless_plan.is_some() {
1570 return self
1571 .decode_lossless_gray16_region_scaled_into(out, stride, roi, downscale);
1572 }
1573 if self.info.sof_kind == SofKind::Progressive12 {
1574 return self.decode_progressive12_gray16_region_scaled_into(
1575 out, stride, roi, downscale,
1576 );
1577 }
1578 self.decode_extended12_gray16_region_into(out, stride, roi)
1579 }
1580 OutputFormat::Gray16Scaled { .. } => {
1581 if self.lossless_plan.is_some() {
1582 return self
1583 .decode_lossless_gray16_region_scaled_into(out, stride, roi, downscale);
1584 }
1585 if self.info.sof_kind == SofKind::Progressive12 {
1586 return self.decode_progressive12_gray16_region_scaled_into(
1587 out, stride, roi, downscale,
1588 );
1589 }
1590 self.decode_extended12_gray16_region_scaled_into(out, stride, roi, downscale)
1591 }
1592 OutputFormat::Rgb16 => {
1593 if self.lossless_plan.is_some() {
1594 return match self.info.color_space {
1595 ColorSpace::YCbCr => self.decode_lossless_ycbcr16_region_scaled_into(
1596 out, stride, roi, downscale,
1597 ),
1598 _ => self
1599 .decode_lossless_rgb16_region_scaled_into(out, stride, roi, downscale),
1600 };
1601 }
1602 if self.info.sof_kind == SofKind::Progressive12 {
1603 return self.decode_progressive12_rgb16_region_scaled_into(
1604 out, stride, roi, downscale,
1605 );
1606 }
1607 self.decode_extended12_rgb16_region_into(out, stride, roi)
1608 }
1609 OutputFormat::Rgb16Scaled { .. } => {
1610 if self.lossless_plan.is_some() {
1611 return match self.info.color_space {
1612 ColorSpace::YCbCr => self.decode_lossless_ycbcr16_region_scaled_into(
1613 out, stride, roi, downscale,
1614 ),
1615 _ => self
1616 .decode_lossless_rgb16_region_scaled_into(out, stride, roi, downscale),
1617 };
1618 }
1619 if self.info.sof_kind == SofKind::Progressive12 {
1620 return self.decode_progressive12_rgb16_region_scaled_into(
1621 out, stride, roi, downscale,
1622 );
1623 }
1624 self.decode_extended12_rgb16_region_scaled_into(out, stride, roi, downscale)
1625 }
1626 OutputFormat::Rgba16 { alpha } | OutputFormat::Rgba16Scaled { alpha, .. } => {
1627 if self.lossless_plan.is_some() {
1628 return self.decode_lossless_rgba16_region_scaled_into(
1629 out, stride, roi, downscale, alpha,
1630 );
1631 }
1632 if matches!(
1633 self.info.sof_kind,
1634 SofKind::Extended12 | SofKind::Progressive12
1635 ) {
1636 return self.decode_12bit_rgba16_region_scaled_into(
1637 out, stride, roi, downscale, alpha,
1638 );
1639 }
1640 Err(JpegError::NotImplemented {
1641 sof: self.info.sof_kind,
1642 })
1643 }
1644 };
1645 if let (Some(total_start), Some(decode_start), Ok(outcome)) =
1646 (total_start, decode_start, &result)
1647 {
1648 let source_width_s = self.info.dimensions.0.to_string();
1649 let source_height_s = self.info.dimensions.1.to_string();
1650 let roi_x_s = roi.x.to_string();
1651 let roi_y_s = roi.y.to_string();
1652 let roi_w_s = roi.w.to_string();
1653 let roi_h_s = roi.h.to_string();
1654 let output_width_s = scaled_roi.w.to_string();
1655 let output_height_s = scaled_roi.h.to_string();
1656 let stride_s = stride.to_string();
1657 let bpp_s = fmt.bytes_per_pixel().to_string();
1658 let output_bytes_s = stride.saturating_mul(scaled_roi.h as usize).to_string();
1659 let scratch_bytes_s = scratch_bytes.to_string();
1660 let warning_count_s = outcome.warnings.len().to_string();
1661 let decode_us = duration_us_string(decode_start.elapsed());
1662 let total_us = duration_us_string(total_start.elapsed());
1663 let mode = if downscale == DownscaleFactor::Full {
1664 "region"
1665 } else {
1666 "region_scaled"
1667 };
1668 emit_jpeg_profile_row(
1669 "decode",
1670 "cpu",
1671 &[
1672 ("mode", mode),
1673 ("fmt", output_format_profile_name(fmt)),
1674 ("downscale", downscale_profile_name(downscale)),
1675 ("source_width", source_width_s.as_str()),
1676 ("source_height", source_height_s.as_str()),
1677 ("roi_x", roi_x_s.as_str()),
1678 ("roi_y", roi_y_s.as_str()),
1679 ("roi_w", roi_w_s.as_str()),
1680 ("roi_h", roi_h_s.as_str()),
1681 ("output_width", output_width_s.as_str()),
1682 ("output_height", output_height_s.as_str()),
1683 ("stride", stride_s.as_str()),
1684 ("bpp", bpp_s.as_str()),
1685 ("scratch_bytes", scratch_bytes_s.as_str()),
1686 ("output_bytes", output_bytes_s.as_str()),
1687 ("decode_us", decode_us.as_str()),
1688 ("total_us", total_us.as_str()),
1689 ("warnings", warning_count_s.as_str()),
1690 ],
1691 );
1692 }
1693 result
1694 }
1695
1696 pub fn decode_region_scaled_into(
1699 &self,
1700 out: &mut [u8],
1701 stride: usize,
1702 fmt: PixelFormat,
1703 roi: Rect,
1704 scale: Downscale,
1705 ) -> Result<DecodeOutcome, JpegError> {
1706 DEFAULT_SCRATCH.with(|pool| {
1707 self.decode_region_scaled_into_with_scratch(
1708 &mut pool.borrow_mut(),
1709 out,
1710 stride,
1711 fmt,
1712 roi,
1713 scale,
1714 )
1715 })
1716 }
1717
1718 pub fn decode_region_scaled_into_with_scratch(
1720 &self,
1721 pool: &mut ScratchPool,
1722 out: &mut [u8],
1723 stride: usize,
1724 fmt: PixelFormat,
1725 roi: Rect,
1726 scale: Downscale,
1727 ) -> Result<DecodeOutcome, JpegError> {
1728 self.decode_region_into_output_format_with_scratch(
1729 pool,
1730 out,
1731 stride,
1732 output_format_from_parts(self.info.sof_kind, fmt, scale)?,
1733 roi,
1734 )
1735 }
1736
1737 pub fn decode_rgba8_into_with_alpha(
1739 &self,
1740 out: &mut [u8],
1741 stride: usize,
1742 alpha: u8,
1743 ) -> Result<DecodeOutcome, JpegError> {
1744 DEFAULT_SCRATCH.with(|pool| {
1745 self.decode_rgba8_into_with_alpha_with_scratch(
1746 &mut pool.borrow_mut(),
1747 out,
1748 stride,
1749 alpha,
1750 )
1751 })
1752 }
1753
1754 pub fn decode_rgba8_into_with_alpha_with_scratch(
1756 &self,
1757 pool: &mut ScratchPool,
1758 out: &mut [u8],
1759 stride: usize,
1760 alpha: u8,
1761 ) -> Result<DecodeOutcome, JpegError> {
1762 self.decode_into_output_format_with_scratch(
1763 pool,
1764 out,
1765 stride,
1766 OutputFormat::Rgba8 { alpha },
1767 )
1768 }
1769
1770 pub fn decode_region_rgba8_into_with_alpha(
1772 &self,
1773 out: &mut [u8],
1774 stride: usize,
1775 roi: Rect,
1776 alpha: u8,
1777 ) -> Result<DecodeOutcome, JpegError> {
1778 DEFAULT_SCRATCH.with(|pool| {
1779 self.decode_region_rgba8_into_with_alpha_with_scratch(
1780 &mut pool.borrow_mut(),
1781 out,
1782 stride,
1783 roi,
1784 alpha,
1785 )
1786 })
1787 }
1788
1789 pub fn decode_region_rgba8_into_with_alpha_with_scratch(
1791 &self,
1792 pool: &mut ScratchPool,
1793 out: &mut [u8],
1794 stride: usize,
1795 roi: Rect,
1796 alpha: u8,
1797 ) -> Result<DecodeOutcome, JpegError> {
1798 self.decode_region_into_output_format_with_scratch(
1799 pool,
1800 out,
1801 stride,
1802 OutputFormat::Rgba8 { alpha },
1803 roi,
1804 )
1805 }
1806}
1807
1808pub fn decode_tile_into(
1832 bytes: &[u8],
1833 pool: &mut ScratchPool,
1834 out: &mut [u8],
1835 stride: usize,
1836 fmt: PixelFormat,
1837) -> Result<DecodeOutcome, JpegError> {
1838 DEFAULT_CONTEXT.with(|ctx| {
1839 decode_tile_into_in_context(bytes, &mut ctx.borrow_mut(), pool, out, stride, fmt)
1840 })
1841}
1842
1843pub fn decode_tile_into_in_context(
1847 bytes: &[u8],
1848 ctx: &mut DecoderContext,
1849 pool: &mut ScratchPool,
1850 out: &mut [u8],
1851 stride: usize,
1852 fmt: PixelFormat,
1853) -> Result<DecodeOutcome, JpegError> {
1854 decode_tile_into_in_context_with_options(
1855 bytes,
1856 ctx,
1857 pool,
1858 out,
1859 stride,
1860 fmt,
1861 DecodeOptions::default(),
1862 )
1863}
1864
1865pub fn decode_tile_into_in_context_with_options(
1869 bytes: &[u8],
1870 ctx: &mut DecoderContext,
1871 pool: &mut ScratchPool,
1872 out: &mut [u8],
1873 stride: usize,
1874 fmt: PixelFormat,
1875 options: DecodeOptions,
1876) -> Result<DecodeOutcome, JpegError> {
1877 let dec = Decoder::from_view_in_context(JpegView::parse_with_options(bytes, options)?, ctx)?;
1878 dec.decode_into_with_scratch(pool, out, stride, fmt)
1879}
1880
1881pub(crate) fn decode_prepared_jpeg_tile_rgb8_in_context(
1882 input: &PreparedJpeg<'_>,
1883 ctx: &mut DecoderContext,
1884 pool: &mut ScratchPool,
1885 out: &mut [u8],
1886 stride: usize,
1887 options: DecodeOptions,
1888) -> Result<DecodedTile, JpegError> {
1889 let view = JpegView::parse_with_options(input.as_bytes(), options)?;
1890 let dimensions = view.info().dimensions;
1891 let dec = Decoder::from_view_in_context(view, ctx)?;
1892 let outcome = dec.decode_into_with_scratch(pool, out, stride, PixelFormat::Rgb8)?;
1893 Ok(DecodedTile {
1894 dimensions,
1895 decoded: outcome.decoded,
1896 warnings: outcome.warnings,
1897 })
1898}
1899
1900#[must_use]
1906pub fn decode_prepared_jpeg_tiles_rgb8(
1907 jobs: &mut [PreparedJpegTileJob<'_, '_>],
1908) -> Vec<Result<DecodedTile, JpegError>> {
1909 crate::JpegBatchSession::new_one_shot(TileBatchOptions::default())
1910 .decode_prepared_jpeg_tiles_rgb8(jobs)
1911}
1912
1913pub fn decode_tiles_into(
1924 jobs: &mut [TileDecodeJob<'_, '_>],
1925 fmt: PixelFormat,
1926 options: TileBatchOptions,
1927) -> Result<Vec<DecodeOutcome>, TileBatchError> {
1928 decode_tiles_into_with_options(jobs, fmt, DecodeOptions::default(), options)
1929}
1930
1931pub fn decode_tiles_into_with_options(
1940 jobs: &mut [TileDecodeJob<'_, '_>],
1941 fmt: PixelFormat,
1942 decode_options: DecodeOptions,
1943 options: TileBatchOptions,
1944) -> Result<Vec<DecodeOutcome>, TileBatchError> {
1945 crate::JpegBatchSession::new_one_shot(options).decode_tiles_into_with_options(
1946 jobs,
1947 fmt,
1948 decode_options,
1949 )
1950}
1951
1952pub fn decode_tiles_scaled_into(
1963 jobs: &mut [TileScaledDecodeJob<'_, '_>],
1964 fmt: PixelFormat,
1965 options: TileBatchOptions,
1966) -> Result<Vec<DecodeOutcome>, TileBatchError> {
1967 decode_tiles_scaled_into_with_options(jobs, fmt, DecodeOptions::default(), options)
1968}
1969
1970pub fn decode_tiles_scaled_into_with_options(
1977 jobs: &mut [TileScaledDecodeJob<'_, '_>],
1978 fmt: PixelFormat,
1979 decode_options: DecodeOptions,
1980 options: TileBatchOptions,
1981) -> Result<Vec<DecodeOutcome>, TileBatchError> {
1982 crate::JpegBatchSession::new_one_shot(options).decode_tiles_scaled_into_with_options(
1983 jobs,
1984 fmt,
1985 decode_options,
1986 )
1987}
1988
1989pub fn decode_tiles_region_scaled_into(
2000 jobs: &mut [TileRegionScaledDecodeJob<'_, '_>],
2001 fmt: PixelFormat,
2002 options: TileBatchOptions,
2003) -> Result<Vec<DecodeOutcome>, TileBatchError> {
2004 decode_tiles_region_scaled_into_with_options(jobs, fmt, DecodeOptions::default(), options)
2005}
2006
2007pub fn decode_tiles_region_scaled_into_with_options(
2014 jobs: &mut [TileRegionScaledDecodeJob<'_, '_>],
2015 fmt: PixelFormat,
2016 decode_options: DecodeOptions,
2017 options: TileBatchOptions,
2018) -> Result<Vec<DecodeOutcome>, TileBatchError> {
2019 crate::JpegBatchSession::new_one_shot(options).decode_tiles_region_scaled_into_with_options(
2020 jobs,
2021 fmt,
2022 decode_options,
2023 )
2024}
2025
2026pub fn decode_tile_region_into_in_context(
2030 bytes: &[u8],
2031 ctx: &mut DecoderContext,
2032 pool: &mut ScratchPool,
2033 out: &mut [u8],
2034 stride: usize,
2035 fmt: PixelFormat,
2036 roi: Rect,
2037) -> Result<DecodeOutcome, JpegError> {
2038 decode_tile_region_into_in_context_with_options(
2039 bytes,
2040 ctx,
2041 pool,
2042 out,
2043 stride,
2044 fmt,
2045 roi,
2046 DecodeOptions::default(),
2047 )
2048}
2049
2050#[allow(clippy::too_many_arguments)]
2054pub fn decode_tile_region_into_in_context_with_options(
2055 bytes: &[u8],
2056 ctx: &mut DecoderContext,
2057 pool: &mut ScratchPool,
2058 out: &mut [u8],
2059 stride: usize,
2060 fmt: PixelFormat,
2061 roi: Rect,
2062 options: DecodeOptions,
2063) -> Result<DecodeOutcome, JpegError> {
2064 let dec = Decoder::from_view_in_context(JpegView::parse_with_options(bytes, options)?, ctx)?;
2065 dec.decode_region_into_with_scratch(pool, out, stride, fmt, roi)
2066}
2067
2068pub fn decode_tile_scaled_into_in_context(
2072 bytes: &[u8],
2073 ctx: &mut DecoderContext,
2074 pool: &mut ScratchPool,
2075 out: &mut [u8],
2076 stride: usize,
2077 fmt: PixelFormat,
2078 scale: Downscale,
2079) -> Result<DecodeOutcome, JpegError> {
2080 decode_tile_scaled_into_in_context_with_options(
2081 bytes,
2082 ctx,
2083 pool,
2084 out,
2085 stride,
2086 fmt,
2087 scale,
2088 DecodeOptions::default(),
2089 )
2090}
2091
2092#[allow(clippy::too_many_arguments)]
2096pub fn decode_tile_scaled_into_in_context_with_options(
2097 bytes: &[u8],
2098 ctx: &mut DecoderContext,
2099 pool: &mut ScratchPool,
2100 out: &mut [u8],
2101 stride: usize,
2102 fmt: PixelFormat,
2103 scale: Downscale,
2104 options: DecodeOptions,
2105) -> Result<DecodeOutcome, JpegError> {
2106 let dec = Decoder::from_view_in_context(JpegView::parse_with_options(bytes, options)?, ctx)?;
2107 dec.decode_scaled_into_with_scratch(pool, out, stride, fmt, scale)
2108}
2109
2110#[allow(clippy::too_many_arguments)]
2114pub fn decode_tile_region_scaled_into_in_context(
2115 bytes: &[u8],
2116 ctx: &mut DecoderContext,
2117 pool: &mut ScratchPool,
2118 out: &mut [u8],
2119 stride: usize,
2120 fmt: PixelFormat,
2121 roi: Rect,
2122 scale: Downscale,
2123) -> Result<DecodeOutcome, JpegError> {
2124 decode_tile_region_scaled_into_in_context_with_options(
2125 bytes,
2126 ctx,
2127 pool,
2128 out,
2129 stride,
2130 fmt,
2131 roi,
2132 scale,
2133 DecodeOptions::default(),
2134 )
2135}
2136
2137#[allow(clippy::too_many_arguments)]
2141pub fn decode_tile_region_scaled_into_in_context_with_options(
2142 bytes: &[u8],
2143 ctx: &mut DecoderContext,
2144 pool: &mut ScratchPool,
2145 out: &mut [u8],
2146 stride: usize,
2147 fmt: PixelFormat,
2148 roi: Rect,
2149 scale: Downscale,
2150 options: DecodeOptions,
2151) -> Result<DecodeOutcome, JpegError> {
2152 let dec = Decoder::from_view_in_context(JpegView::parse_with_options(bytes, options)?, ctx)?;
2153 dec.decode_region_scaled_into_with_scratch(pool, out, stride, fmt, roi, scale)
2154}
2155
2156impl Decoder<'_> {
2157 pub fn decode_tile<S>(
2160 bytes: &[u8],
2161 ctx: &mut DecoderContext,
2162 pool: &mut ScratchPool,
2163 sink: &mut S,
2164 ) -> Result<DecodeOutcome, JpegError>
2165 where
2166 S: RowSink<u8, Error = JpegError>,
2167 {
2168 let dec = Decoder::from_view_in_context(JpegView::parse(bytes)?, ctx)?;
2169 dec.decode_rows_with_scratch(pool, sink)
2170 }
2171}
2172
2173impl Decoder<'_> {
2174 fn decode_scratch_bytes(&self, cap: usize) -> Result<usize, JpegError> {
2175 let scratch_bytes = self
2176 .progressive_plan
2177 .as_ref()
2178 .map_or(self.plan.scratch_bytes, |plan| plan.scratch_bytes);
2179 if scratch_bytes > cap {
2180 return Err(JpegError::MemoryCapExceeded {
2181 requested: scratch_bytes,
2182 cap,
2183 });
2184 }
2185 Ok(scratch_bytes)
2186 }
2187
2188 fn checkpoint_for_mcu(
2189 &self,
2190 scan_bytes: &[u8],
2191 target_mcu: u32,
2192 ) -> Result<Option<DeviceCheckpoint>, JpegError> {
2193 if self.plan.restart_interval.is_some() || target_mcu < CPU_ROI_CHECKPOINT_MIN_TARGET_MCUS {
2194 return Ok(None);
2195 }
2196
2197 let mut cache = self
2198 .cpu_entropy_checkpoints
2199 .lock()
2200 .expect("CPU entropy checkpoint cache mutex poisoned");
2201 checkpoint_before_mcu(
2202 &self.plan,
2203 scan_bytes,
2204 CPU_ROI_CHECKPOINT_CADENCE_MCUS,
2205 target_mcu,
2206 &mut cache,
2207 )
2208 }
2209
2210 fn source_window_for_output_rect(
2211 &self,
2212 downscale: DownscaleFactor,
2213 output_rect: Rect,
2214 ) -> (u32, u32) {
2215 if self.progressive_plan.is_some() {
2216 return (0, scaled_dimensions(self.info.dimensions, downscale).0);
2217 }
2218 let layout = stripe_region_layout(&self.plan, downscale, output_rect);
2219 (layout.source_x0, layout.source_width)
2220 }
2221
2222 fn decode_with_writer<W: OutputWriter>(
2223 &self,
2224 pool: &mut ScratchPool,
2225 writer: &mut W,
2226 downscale: DownscaleFactor,
2227 decoded: Rect,
2228 ) -> Result<DecodeOutcome, JpegError> {
2229 let _ = self.decode_scratch_bytes(DEFAULT_MAX_DECODE_BYTES)?;
2230 let profile_enabled = jpeg_profile_stages_enabled();
2231 if let Some(plan) = &self.progressive_plan {
2232 let scan_start = profile_enabled.then(Instant::now);
2233 let scan_warnings = if downscale == DownscaleFactor::Full {
2234 decode_progressive(plan, self.backend, self.bytes, writer)?
2235 } else {
2236 let mut scaled =
2237 ProgressiveDownscaleWriter::new(writer, downscale, self.info.dimensions);
2238 decode_progressive(plan, self.backend, self.bytes, &mut scaled)?
2239 };
2240 if let Some(start) = scan_start {
2241 emit_decode_scan_profile(
2242 "progressive",
2243 self.info.dimensions,
2244 decoded,
2245 downscale,
2246 start.elapsed(),
2247 );
2248 }
2249 return Ok(DecodeOutcome {
2250 decoded,
2251 warnings: merged_warnings(&self.warnings, scan_warnings),
2252 });
2253 }
2254 let output_rect = scaled_rect_covering(decoded, downscale)?;
2255 let scan_bytes = &self.bytes[self.plan.scan_offset..];
2256 let scan_start = profile_enabled.then(Instant::now);
2257 let scan_warnings = decode_scan_baseline(
2258 &self.plan,
2259 self.backend,
2260 scan_bytes,
2261 pool,
2262 writer,
2263 downscale,
2264 output_rect,
2265 )?;
2266 if let Some(start) = scan_start {
2267 emit_decode_scan_profile(
2268 "baseline",
2269 self.info.dimensions,
2270 decoded,
2271 downscale,
2272 start.elapsed(),
2273 );
2274 }
2275 Ok(DecodeOutcome {
2276 decoded,
2277 warnings: merged_warnings(&self.warnings, scan_warnings),
2278 })
2279 }
2280
2281 fn decode_rgb_with_writer<W: OutputWriter + InterleavedRgbWriter>(
2282 &self,
2283 pool: &mut ScratchPool,
2284 writer: &mut W,
2285 downscale: DownscaleFactor,
2286 decoded: Rect,
2287 ) -> Result<DecodeOutcome, JpegError> {
2288 let _ = self.decode_scratch_bytes(DEFAULT_MAX_DECODE_BYTES)?;
2289 let profile_enabled = jpeg_profile_stages_enabled();
2290 if let Some(plan) = &self.progressive_plan {
2291 let scan_start = profile_enabled.then(Instant::now);
2292 let scan_warnings = if downscale == DownscaleFactor::Full {
2293 decode_progressive(plan, self.backend, self.bytes, writer)?
2294 } else {
2295 let mut scaled =
2296 ProgressiveDownscaleWriter::new(writer, downscale, self.info.dimensions);
2297 decode_progressive(plan, self.backend, self.bytes, &mut scaled)?
2298 };
2299 if let Some(start) = scan_start {
2300 emit_decode_scan_profile(
2301 "progressive_rgb",
2302 self.info.dimensions,
2303 decoded,
2304 downscale,
2305 start.elapsed(),
2306 );
2307 }
2308 return Ok(DecodeOutcome {
2309 decoded,
2310 warnings: merged_warnings(&self.warnings, scan_warnings),
2311 });
2312 }
2313 let output_rect = scaled_rect_covering(decoded, downscale)?;
2314 let scan_bytes = &self.bytes[self.plan.scan_offset..];
2315 let scan_start = profile_enabled.then(Instant::now);
2316 let (scan_path, scan_warnings) =
2317 if downscale == DownscaleFactor::Full && self.plan.matches_fast_tile_shape() {
2318 (
2319 "fast420_rgb",
2320 decode_scan_fast_tile_rgb(&self.plan, self.backend, scan_bytes, pool, writer)?,
2321 )
2322 } else if downscale == DownscaleFactor::Full
2323 && decoded == Rect::full(self.info.dimensions)
2324 && self.plan.matches_fast_rgb444_shape()
2325 {
2326 (
2327 "fast444_rgb",
2328 decode_scan_fast_rgb_444(&self.plan, self.backend, scan_bytes, pool, writer)?,
2329 )
2330 } else {
2331 (
2332 "baseline_rgb",
2333 decode_scan_baseline_rgb(
2334 &self.plan,
2335 self.backend,
2336 scan_bytes,
2337 pool,
2338 writer,
2339 downscale,
2340 output_rect,
2341 )?,
2342 )
2343 };
2344 if let Some(start) = scan_start {
2345 emit_decode_scan_profile(
2346 scan_path,
2347 self.info.dimensions,
2348 decoded,
2349 downscale,
2350 start.elapsed(),
2351 );
2352 }
2353 Ok(DecodeOutcome {
2354 decoded,
2355 warnings: merged_warnings(&self.warnings, scan_warnings),
2356 })
2357 }
2358
2359 fn decode_lossless_gray8_into(
2360 &self,
2361 out: &mut [u8],
2362 stride: usize,
2363 ) -> Result<DecodeOutcome, JpegError> {
2364 let plan = self
2365 .lossless_plan
2366 .as_ref()
2367 .ok_or(JpegError::NotImplemented {
2368 sof: self.info.sof_kind,
2369 })?;
2370 if plan.bit_depth != 8 {
2371 return Err(JpegError::UnsupportedBitDepth {
2372 depth: plan.bit_depth,
2373 });
2374 }
2375 if !(1..=7).contains(&plan.predictor) {
2376 return Err(JpegError::UnsupportedPredictor {
2377 predictor: plan.predictor,
2378 });
2379 }
2380
2381 let (width, height) = plan.dimensions;
2382 let scan_bytes = &self.bytes[plan.scan_offset..];
2383 let mut br = BitReader::new(scan_bytes);
2384 let restart = u32::from(self.plan.restart_interval.unwrap_or(0));
2385 let total_samples = width.saturating_mul(height);
2386 let mut samples_since_restart = 0u32;
2387 let mut expected_rst = 0u8;
2388 for y in 0..height as usize {
2389 for x in 0..width as usize {
2390 let sample_index = y as u32 * width + x as u32;
2391 if restart > 0 && samples_since_restart == restart {
2392 consume_lossless_restart(
2393 &mut br,
2394 sample_index,
2395 total_samples,
2396 &mut expected_rst,
2397 )?;
2398 samples_since_restart = 0;
2399 }
2400 let predictor = if restart > 0 && samples_since_restart == 0 {
2401 128
2402 } else {
2403 lossless_predictor_value(plan.predictor, out, stride, x, y)
2404 };
2405 let diff = plan.dc_table.decode_fast_dc(&mut br)?;
2406 let sample = <u8 as LosslessSample>::from_i32(predictor + diff)?;
2407 out[y * stride + x] = sample;
2408 samples_since_restart += 1;
2409 }
2410 }
2411
2412 let scan_warnings = finish_scan(&mut br, true)?;
2413 Ok(DecodeOutcome {
2414 decoded: Rect::full(self.info.dimensions),
2415 warnings: merged_warnings(&self.warnings, scan_warnings),
2416 })
2417 }
2418
2419 fn decode_lossless_gray8_region_scaled_into(
2420 &self,
2421 out: &mut [u8],
2422 stride: usize,
2423 roi: Rect,
2424 downscale: DownscaleFactor,
2425 ) -> Result<DecodeOutcome, JpegError> {
2426 if roi == Rect::full(self.info.dimensions) && downscale == DownscaleFactor::Full {
2427 return self.decode_lossless_gray8_into(out, stride);
2428 }
2429
2430 let (width, height) = self.info.dimensions;
2431 let full_stride = width as usize;
2432 let mut full =
2433 allocate_output_buffer(checked_scratch_len(&[full_stride, height as usize])?);
2434 let mut outcome = self.decode_lossless_gray8_into(&mut full, full_stride)?;
2435 let output_rect = scaled_rect_covering(roi, downscale)?;
2436 copy_gray8_scaled_rect(
2437 &full,
2438 (width, height),
2439 output_rect,
2440 downscale.denominator(),
2441 out,
2442 stride,
2443 );
2444 outcome.decoded = roi;
2445 Ok(outcome)
2446 }
2447
2448 fn decode_lossless_gray_rows<P, S>(
2449 &self,
2450 sink: &mut S,
2451 prev_row: &mut Vec<u8>,
2452 curr_row: &mut Vec<u8>,
2453 mut emit_row: impl FnMut(&mut S, u32, &[u8]) -> Result<(), JpegError>,
2454 ) -> Result<DecodeOutcome, JpegError>
2455 where
2456 P: LosslessSample,
2457 S: RowSink<u8, Error = JpegError>,
2458 {
2459 let plan = self
2460 .lossless_plan
2461 .as_ref()
2462 .ok_or(JpegError::NotImplemented {
2463 sof: self.info.sof_kind,
2464 })?;
2465 if plan.bit_depth != P::BIT_DEPTH {
2466 return Err(JpegError::UnsupportedBitDepth {
2467 depth: plan.bit_depth,
2468 });
2469 }
2470 if self.info.color_space != ColorSpace::Grayscale {
2471 return Err(JpegError::NotImplemented {
2472 sof: self.info.sof_kind,
2473 });
2474 }
2475 if !(1..=7).contains(&plan.predictor) {
2476 return Err(JpegError::UnsupportedPredictor {
2477 predictor: plan.predictor,
2478 });
2479 }
2480
2481 let (width, height) = plan.dimensions;
2482 let width = width as usize;
2483 let row_len = width.saturating_mul(P::BYTES);
2484 prev_row.resize(row_len, 0);
2485 curr_row.resize(row_len, 0);
2486
2487 let scan_bytes = &self.bytes[plan.scan_offset..];
2488 let mut br = BitReader::new(scan_bytes);
2489 let restart = u32::from(self.plan.restart_interval.unwrap_or(0));
2490 let total_samples = plan.dimensions.0.saturating_mul(height);
2491 let mut samples_since_restart = 0u32;
2492 let mut expected_rst = 0u8;
2493 for y in 0..height as usize {
2494 for x in 0..width {
2495 let sample_index = y as u32 * plan.dimensions.0 + x as u32;
2496 if restart > 0 && samples_since_restart == restart {
2497 consume_lossless_restart(
2498 &mut br,
2499 sample_index,
2500 total_samples,
2501 &mut expected_rst,
2502 )?;
2503 samples_since_restart = 0;
2504 }
2505 let predictor = if restart > 0 && samples_since_restart == 0 {
2506 P::RESTART_PREDICTOR
2507 } else {
2508 lossless_predictor_gray_rows::<P>(plan.predictor, curr_row, prev_row, x, y)
2509 };
2510 let diff = plan.dc_table.decode_fast_dc(&mut br)?;
2511 let sample = P::from_i32(predictor + diff)?;
2512 sample.write_le(&mut curr_row[x * P::BYTES..]);
2513 samples_since_restart += 1;
2514 }
2515 emit_row(sink, y as u32, &curr_row[..row_len])?;
2516 core::mem::swap(prev_row, curr_row);
2517 }
2518
2519 let scan_warnings = finish_scan(&mut br, true)?;
2520 Ok(DecodeOutcome {
2521 decoded: Rect::full(self.info.dimensions),
2522 warnings: merged_warnings(&self.warnings, scan_warnings),
2523 })
2524 }
2525
2526 fn decode_lossless_gray8_rows<S>(
2527 &self,
2528 sink: &mut S,
2529 prev_row: &mut Vec<u8>,
2530 curr_row: &mut Vec<u8>,
2531 rgb_row: &mut [u8],
2532 ) -> Result<DecodeOutcome, JpegError>
2533 where
2534 S: RowSink<u8, Error = JpegError>,
2535 {
2536 self.decode_lossless_gray_rows::<u8, S>(sink, prev_row, curr_row, |sink, y, gray_row| {
2537 let rgb_len = gray_row.len().saturating_mul(3);
2538 if rgb_row.len() < rgb_len {
2539 return Err(JpegError::OutputBufferTooSmall {
2540 required: rgb_len,
2541 provided: rgb_row.len(),
2542 });
2543 }
2544 for (pixel, &sample) in rgb_row[..rgb_len].chunks_exact_mut(3).zip(gray_row.iter()) {
2545 pixel.copy_from_slice(&[sample, sample, sample]);
2546 }
2547 sink.write_row(y, &rgb_row[..rgb_len])
2548 })
2549 }
2550
2551 fn decode_lossless_rgb8_into(
2552 &self,
2553 out: &mut [u8],
2554 stride: usize,
2555 ) -> Result<DecodeOutcome, JpegError> {
2556 match lossless_color_sampling(&self.info) {
2557 Some(LosslessColorSampling::S444) => {
2558 self.decode_lossless_color8_components_into(out, stride, ColorSpace::Rgb)
2559 }
2560 Some(LosslessColorSampling::S422 | LosslessColorSampling::S420) => {
2561 self.decode_lossless_color8_sampled_into(out, stride, ColorSpace::Rgb)
2562 }
2563 None => Err(JpegError::NotImplemented {
2564 sof: self.info.sof_kind,
2565 }),
2566 }
2567 }
2568
2569 fn decode_lossless_color_components_into<P>(
2570 &self,
2571 out: &mut [u8],
2572 stride: usize,
2573 color_space: ColorSpace,
2574 ) -> Result<DecodeOutcome, JpegError>
2575 where
2576 P: LosslessSample,
2577 {
2578 let plan = self
2579 .lossless_plan
2580 .as_ref()
2581 .ok_or(JpegError::NotImplemented {
2582 sof: self.info.sof_kind,
2583 })?;
2584 if plan.bit_depth != P::BIT_DEPTH {
2585 return Err(JpegError::UnsupportedBitDepth {
2586 depth: plan.bit_depth,
2587 });
2588 }
2589 if self.info.color_space != color_space {
2590 return Err(JpegError::NotImplemented {
2591 sof: self.info.sof_kind,
2592 });
2593 }
2594 if self.plan.components.len() != 3 {
2595 return Err(JpegError::UnsupportedComponentCount {
2596 count: self.plan.components.len() as u8,
2597 });
2598 }
2599 if !(1..=7).contains(&plan.predictor) {
2600 return Err(JpegError::UnsupportedPredictor {
2601 predictor: plan.predictor,
2602 });
2603 }
2604
2605 let (width, height) = plan.dimensions;
2606 let scan_bytes = &self.bytes[plan.scan_offset..];
2607 let mut br = BitReader::new(scan_bytes);
2608 let restart = u32::from(self.plan.restart_interval.unwrap_or(0));
2609 let total_pixels = width.saturating_mul(height);
2610 let mut pixels_since_restart = 0u32;
2611 let mut expected_rst = 0u8;
2612 for y in 0..height as usize {
2613 for x in 0..width as usize {
2614 let pixel_index = y as u32 * width + x as u32;
2615 if restart > 0 && pixels_since_restart == restart {
2616 consume_lossless_restart(
2617 &mut br,
2618 pixel_index,
2619 total_pixels,
2620 &mut expected_rst,
2621 )?;
2622 pixels_since_restart = 0;
2623 }
2624 for component in &self.plan.components {
2625 if component.output_index >= 3 {
2626 return Err(JpegError::UnsupportedComponentCount {
2627 count: self.plan.components.len() as u8,
2628 });
2629 }
2630 let predictor = if restart > 0 && pixels_since_restart == 0 {
2631 P::RESTART_PREDICTOR
2632 } else {
2633 lossless_predictor_color_into::<P>(
2634 plan.predictor,
2635 out,
2636 stride,
2637 x,
2638 y,
2639 component.output_index,
2640 )
2641 };
2642 let diff = component.dc_table.decode_fast_dc(&mut br)?;
2643 let sample = P::from_i32(predictor + diff)?;
2644 let offset = y * stride + (x * 3 + component.output_index) * P::BYTES;
2645 sample.write_le(&mut out[offset..]);
2646 }
2647 pixels_since_restart += 1;
2648 }
2649 }
2650
2651 let scan_warnings = finish_scan(&mut br, true)?;
2652 Ok(DecodeOutcome {
2653 decoded: Rect::full(self.info.dimensions),
2654 warnings: merged_warnings(&self.warnings, scan_warnings),
2655 })
2656 }
2657
2658 fn decode_lossless_color8_components_into(
2659 &self,
2660 out: &mut [u8],
2661 stride: usize,
2662 color_space: ColorSpace,
2663 ) -> Result<DecodeOutcome, JpegError> {
2664 self.decode_lossless_color_components_into::<u8>(out, stride, color_space)
2665 }
2666
2667 fn decode_lossless_color_sampled_into<P>(
2668 &self,
2669 out: &mut [u8],
2670 stride: usize,
2671 color_space: ColorSpace,
2672 write_output: impl FnOnce(
2673 &mut [u8],
2674 usize,
2675 ColorSpace,
2676 LosslessColorSampling,
2677 (usize, usize),
2678 LosslessColorPlanes<'_, P>,
2679 ),
2680 ) -> Result<DecodeOutcome, JpegError>
2681 where
2682 P: LosslessSample,
2683 {
2684 let plan = self
2685 .lossless_plan
2686 .as_ref()
2687 .ok_or(JpegError::NotImplemented {
2688 sof: self.info.sof_kind,
2689 })?;
2690 if plan.bit_depth != P::BIT_DEPTH {
2691 return Err(JpegError::UnsupportedBitDepth {
2692 depth: plan.bit_depth,
2693 });
2694 }
2695 if self.info.color_space != color_space {
2696 return Err(JpegError::NotImplemented {
2697 sof: self.info.sof_kind,
2698 });
2699 }
2700 let sampling = lossless_color_sampling(&self.info).ok_or(JpegError::NotImplemented {
2701 sof: self.info.sof_kind,
2702 })?;
2703 if !matches!(
2704 sampling,
2705 LosslessColorSampling::S422 | LosslessColorSampling::S420
2706 ) {
2707 return Err(JpegError::NotImplemented {
2708 sof: self.info.sof_kind,
2709 });
2710 }
2711 if self.plan.components.len() != 3 {
2712 return Err(JpegError::UnsupportedComponentCount {
2713 count: self.plan.components.len() as u8,
2714 });
2715 }
2716 if !(1..=7).contains(&plan.predictor) {
2717 return Err(JpegError::UnsupportedPredictor {
2718 predictor: plan.predictor,
2719 });
2720 }
2721
2722 let (width, height) = plan.dimensions;
2723 let width = width as usize;
2724 let height = height as usize;
2725 let chroma_width = width.div_ceil(self.info.sampling.max_h as usize);
2726 let chroma_height = height.div_ceil(self.info.sampling.max_v as usize);
2727 let mut c0 = vec![P::default(); width * height];
2728 let mut c1 = vec![P::default(); chroma_width * chroma_height];
2729 let mut c2 = vec![P::default(); chroma_width * chroma_height];
2730
2731 let scan_bytes = &self.bytes[plan.scan_offset..];
2732 let mut br = BitReader::new(scan_bytes);
2733 let restart = u32::from(self.plan.restart_interval.unwrap_or(0));
2734 let total_mcus = (chroma_width * chroma_height) as u32;
2735 let mut mcus_since_restart = 0u32;
2736 let mut expected_rst = 0u8;
2737 for mcu_y in 0..chroma_height {
2738 for mcu_x in 0..chroma_width {
2739 let mcu_index = (mcu_y * chroma_width + mcu_x) as u32;
2740 if restart > 0 && mcus_since_restart == restart {
2741 consume_lossless_restart(&mut br, mcu_index, total_mcus, &mut expected_rst)?;
2742 mcus_since_restart = 0;
2743 }
2744 for component in &self.plan.components {
2745 let (plane, plane_width, plane_height) = match component.output_index {
2746 0 => (&mut c0, width, height),
2747 1 => (&mut c1, chroma_width, chroma_height),
2748 2 => (&mut c2, chroma_width, chroma_height),
2749 _ => {
2750 return Err(JpegError::UnsupportedComponentCount {
2751 count: self.plan.components.len() as u8,
2752 });
2753 }
2754 };
2755 for local_y in 0..component.v as usize {
2756 for local_x in 0..component.h as usize {
2757 let x = mcu_x * component.h as usize + local_x;
2758 let y = mcu_y * component.v as usize + local_y;
2759 if x >= plane_width || y >= plane_height {
2760 continue;
2761 }
2762 decode_lossless_plane_sample(
2763 &mut br,
2764 &component.dc_table,
2765 plan.predictor,
2766 plane,
2767 plane_width,
2768 LosslessPlaneSample {
2769 x,
2770 y,
2771 restart_first_sample: restart > 0
2772 && mcus_since_restart == 0
2773 && local_x == 0
2774 && local_y == 0,
2775 },
2776 )?;
2777 }
2778 }
2779 }
2780 mcus_since_restart += 1;
2781 }
2782 }
2783
2784 let scan_warnings = finish_scan(&mut br, true)?;
2785 write_output(
2786 out,
2787 stride,
2788 color_space,
2789 sampling,
2790 (width, height),
2791 LosslessColorPlanes {
2792 c0: &c0,
2793 c1: &c1,
2794 c2: &c2,
2795 },
2796 );
2797 Ok(DecodeOutcome {
2798 decoded: Rect::full(self.info.dimensions),
2799 warnings: merged_warnings(&self.warnings, scan_warnings),
2800 })
2801 }
2802
2803 fn decode_lossless_color8_sampled_into(
2804 &self,
2805 out: &mut [u8],
2806 stride: usize,
2807 color_space: ColorSpace,
2808 ) -> Result<DecodeOutcome, JpegError> {
2809 self.decode_lossless_color_sampled_into::<u8>(
2810 out,
2811 stride,
2812 color_space,
2813 write_lossless_color8_sampled_output,
2814 )
2815 }
2816
2817 fn decode_lossless_ycbcr8_into(
2818 &self,
2819 out: &mut [u8],
2820 stride: usize,
2821 ) -> Result<DecodeOutcome, JpegError> {
2822 match lossless_color_sampling(&self.info) {
2823 Some(LosslessColorSampling::S444) => {
2824 let outcome =
2825 self.decode_lossless_color8_components_into(out, stride, ColorSpace::YCbCr)?;
2826 convert_ycbcr8_to_rgb8_in_place(out, stride, self.info.dimensions);
2827 Ok(outcome)
2828 }
2829 Some(LosslessColorSampling::S422 | LosslessColorSampling::S420) => {
2830 self.decode_lossless_color8_sampled_into(out, stride, ColorSpace::YCbCr)
2831 }
2832 None => Err(JpegError::NotImplemented {
2833 sof: self.info.sof_kind,
2834 }),
2835 }
2836 }
2837
2838 fn decode_lossless_rgb8_region_scaled_into(
2839 &self,
2840 out: &mut [u8],
2841 stride: usize,
2842 roi: Rect,
2843 downscale: DownscaleFactor,
2844 ) -> Result<DecodeOutcome, JpegError> {
2845 if roi == Rect::full(self.info.dimensions) && downscale == DownscaleFactor::Full {
2846 return self.decode_lossless_rgb8_into(out, stride);
2847 }
2848
2849 let (width, height) = self.info.dimensions;
2850 let full_stride = width as usize * 3;
2851 let mut full =
2852 allocate_output_buffer(checked_scratch_len(&[full_stride, height as usize])?);
2853 let mut outcome = self.decode_lossless_rgb8_into(&mut full, full_stride)?;
2854 let output_rect = scaled_rect_covering(roi, downscale)?;
2855 copy_rgb8_scaled_rect(
2856 &full,
2857 (width, height),
2858 output_rect,
2859 downscale.denominator(),
2860 out,
2861 stride,
2862 );
2863 outcome.decoded = roi;
2864 Ok(outcome)
2865 }
2866
2867 fn decode_lossless_ycbcr8_region_scaled_into(
2868 &self,
2869 out: &mut [u8],
2870 stride: usize,
2871 roi: Rect,
2872 downscale: DownscaleFactor,
2873 ) -> Result<DecodeOutcome, JpegError> {
2874 if roi == Rect::full(self.info.dimensions) && downscale == DownscaleFactor::Full {
2875 return self.decode_lossless_ycbcr8_into(out, stride);
2876 }
2877
2878 let (width, height) = self.info.dimensions;
2879 let full_stride = width as usize * 3;
2880 let mut full =
2881 allocate_output_buffer(checked_scratch_len(&[full_stride, height as usize])?);
2882 let mut outcome = self.decode_lossless_ycbcr8_into(&mut full, full_stride)?;
2883 let output_rect = scaled_rect_covering(roi, downscale)?;
2884 copy_rgb8_scaled_rect(
2885 &full,
2886 (width, height),
2887 output_rect,
2888 downscale.denominator(),
2889 out,
2890 stride,
2891 );
2892 outcome.decoded = roi;
2893 Ok(outcome)
2894 }
2895
2896 fn decode_lossless_rgba8_region_into(
2897 &self,
2898 out: &mut [u8],
2899 stride: usize,
2900 roi: Rect,
2901 downscale: DownscaleFactor,
2902 alpha: u8,
2903 ) -> Result<DecodeOutcome, JpegError> {
2904 let output_rect = scaled_rect_covering(roi, downscale)?;
2905 let rgb_stride = output_rect.w as usize * 3;
2906 let mut rgb =
2907 allocate_output_buffer(checked_scratch_len(&[rgb_stride, output_rect.h as usize])?);
2908 let outcome = match self.info.color_space {
2909 ColorSpace::YCbCr => {
2910 self.decode_lossless_ycbcr8_region_scaled_into(&mut rgb, rgb_stride, roi, downscale)
2911 }
2912 _ => self.decode_lossless_rgb8_region_scaled_into(&mut rgb, rgb_stride, roi, downscale),
2913 }?;
2914 copy_rgb8_to_rgba8(
2915 &rgb,
2916 rgb_stride,
2917 output_rect.w,
2918 output_rect.h,
2919 out,
2920 stride,
2921 alpha,
2922 );
2923 Ok(outcome)
2924 }
2925
2926 fn decode_lossless_color_rows<P, S>(
2927 &self,
2928 sink: &mut S,
2929 prev_row: &mut Vec<u8>,
2930 curr_row: &mut Vec<u8>,
2931 conversion_row: Option<&mut [u8]>,
2932 color_space: ColorSpace,
2933 convert_row: impl Fn(&[u8], &mut [u8]),
2934 ) -> Result<DecodeOutcome, JpegError>
2935 where
2936 P: LosslessSample,
2937 S: RowSink<u8, Error = JpegError>,
2938 {
2939 let plan = self
2940 .lossless_plan
2941 .as_ref()
2942 .ok_or(JpegError::NotImplemented {
2943 sof: self.info.sof_kind,
2944 })?;
2945 if plan.bit_depth != P::BIT_DEPTH {
2946 return Err(JpegError::UnsupportedBitDepth {
2947 depth: plan.bit_depth,
2948 });
2949 }
2950 if self.info.color_space != color_space {
2951 return Err(JpegError::NotImplemented {
2952 sof: self.info.sof_kind,
2953 });
2954 }
2955 if self.plan.components.len() != 3 {
2956 return Err(JpegError::UnsupportedComponentCount {
2957 count: self.plan.components.len() as u8,
2958 });
2959 }
2960 if !(1..=7).contains(&plan.predictor) {
2961 return Err(JpegError::UnsupportedPredictor {
2962 predictor: plan.predictor,
2963 });
2964 }
2965
2966 let (width, height) = plan.dimensions;
2967 let width = width as usize;
2968 let row_len = width.saturating_mul(3 * P::BYTES);
2969 prev_row.resize(row_len, 0);
2970 curr_row.resize(row_len, 0);
2971
2972 let scan_bytes = &self.bytes[plan.scan_offset..];
2973 let mut br = BitReader::new(scan_bytes);
2974 let restart = u32::from(self.plan.restart_interval.unwrap_or(0));
2975 let total_pixels = plan.dimensions.0.saturating_mul(height);
2976 let mut pixels_since_restart = 0u32;
2977 let mut expected_rst = 0u8;
2978 let mut conversion_row = conversion_row;
2979 for y in 0..height as usize {
2980 for x in 0..width {
2981 let pixel_index = y as u32 * plan.dimensions.0 + x as u32;
2982 if restart > 0 && pixels_since_restart == restart {
2983 consume_lossless_restart(
2984 &mut br,
2985 pixel_index,
2986 total_pixels,
2987 &mut expected_rst,
2988 )?;
2989 pixels_since_restart = 0;
2990 }
2991 for component in &self.plan.components {
2992 if component.output_index >= 3 {
2993 return Err(JpegError::UnsupportedComponentCount {
2994 count: self.plan.components.len() as u8,
2995 });
2996 }
2997 let predictor = if restart > 0 && pixels_since_restart == 0 {
2998 P::RESTART_PREDICTOR
2999 } else {
3000 lossless_predictor_color_rows::<P>(
3001 plan.predictor,
3002 curr_row,
3003 prev_row,
3004 x,
3005 y,
3006 component.output_index,
3007 )
3008 };
3009 let diff = component.dc_table.decode_fast_dc(&mut br)?;
3010 let sample = P::from_i32(predictor + diff)?;
3011 let offset = (x * 3 + component.output_index) * P::BYTES;
3012 sample.write_le(&mut curr_row[offset..]);
3013 }
3014 pixels_since_restart += 1;
3015 }
3016 let row = if color_space == ColorSpace::YCbCr {
3017 let row = conversion_row
3018 .as_deref_mut()
3019 .ok_or(JpegError::OutputBufferTooSmall {
3020 required: row_len,
3021 provided: 0,
3022 })?;
3023 if row.len() < row_len {
3024 return Err(JpegError::OutputBufferTooSmall {
3025 required: row_len,
3026 provided: row.len(),
3027 });
3028 }
3029 convert_row(&curr_row[..row_len], &mut row[..row_len]);
3030 &row[..row_len]
3031 } else {
3032 &curr_row[..row_len]
3033 };
3034 sink.write_row(y as u32, row)?;
3035 core::mem::swap(prev_row, curr_row);
3036 }
3037
3038 let scan_warnings = finish_scan(&mut br, true)?;
3039 Ok(DecodeOutcome {
3040 decoded: Rect::full(self.info.dimensions),
3041 warnings: merged_warnings(&self.warnings, scan_warnings),
3042 })
3043 }
3044
3045 fn decode_lossless_color8_rows<S>(
3046 &self,
3047 sink: &mut S,
3048 prev_row: &mut Vec<u8>,
3049 curr_row: &mut Vec<u8>,
3050 conversion_row: Option<&mut [u8]>,
3051 color_space: ColorSpace,
3052 ) -> Result<DecodeOutcome, JpegError>
3053 where
3054 S: RowSink<u8, Error = JpegError>,
3055 {
3056 self.decode_lossless_color_rows::<u8, S>(
3057 sink,
3058 prev_row,
3059 curr_row,
3060 conversion_row,
3061 color_space,
3062 copy_ycbcr8_row_to_rgb8,
3063 )
3064 }
3065
3066 fn decode_lossless_gray16_rows<S>(
3067 &self,
3068 sink: &mut S,
3069 prev_row: &mut Vec<u8>,
3070 curr_row: &mut Vec<u8>,
3071 ) -> Result<DecodeOutcome, JpegError>
3072 where
3073 S: RowSink<u8, Error = JpegError>,
3074 {
3075 self.decode_lossless_gray_rows::<u16, S>(sink, prev_row, curr_row, |sink, y, row| {
3076 sink.write_row(y, row)
3077 })
3078 }
3079
3080 fn decode_lossless_color16_rows<S>(
3081 &self,
3082 sink: &mut S,
3083 prev_row: &mut Vec<u8>,
3084 curr_row: &mut Vec<u8>,
3085 conversion_row: Option<&mut [u8]>,
3086 color_space: ColorSpace,
3087 ) -> Result<DecodeOutcome, JpegError>
3088 where
3089 S: RowSink<u8, Error = JpegError>,
3090 {
3091 self.decode_lossless_color_rows::<u16, S>(
3092 sink,
3093 prev_row,
3094 curr_row,
3095 conversion_row,
3096 color_space,
3097 copy_ycbcr16_row_to_rgb16,
3098 )
3099 }
3100
3101 fn decode_lossless_rgb16_into(
3102 &self,
3103 out: &mut [u8],
3104 stride: usize,
3105 ) -> Result<DecodeOutcome, JpegError> {
3106 match lossless_color_sampling(&self.info) {
3107 Some(LosslessColorSampling::S444) => {
3108 self.decode_lossless_color16_components_into(out, stride, ColorSpace::Rgb)
3109 }
3110 Some(LosslessColorSampling::S422) => {
3111 self.decode_lossless_color16_sampled_into(out, stride, ColorSpace::Rgb)
3112 }
3113 Some(LosslessColorSampling::S420) => {
3114 self.decode_lossless_color16_sampled_into(out, stride, ColorSpace::Rgb)
3115 }
3116 None => Err(JpegError::NotImplemented {
3117 sof: self.info.sof_kind,
3118 }),
3119 }
3120 }
3121
3122 fn decode_lossless_color16_components_into(
3123 &self,
3124 out: &mut [u8],
3125 stride: usize,
3126 color_space: ColorSpace,
3127 ) -> Result<DecodeOutcome, JpegError> {
3128 self.decode_lossless_color_components_into::<u16>(out, stride, color_space)
3129 }
3130
3131 fn decode_lossless_color16_sampled_into(
3132 &self,
3133 out: &mut [u8],
3134 stride: usize,
3135 color_space: ColorSpace,
3136 ) -> Result<DecodeOutcome, JpegError> {
3137 self.decode_lossless_color_sampled_into::<u16>(
3138 out,
3139 stride,
3140 color_space,
3141 write_lossless_color16_sampled_output,
3142 )
3143 }
3144
3145 fn decode_lossless_ycbcr16_into(
3146 &self,
3147 out: &mut [u8],
3148 stride: usize,
3149 ) -> Result<DecodeOutcome, JpegError> {
3150 match lossless_color_sampling(&self.info) {
3151 Some(LosslessColorSampling::S444) => {
3152 let outcome =
3153 self.decode_lossless_color16_components_into(out, stride, ColorSpace::YCbCr)?;
3154 convert_ycbcr16_to_rgb16_in_place(out, stride, self.info.dimensions);
3155 Ok(outcome)
3156 }
3157 Some(LosslessColorSampling::S422) => {
3158 self.decode_lossless_color16_sampled_into(out, stride, ColorSpace::YCbCr)
3159 }
3160 Some(LosslessColorSampling::S420) => {
3161 self.decode_lossless_color16_sampled_into(out, stride, ColorSpace::YCbCr)
3162 }
3163 None => Err(JpegError::NotImplemented {
3164 sof: self.info.sof_kind,
3165 }),
3166 }
3167 }
3168
3169 fn decode_lossless_rgb16_region_scaled_into(
3170 &self,
3171 out: &mut [u8],
3172 stride: usize,
3173 roi: Rect,
3174 downscale: DownscaleFactor,
3175 ) -> Result<DecodeOutcome, JpegError> {
3176 if roi == Rect::full(self.info.dimensions) && downscale == DownscaleFactor::Full {
3177 return self.decode_lossless_rgb16_into(out, stride);
3178 }
3179
3180 let (width, height) = self.info.dimensions;
3181 let full_stride = width as usize * 6;
3182 let mut full =
3183 allocate_output_buffer(checked_scratch_len(&[full_stride, height as usize])?);
3184 let mut outcome = self.decode_lossless_rgb16_into(&mut full, full_stride)?;
3185 let output_rect = scaled_rect_covering(roi, downscale)?;
3186 copy_rgb16_scaled_rect(
3187 &full,
3188 (width, height),
3189 output_rect,
3190 downscale.denominator(),
3191 out,
3192 stride,
3193 );
3194 outcome.decoded = roi;
3195 Ok(outcome)
3196 }
3197
3198 fn decode_lossless_ycbcr16_region_scaled_into(
3199 &self,
3200 out: &mut [u8],
3201 stride: usize,
3202 roi: Rect,
3203 downscale: DownscaleFactor,
3204 ) -> Result<DecodeOutcome, JpegError> {
3205 if roi == Rect::full(self.info.dimensions) && downscale == DownscaleFactor::Full {
3206 return self.decode_lossless_ycbcr16_into(out, stride);
3207 }
3208
3209 let (width, height) = self.info.dimensions;
3210 let full_stride = width as usize * 6;
3211 let mut full =
3212 allocate_output_buffer(checked_scratch_len(&[full_stride, height as usize])?);
3213 let mut outcome = self.decode_lossless_ycbcr16_into(&mut full, full_stride)?;
3214 let output_rect = scaled_rect_covering(roi, downscale)?;
3215 copy_rgb16_scaled_rect(
3216 &full,
3217 (width, height),
3218 output_rect,
3219 downscale.denominator(),
3220 out,
3221 stride,
3222 );
3223 outcome.decoded = roi;
3224 Ok(outcome)
3225 }
3226
3227 fn decode_lossless_rgba16_region_scaled_into(
3228 &self,
3229 out: &mut [u8],
3230 stride: usize,
3231 roi: Rect,
3232 downscale: DownscaleFactor,
3233 alpha: u16,
3234 ) -> Result<DecodeOutcome, JpegError> {
3235 let output_rect = scaled_rect_covering(roi, downscale)?;
3236 let rgb_stride = output_rect.w as usize * 6;
3237 let mut rgb =
3238 allocate_output_buffer(checked_scratch_len(&[rgb_stride, output_rect.h as usize])?);
3239 let outcome = match self.info.color_space {
3240 ColorSpace::YCbCr => self
3241 .decode_lossless_ycbcr16_region_scaled_into(&mut rgb, rgb_stride, roi, downscale),
3242 _ => {
3243 self.decode_lossless_rgb16_region_scaled_into(&mut rgb, rgb_stride, roi, downscale)
3244 }
3245 }?;
3246 copy_rgb16_to_rgba16(
3247 &rgb,
3248 rgb_stride,
3249 output_rect.w,
3250 output_rect.h,
3251 out,
3252 stride,
3253 alpha,
3254 );
3255 Ok(outcome)
3256 }
3257
3258 fn decode_12bit_rgba16_region_scaled_into(
3259 &self,
3260 out: &mut [u8],
3261 stride: usize,
3262 roi: Rect,
3263 downscale: DownscaleFactor,
3264 alpha: u16,
3265 ) -> Result<DecodeOutcome, JpegError> {
3266 let output_rect = scaled_rect_covering(roi, downscale)?;
3267 let rgb_stride = output_rect.w as usize * 6;
3268 let mut rgb =
3269 allocate_output_buffer(checked_scratch_len(&[rgb_stride, output_rect.h as usize])?);
3270 let outcome = if self.info.sof_kind == SofKind::Progressive12 {
3271 self.decode_progressive12_rgb16_region_scaled_into(&mut rgb, rgb_stride, roi, downscale)
3272 } else {
3273 self.decode_extended12_rgb16_region_scaled_into(&mut rgb, rgb_stride, roi, downscale)
3274 }?;
3275 copy_rgb16_to_rgba16(
3276 &rgb,
3277 rgb_stride,
3278 output_rect.w,
3279 output_rect.h,
3280 out,
3281 stride,
3282 alpha,
3283 );
3284 Ok(outcome)
3285 }
3286
3287 fn decode_lossless_gray16_into(
3288 &self,
3289 out: &mut [u8],
3290 stride: usize,
3291 ) -> Result<DecodeOutcome, JpegError> {
3292 let plan = self
3293 .lossless_plan
3294 .as_ref()
3295 .ok_or(JpegError::NotImplemented {
3296 sof: self.info.sof_kind,
3297 })?;
3298 if plan.bit_depth != 16 {
3299 return Err(JpegError::UnsupportedBitDepth {
3300 depth: plan.bit_depth,
3301 });
3302 }
3303 if !(1..=7).contains(&plan.predictor) {
3304 return Err(JpegError::UnsupportedPredictor {
3305 predictor: plan.predictor,
3306 });
3307 }
3308
3309 let (width, height) = plan.dimensions;
3310 let scan_bytes = &self.bytes[plan.scan_offset..];
3311 let mut br = BitReader::new(scan_bytes);
3312 let restart = u32::from(self.plan.restart_interval.unwrap_or(0));
3313 let total_samples = width.saturating_mul(height);
3314 let mut samples_since_restart = 0u32;
3315 let mut expected_rst = 0u8;
3316 for y in 0..height as usize {
3317 for x in 0..width as usize {
3318 let sample_index = y as u32 * width + x as u32;
3319 if restart > 0 && samples_since_restart == restart {
3320 consume_lossless_restart(
3321 &mut br,
3322 sample_index,
3323 total_samples,
3324 &mut expected_rst,
3325 )?;
3326 samples_since_restart = 0;
3327 }
3328 let predictor = if restart > 0 && samples_since_restart == 0 {
3329 32768
3330 } else {
3331 lossless_predictor_value_u16(plan.predictor, out, stride, x, y)
3332 };
3333 let diff = plan.dc_table.decode_fast_dc(&mut br)?;
3334 let sample = <u16 as LosslessSample>::from_i32(predictor + diff)?;
3335 let offset = y * stride + x * 2;
3336 sample.write_le(&mut out[offset..offset + 2]);
3337 samples_since_restart += 1;
3338 }
3339 }
3340
3341 let scan_warnings = finish_scan(&mut br, true)?;
3342 Ok(DecodeOutcome {
3343 decoded: Rect::full(self.info.dimensions),
3344 warnings: merged_warnings(&self.warnings, scan_warnings),
3345 })
3346 }
3347
3348 fn decode_lossless_gray16_region_scaled_into(
3349 &self,
3350 out: &mut [u8],
3351 stride: usize,
3352 roi: Rect,
3353 downscale: DownscaleFactor,
3354 ) -> Result<DecodeOutcome, JpegError> {
3355 if roi == Rect::full(self.info.dimensions) && downscale == DownscaleFactor::Full {
3356 return self.decode_lossless_gray16_into(out, stride);
3357 }
3358
3359 let (width, height) = self.info.dimensions;
3360 let full_stride = width as usize * 2;
3361 let mut full =
3362 allocate_output_buffer(checked_scratch_len(&[full_stride, height as usize])?);
3363 let mut outcome = self.decode_lossless_gray16_into(&mut full, full_stride)?;
3364 let output_rect = scaled_rect_covering(roi, downscale)?;
3365 copy_gray16_scaled_rect(
3366 &full,
3367 (width, height),
3368 output_rect,
3369 downscale.denominator(),
3370 out,
3371 stride,
3372 );
3373 outcome.decoded = roi;
3374 Ok(outcome)
3375 }
3376
3377 fn decode_progressive12_gray16_region_scaled_into(
3378 &self,
3379 out: &mut [u8],
3380 stride: usize,
3381 roi: Rect,
3382 downscale: DownscaleFactor,
3383 ) -> Result<DecodeOutcome, JpegError> {
3384 self.decode_progressive12_region_into(out, stride, roi, downscale, Extended12Output::Gray16)
3385 }
3386
3387 fn decode_progressive12_rgb16_region_scaled_into(
3388 &self,
3389 out: &mut [u8],
3390 stride: usize,
3391 roi: Rect,
3392 downscale: DownscaleFactor,
3393 ) -> Result<DecodeOutcome, JpegError> {
3394 self.decode_progressive12_region_into(out, stride, roi, downscale, Extended12Output::Rgb16)
3395 }
3396
3397 fn decode_progressive12_region_into(
3398 &self,
3399 out: &mut [u8],
3400 stride: usize,
3401 roi: Rect,
3402 downscale: DownscaleFactor,
3403 output: Extended12Output,
3404 ) -> Result<DecodeOutcome, JpegError> {
3405 let plan = self
3406 .progressive_plan
3407 .as_ref()
3408 .ok_or(JpegError::NotImplemented {
3409 sof: self.info.sof_kind,
3410 })?;
3411 if self.info.sof_kind != SofKind::Progressive12
3412 || !matches!(
3413 self.info.color_space,
3414 ColorSpace::Grayscale
3415 | ColorSpace::YCbCr
3416 | ColorSpace::Rgb
3417 | ColorSpace::Cmyk
3418 | ColorSpace::Ycck
3419 )
3420 {
3421 return Err(JpegError::NotImplemented {
3422 sof: self.info.sof_kind,
3423 });
3424 }
3425 if !roi.is_within(self.info.dimensions) {
3426 return Err(JpegError::RectOutOfBounds {
3427 rect: roi,
3428 width: self.info.dimensions.0,
3429 height: self.info.dimensions.1,
3430 });
3431 }
3432 if matches!(output, Extended12Output::Rgb16) {
3433 match self.info.color_space {
3434 ColorSpace::Rgb => {
3435 let sampling = progressive_color_sampling(plan, self.info.sof_kind)?;
3436 return match sampling {
3437 Extended12ColorSampling::S444 => self
3438 .decode_progressive12_color444_region_into(
3439 out,
3440 stride,
3441 roi,
3442 downscale,
3443 Extended12RgbProjection::Identity,
3444 ),
3445 Extended12ColorSampling::S422 | Extended12ColorSampling::S420 => self
3446 .decode_progressive12_color_subsampled_region_into(
3447 out,
3448 stride,
3449 roi,
3450 downscale,
3451 sampling,
3452 Extended12RgbProjection::Identity,
3453 ),
3454 };
3455 }
3456 ColorSpace::YCbCr => {
3457 let sampling = progressive_color_sampling(plan, self.info.sof_kind)?;
3458 return match sampling {
3459 Extended12ColorSampling::S444 => self
3460 .decode_progressive12_color444_region_into(
3461 out,
3462 stride,
3463 roi,
3464 downscale,
3465 Extended12RgbProjection::YCbCr,
3466 ),
3467 Extended12ColorSampling::S422 | Extended12ColorSampling::S420 => self
3468 .decode_progressive12_color_subsampled_region_into(
3469 out,
3470 stride,
3471 roi,
3472 downscale,
3473 sampling,
3474 Extended12RgbProjection::YCbCr,
3475 ),
3476 };
3477 }
3478 ColorSpace::Cmyk | ColorSpace::Ycck => {
3479 let sampling = progressive_four_component_sampling(plan, self.info.sof_kind)?;
3480 return self.decode_progressive12_four_component_region_into(
3481 out, stride, roi, downscale, sampling,
3482 );
3483 }
3484 ColorSpace::Grayscale => {}
3485 }
3486 }
3487 if self.info.color_space != ColorSpace::Grayscale || plan.components.len() != 1 {
3488 return Err(JpegError::NotImplemented {
3489 sof: self.info.sof_kind,
3490 });
3491 }
3492
3493 let output_rect = scaled_rect_covering(roi, downscale)?;
3494 let dct_blocks = decode_progressive_dct_blocks(plan, self.bytes)?;
3495 let component = &plan.components[0];
3496 let component_coeffs = &dct_blocks.quantized[0];
3497 let (width, height) = self.info.dimensions;
3498 let mut dequant = [0i16; 64];
3499 let mut pixels = [0u16; 64];
3500 let write_region = Extended12WriteRegion {
3501 output_rect,
3502 dimensions: (width, height),
3503 downscale,
3504 output,
3505 };
3506
3507 for block_y in 0..component.block_rows as usize {
3508 for block_x in 0..component.block_cols as usize {
3509 let block_index = block_y * component.block_cols as usize + block_x;
3510 dequantize_progressive12_block(
3511 &component_coeffs[block_index],
3512 &component.quant,
3513 &mut dequant,
3514 );
3515 if dequant[1..].iter().all(|&coeff| coeff == 0) {
3516 pixels.fill(crate::idct::idct_islow_12bit_dc_only_sample(dequant[0]));
3517 } else {
3518 crate::idct::idct_islow_12bit(&dequant, &mut pixels);
3519 }
3520 write_extended12_block_region(
3521 out,
3522 stride,
3523 write_region,
3524 ((block_x as u32) * 8, (block_y as u32) * 8),
3525 &pixels,
3526 );
3527 }
3528 }
3529
3530 Ok(DecodeOutcome {
3531 decoded: roi,
3532 warnings: self.warnings.to_vec(),
3533 })
3534 }
3535
3536 fn decode_progressive12_color444_region_into(
3537 &self,
3538 out: &mut [u8],
3539 stride: usize,
3540 roi: Rect,
3541 downscale: DownscaleFactor,
3542 projection: Extended12RgbProjection,
3543 ) -> Result<DecodeOutcome, JpegError> {
3544 let plan = self
3545 .progressive_plan
3546 .as_ref()
3547 .ok_or(JpegError::NotImplemented {
3548 sof: self.info.sof_kind,
3549 })?;
3550 if plan.components.len() != 3
3551 || plan.sampling.max_h != 1
3552 || plan.sampling.max_v != 1
3553 || plan
3554 .components
3555 .iter()
3556 .any(|component| component.h != 1 || component.v != 1 || component.output_index > 2)
3557 {
3558 return Err(JpegError::NotImplemented {
3559 sof: self.info.sof_kind,
3560 });
3561 }
3562
3563 let output_rect = scaled_rect_covering(roi, downscale)?;
3564 let dct_blocks = decode_progressive_dct_blocks(plan, self.bytes)?;
3565 let (width, height) = self.info.dimensions;
3566 let component_indices = progressive_color_component_indices(plan)?;
3567 let block_cols = plan.components[component_indices[0]].block_cols as usize;
3568 let block_rows = plan.components[component_indices[0]].block_rows as usize;
3569 let mut dequant = [[0i16; 64]; 3];
3570 let mut pixels = [[0u16; 64]; 3];
3571 let write_region = Extended12WriteRegion {
3572 output_rect,
3573 dimensions: (width, height),
3574 downscale,
3575 output: Extended12Output::Rgb16,
3576 };
3577
3578 for block_y in 0..block_rows {
3579 for block_x in 0..block_cols {
3580 for output_index in 0..3 {
3581 let component_index = component_indices[output_index];
3582 let component = &plan.components[component_index];
3583 let component_coeffs = &dct_blocks.quantized[component_index];
3584 let block_index = block_y * component.block_cols as usize + block_x;
3585 dequantize_progressive12_block(
3586 &component_coeffs[block_index],
3587 &component.quant,
3588 &mut dequant[output_index],
3589 );
3590 if dequant[output_index][1..].iter().all(|&coeff| coeff == 0) {
3591 pixels[output_index].fill(crate::idct::idct_islow_12bit_dc_only_sample(
3592 dequant[output_index][0],
3593 ));
3594 } else {
3595 crate::idct::idct_islow_12bit(
3596 &dequant[output_index],
3597 &mut pixels[output_index],
3598 );
3599 }
3600 }
3601 write_extended12_rgb_block_region(
3602 out,
3603 stride,
3604 write_region,
3605 projection,
3606 ((block_x as u32) * 8, (block_y as u32) * 8),
3607 &pixels,
3608 );
3609 }
3610 }
3611
3612 Ok(DecodeOutcome {
3613 decoded: roi,
3614 warnings: self.warnings.to_vec(),
3615 })
3616 }
3617
3618 fn decode_progressive12_color_subsampled_region_into(
3619 &self,
3620 out: &mut [u8],
3621 stride: usize,
3622 roi: Rect,
3623 downscale: DownscaleFactor,
3624 sampling: Extended12ColorSampling,
3625 projection: Extended12RgbProjection,
3626 ) -> Result<DecodeOutcome, JpegError> {
3627 let plan = self
3628 .progressive_plan
3629 .as_ref()
3630 .ok_or(JpegError::NotImplemented {
3631 sof: self.info.sof_kind,
3632 })?;
3633 debug_assert!(matches!(
3634 sampling,
3635 Extended12ColorSampling::S422 | Extended12ColorSampling::S420
3636 ));
3637 if progressive_color_sampling(plan, self.info.sof_kind)? != sampling {
3638 return Err(JpegError::NotImplemented {
3639 sof: self.info.sof_kind,
3640 });
3641 }
3642
3643 let output_rect = scaled_rect_covering(roi, downscale)?;
3644 let dct_blocks = decode_progressive_dct_blocks(plan, self.bytes)?;
3645 let planes = render_progressive12_color_planes(plan, &dct_blocks.quantized)?;
3646 let write_region = Extended12WriteRegion {
3647 output_rect,
3648 dimensions: self.info.dimensions,
3649 downscale,
3650 output: Extended12Output::Rgb16,
3651 };
3652 match sampling {
3653 Extended12ColorSampling::S444 => unreachable!("4:4:4 path is handled directly"),
3654 Extended12ColorSampling::S422 => write_extended12_color422_planes_region(
3655 out,
3656 stride,
3657 write_region,
3658 projection,
3659 &planes,
3660 ),
3661 Extended12ColorSampling::S420 => write_extended12_color420_planes_region(
3662 out,
3663 stride,
3664 write_region,
3665 projection,
3666 &planes,
3667 ),
3668 }
3669
3670 Ok(DecodeOutcome {
3671 decoded: roi,
3672 warnings: self.warnings.to_vec(),
3673 })
3674 }
3675
3676 fn decode_progressive12_four_component_region_into(
3677 &self,
3678 out: &mut [u8],
3679 stride: usize,
3680 roi: Rect,
3681 downscale: DownscaleFactor,
3682 sampling: Extended12ColorSampling,
3683 ) -> Result<DecodeOutcome, JpegError> {
3684 let plan = self
3685 .progressive_plan
3686 .as_ref()
3687 .ok_or(JpegError::NotImplemented {
3688 sof: self.info.sof_kind,
3689 })?;
3690 if progressive_four_component_sampling(plan, self.info.sof_kind)? != sampling {
3691 return Err(JpegError::NotImplemented {
3692 sof: self.info.sof_kind,
3693 });
3694 }
3695
3696 let output_rect = scaled_rect_covering(roi, downscale)?;
3697 let dct_blocks = decode_progressive_dct_blocks(plan, self.bytes)?;
3698 let planes = render_progressive12_four_component_planes(plan, &dct_blocks.quantized)?;
3699 write_extended12_four_component_planes_region(
3700 out,
3701 stride,
3702 Extended12WriteRegion {
3703 output_rect,
3704 dimensions: self.info.dimensions,
3705 downscale,
3706 output: Extended12Output::Rgb16,
3707 },
3708 self.info.color_space,
3709 sampling,
3710 &planes,
3711 );
3712
3713 Ok(DecodeOutcome {
3714 decoded: roi,
3715 warnings: self.warnings.to_vec(),
3716 })
3717 }
3718
3719 fn decode_extended12_gray16_into(
3720 &self,
3721 out: &mut [u8],
3722 stride: usize,
3723 ) -> Result<DecodeOutcome, JpegError> {
3724 self.decode_extended12_region_into(
3725 out,
3726 stride,
3727 Rect::full(self.info.dimensions),
3728 DownscaleFactor::Full,
3729 Extended12Output::Gray16,
3730 )
3731 }
3732
3733 fn decode_extended12_gray16_region_into(
3734 &self,
3735 out: &mut [u8],
3736 stride: usize,
3737 roi: Rect,
3738 ) -> Result<DecodeOutcome, JpegError> {
3739 self.decode_extended12_region_into(
3740 out,
3741 stride,
3742 roi,
3743 DownscaleFactor::Full,
3744 Extended12Output::Gray16,
3745 )
3746 }
3747
3748 fn decode_extended12_gray16_region_scaled_into(
3749 &self,
3750 out: &mut [u8],
3751 stride: usize,
3752 roi: Rect,
3753 downscale: DownscaleFactor,
3754 ) -> Result<DecodeOutcome, JpegError> {
3755 self.decode_extended12_region_into(out, stride, roi, downscale, Extended12Output::Gray16)
3756 }
3757
3758 fn decode_extended12_rgb16_into(
3759 &self,
3760 out: &mut [u8],
3761 stride: usize,
3762 ) -> Result<DecodeOutcome, JpegError> {
3763 self.decode_extended12_region_into(
3764 out,
3765 stride,
3766 Rect::full(self.info.dimensions),
3767 DownscaleFactor::Full,
3768 Extended12Output::Rgb16,
3769 )
3770 }
3771
3772 fn decode_extended12_rgb16_region_into(
3773 &self,
3774 out: &mut [u8],
3775 stride: usize,
3776 roi: Rect,
3777 ) -> Result<DecodeOutcome, JpegError> {
3778 self.decode_extended12_region_into(
3779 out,
3780 stride,
3781 roi,
3782 DownscaleFactor::Full,
3783 Extended12Output::Rgb16,
3784 )
3785 }
3786
3787 fn decode_extended12_rgb16_region_scaled_into(
3788 &self,
3789 out: &mut [u8],
3790 stride: usize,
3791 roi: Rect,
3792 downscale: DownscaleFactor,
3793 ) -> Result<DecodeOutcome, JpegError> {
3794 self.decode_extended12_region_into(out, stride, roi, downscale, Extended12Output::Rgb16)
3795 }
3796
3797 fn decode_extended12_region_into(
3798 &self,
3799 out: &mut [u8],
3800 stride: usize,
3801 roi: Rect,
3802 downscale: DownscaleFactor,
3803 output: Extended12Output,
3804 ) -> Result<DecodeOutcome, JpegError> {
3805 if self.info.sof_kind != SofKind::Extended12 {
3806 return Err(JpegError::NotImplemented {
3807 sof: self.info.sof_kind,
3808 });
3809 }
3810 if !roi.is_within(self.info.dimensions) {
3811 return Err(JpegError::RectOutOfBounds {
3812 rect: roi,
3813 width: self.info.dimensions.0,
3814 height: self.info.dimensions.1,
3815 });
3816 }
3817 if matches!(output, Extended12Output::Rgb16) {
3818 match self.info.color_space {
3819 ColorSpace::Rgb => {
3820 let sampling = extended12_color_sampling(&self.plan, self.info.sof_kind)?;
3821 return match sampling {
3822 Extended12ColorSampling::S444 => self
3823 .decode_extended12_color444_region_into(
3824 out,
3825 stride,
3826 roi,
3827 downscale,
3828 Extended12RgbProjection::Identity,
3829 ),
3830 Extended12ColorSampling::S422 | Extended12ColorSampling::S420 => self
3831 .decode_extended12_color_subsampled_region_into(
3832 out,
3833 stride,
3834 roi,
3835 downscale,
3836 sampling,
3837 Extended12RgbProjection::Identity,
3838 ),
3839 };
3840 }
3841 ColorSpace::YCbCr => {
3842 let sampling = extended12_color_sampling(&self.plan, self.info.sof_kind)?;
3843 return match sampling {
3844 Extended12ColorSampling::S444 => self
3845 .decode_extended12_color444_region_into(
3846 out,
3847 stride,
3848 roi,
3849 downscale,
3850 Extended12RgbProjection::YCbCr,
3851 ),
3852 Extended12ColorSampling::S422 | Extended12ColorSampling::S420 => self
3853 .decode_extended12_color_subsampled_region_into(
3854 out,
3855 stride,
3856 roi,
3857 downscale,
3858 sampling,
3859 Extended12RgbProjection::YCbCr,
3860 ),
3861 };
3862 }
3863 ColorSpace::Cmyk | ColorSpace::Ycck => {
3864 let sampling =
3865 extended12_four_component_sampling(&self.plan, self.info.sof_kind)?;
3866 return match sampling {
3867 Extended12ColorSampling::S444 => self
3868 .decode_extended12_four_component444_region_into(
3869 out, stride, roi, downscale,
3870 ),
3871 Extended12ColorSampling::S422 | Extended12ColorSampling::S420 => self
3872 .decode_extended12_four_component_subsampled_region_into(
3873 out, stride, roi, downscale, sampling,
3874 ),
3875 };
3876 }
3877 ColorSpace::Grayscale => {}
3878 }
3879 }
3880 if self.info.color_space != ColorSpace::Grayscale || self.plan.components.len() != 1 {
3881 return Err(JpegError::NotImplemented {
3882 sof: self.info.sof_kind,
3883 });
3884 }
3885
3886 let output_rect = scaled_rect_covering(roi, downscale)?;
3887 let scan_bytes = &self.bytes[self.plan.scan_offset..];
3888 let component = &self.plan.components[0];
3889 let (width, height) = self.info.dimensions;
3890 let mcu_cols = width.div_ceil(8);
3891 let mcu_rows = height.div_ceil(8);
3892 let mut br = BitReader::new(scan_bytes);
3893 let mut prev_dc = 0i32;
3894 let mut coeff = CoefficientBlock::default();
3895 let mut pixels = [0u16; 64];
3896 let restart = u32::from(self.plan.restart_interval.unwrap_or(0));
3897 let mut mcus_since_restart = 0u32;
3898 let mut expected_rst = 0u8;
3899 let total_mcus = mcu_cols * mcu_rows;
3900 let write_region = Extended12WriteRegion {
3901 output_rect,
3902 dimensions: (width, height),
3903 downscale,
3904 output,
3905 };
3906
3907 for mcu_y in 0..mcu_rows {
3908 for mcu_x in 0..mcu_cols {
3909 let current_mcu = mcu_y * mcu_cols + mcu_x;
3910 if restart > 0 && mcus_since_restart == restart {
3911 consume_extended12_restart(
3912 &mut br,
3913 current_mcu,
3914 total_mcus,
3915 &mut expected_rst,
3916 )?;
3917 prev_dc = 0;
3918 mcus_since_restart = 0;
3919 }
3920 decode_extended12_block_pixels(
3921 &mut br,
3922 component,
3923 &mut prev_dc,
3924 &mut coeff,
3925 &mut pixels,
3926 )?;
3927 write_extended12_block_region(
3928 out,
3929 stride,
3930 write_region,
3931 (mcu_x * 8, mcu_y * 8),
3932 &pixels,
3933 );
3934 mcus_since_restart += 1;
3935 }
3936 }
3937
3938 let scan_warnings = finish_scan(&mut br, true)?;
3939 Ok(DecodeOutcome {
3940 decoded: roi,
3941 warnings: merged_warnings(&self.warnings, scan_warnings),
3942 })
3943 }
3944
3945 fn decode_extended12_color444_region_into(
3946 &self,
3947 out: &mut [u8],
3948 stride: usize,
3949 roi: Rect,
3950 downscale: DownscaleFactor,
3951 projection: Extended12RgbProjection,
3952 ) -> Result<DecodeOutcome, JpegError> {
3953 validate_extended12_color444_plan(&self.plan, self.info.sof_kind)?;
3954
3955 let output_rect = scaled_rect_covering(roi, downscale)?;
3956 let scan_bytes = &self.bytes[self.plan.scan_offset..];
3957 let (width, height) = self.info.dimensions;
3958 let mcu_cols = width.div_ceil(8);
3959 let mcu_rows = height.div_ceil(8);
3960 let mut br = BitReader::new(scan_bytes);
3961 let mut prev_dc = [0i32; 3];
3962 let mut coeffs: [CoefficientBlock; 3] =
3963 core::array::from_fn(|_| CoefficientBlock::default());
3964 let mut pixels = [[0u16; 64]; 3];
3965 let restart = u32::from(self.plan.restart_interval.unwrap_or(0));
3966 let mut mcus_since_restart = 0u32;
3967 let mut expected_rst = 0u8;
3968 let total_mcus = mcu_cols * mcu_rows;
3969 let write_region = Extended12WriteRegion {
3970 output_rect,
3971 dimensions: (width, height),
3972 downscale,
3973 output: Extended12Output::Rgb16,
3974 };
3975
3976 for mcu_y in 0..mcu_rows {
3977 for mcu_x in 0..mcu_cols {
3978 let current_mcu = mcu_y * mcu_cols + mcu_x;
3979 if restart > 0 && mcus_since_restart == restart {
3980 consume_extended12_restart(
3981 &mut br,
3982 current_mcu,
3983 total_mcus,
3984 &mut expected_rst,
3985 )?;
3986 prev_dc.fill(0);
3987 mcus_since_restart = 0;
3988 }
3989 for component in &self.plan.components {
3990 let output_index = component.output_index;
3991 decode_extended12_block_pixels(
3992 &mut br,
3993 component,
3994 &mut prev_dc[output_index],
3995 &mut coeffs[output_index],
3996 &mut pixels[output_index],
3997 )?;
3998 }
3999 write_extended12_rgb_block_region(
4000 out,
4001 stride,
4002 write_region,
4003 projection,
4004 (mcu_x * 8, mcu_y * 8),
4005 &pixels,
4006 );
4007 mcus_since_restart += 1;
4008 }
4009 }
4010
4011 let scan_warnings = finish_scan(&mut br, true)?;
4012 Ok(DecodeOutcome {
4013 decoded: roi,
4014 warnings: merged_warnings(&self.warnings, scan_warnings),
4015 })
4016 }
4017
4018 fn decode_extended12_color_subsampled_region_into(
4019 &self,
4020 out: &mut [u8],
4021 stride: usize,
4022 roi: Rect,
4023 downscale: DownscaleFactor,
4024 sampling: Extended12ColorSampling,
4025 projection: Extended12RgbProjection,
4026 ) -> Result<DecodeOutcome, JpegError> {
4027 debug_assert!(matches!(
4028 sampling,
4029 Extended12ColorSampling::S422 | Extended12ColorSampling::S420
4030 ));
4031 if extended12_color_sampling(&self.plan, self.info.sof_kind)? != sampling {
4032 return Err(JpegError::NotImplemented {
4033 sof: self.info.sof_kind,
4034 });
4035 }
4036
4037 let output_rect = scaled_rect_covering(roi, downscale)?;
4038 let scan_bytes = &self.bytes[self.plan.scan_offset..];
4039 let (planes, scan_warnings) =
4040 decode_extended12_color_planes(&self.plan, scan_bytes, self.info.sof_kind)?;
4041 let write_region = Extended12WriteRegion {
4042 output_rect,
4043 dimensions: self.info.dimensions,
4044 downscale,
4045 output: Extended12Output::Rgb16,
4046 };
4047 match sampling {
4048 Extended12ColorSampling::S444 => unreachable!("4:4:4 path is handled directly"),
4049 Extended12ColorSampling::S422 => write_extended12_color422_planes_region(
4050 out,
4051 stride,
4052 write_region,
4053 projection,
4054 &planes,
4055 ),
4056 Extended12ColorSampling::S420 => write_extended12_color420_planes_region(
4057 out,
4058 stride,
4059 write_region,
4060 projection,
4061 &planes,
4062 ),
4063 }
4064
4065 Ok(DecodeOutcome {
4066 decoded: roi,
4067 warnings: merged_warnings(&self.warnings, scan_warnings),
4068 })
4069 }
4070
4071 fn decode_extended12_four_component444_region_into(
4072 &self,
4073 out: &mut [u8],
4074 stride: usize,
4075 roi: Rect,
4076 downscale: DownscaleFactor,
4077 ) -> Result<DecodeOutcome, JpegError> {
4078 validate_extended12_four_component444_plan(&self.plan, self.info.sof_kind)?;
4079
4080 let output_rect = scaled_rect_covering(roi, downscale)?;
4081 let scan_bytes = &self.bytes[self.plan.scan_offset..];
4082 let (width, height) = self.info.dimensions;
4083 let mcu_cols = width.div_ceil(8);
4084 let mcu_rows = height.div_ceil(8);
4085 let mut br = BitReader::new(scan_bytes);
4086 let mut prev_dc = [0i32; 4];
4087 let mut coeffs: [CoefficientBlock; 4] =
4088 core::array::from_fn(|_| CoefficientBlock::default());
4089 let mut pixels = [[0u16; 64]; 4];
4090 let restart = u32::from(self.plan.restart_interval.unwrap_or(0));
4091 let mut mcus_since_restart = 0u32;
4092 let mut expected_rst = 0u8;
4093 let total_mcus = mcu_cols * mcu_rows;
4094 let write_region = Extended12WriteRegion {
4095 output_rect,
4096 dimensions: (width, height),
4097 downscale,
4098 output: Extended12Output::Rgb16,
4099 };
4100
4101 for mcu_y in 0..mcu_rows {
4102 for mcu_x in 0..mcu_cols {
4103 let current_mcu = mcu_y * mcu_cols + mcu_x;
4104 if restart > 0 && mcus_since_restart == restart {
4105 consume_extended12_restart(
4106 &mut br,
4107 current_mcu,
4108 total_mcus,
4109 &mut expected_rst,
4110 )?;
4111 prev_dc.fill(0);
4112 mcus_since_restart = 0;
4113 }
4114 for component in &self.plan.components {
4115 let output_index = component.output_index;
4116 decode_extended12_block_pixels(
4117 &mut br,
4118 component,
4119 &mut prev_dc[output_index],
4120 &mut coeffs[output_index],
4121 &mut pixels[output_index],
4122 )?;
4123 }
4124 write_extended12_four_component_block_region(
4125 out,
4126 stride,
4127 write_region,
4128 self.info.color_space,
4129 (mcu_x * 8, mcu_y * 8),
4130 &pixels,
4131 );
4132 mcus_since_restart += 1;
4133 }
4134 }
4135
4136 let scan_warnings = finish_scan(&mut br, true)?;
4137 Ok(DecodeOutcome {
4138 decoded: roi,
4139 warnings: merged_warnings(&self.warnings, scan_warnings),
4140 })
4141 }
4142
4143 fn decode_extended12_four_component_subsampled_region_into(
4144 &self,
4145 out: &mut [u8],
4146 stride: usize,
4147 roi: Rect,
4148 downscale: DownscaleFactor,
4149 sampling: Extended12ColorSampling,
4150 ) -> Result<DecodeOutcome, JpegError> {
4151 debug_assert!(matches!(
4152 sampling,
4153 Extended12ColorSampling::S422 | Extended12ColorSampling::S420
4154 ));
4155 if extended12_four_component_sampling(&self.plan, self.info.sof_kind)? != sampling {
4156 return Err(JpegError::NotImplemented {
4157 sof: self.info.sof_kind,
4158 });
4159 }
4160
4161 let output_rect = scaled_rect_covering(roi, downscale)?;
4162 let scan_bytes = &self.bytes[self.plan.scan_offset..];
4163 let (planes, scan_warnings) =
4164 decode_extended12_four_component_planes(&self.plan, scan_bytes, self.info.sof_kind)?;
4165 write_extended12_four_component_planes_region(
4166 out,
4167 stride,
4168 Extended12WriteRegion {
4169 output_rect,
4170 dimensions: self.info.dimensions,
4171 downscale,
4172 output: Extended12Output::Rgb16,
4173 },
4174 self.info.color_space,
4175 sampling,
4176 &planes,
4177 );
4178
4179 Ok(DecodeOutcome {
4180 decoded: roi,
4181 warnings: merged_warnings(&self.warnings, scan_warnings),
4182 })
4183 }
4184}
4185
4186#[derive(Debug, Clone, Copy)]
4187enum Extended12Output {
4188 Gray16,
4189 Rgb16,
4190}
4191
4192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4193enum Extended12ColorSampling {
4194 S444,
4195 S422,
4196 S420,
4197}
4198
4199fn lossless_color_sampling(info: &Info) -> Option<LosslessColorSampling> {
4200 if info.sampling.len() != 3 {
4201 return None;
4202 }
4203 match (
4204 info.sampling.max_h,
4205 info.sampling.max_v,
4206 info.sampling.components(),
4207 ) {
4208 (1, 1, &[(1, 1), (1, 1), (1, 1)]) => Some(LosslessColorSampling::S444),
4209 (2, 1, &[(2, 1), (1, 1), (1, 1)])
4210 if matches!(info.bit_depth, 8 | 16) && info.dimensions.0.is_multiple_of(2) =>
4211 {
4212 Some(LosslessColorSampling::S422)
4213 }
4214 (2, 2, &[(2, 2), (1, 1), (1, 1)])
4215 if matches!(info.bit_depth, 8 | 16)
4216 && info.dimensions.0.is_multiple_of(2)
4217 && info.dimensions.1.is_multiple_of(2) =>
4218 {
4219 Some(LosslessColorSampling::S420)
4220 }
4221 _ => None,
4222 }
4223}
4224
4225#[derive(Debug, Clone, Copy)]
4226enum Extended12RgbProjection {
4227 Identity,
4228 YCbCr,
4229}
4230
4231#[derive(Debug, Clone, Copy)]
4232struct Extended12WriteRegion {
4233 output_rect: Rect,
4234 dimensions: (u32, u32),
4235 downscale: DownscaleFactor,
4236 output: Extended12Output,
4237}
4238
4239struct Extended12Plane {
4240 pixels: Vec<u16>,
4241 stride: usize,
4242 width: usize,
4243}
4244
4245fn decode_extended12_block_pixels(
4246 br: &mut BitReader<'_>,
4247 component: &PreparedComponentPlan,
4248 prev_dc: &mut i32,
4249 coeff: &mut CoefficientBlock,
4250 pixels: &mut [u16; 64],
4251) -> Result<(), JpegError> {
4252 let activity = decode_block_with_activity(
4253 br,
4254 &component.dc_table,
4255 &component.ac_table,
4256 prev_dc,
4257 component.quant.as_ref(),
4258 coeff,
4259 )?;
4260 match activity {
4261 BlockActivity::DcOnly => {
4262 pixels.fill(crate::idct::idct_islow_12bit_dc_only_sample(
4263 coeff.dc_coeff(),
4264 ));
4265 }
4266 BlockActivity::BottomHalfZero | BlockActivity::General => {
4267 crate::idct::idct_islow_12bit(coeff.coefficients(), pixels);
4268 }
4269 }
4270 Ok(())
4271}
4272
4273fn decode_extended12_color_planes(
4274 plan: &PreparedDecodePlan,
4275 scan_bytes: &[u8],
4276 sof: SofKind,
4277) -> Result<([Extended12Plane; 3], Vec<Warning>), JpegError> {
4278 if plan.components.len() != 3 {
4279 return Err(JpegError::NotImplemented { sof });
4280 }
4281 let mut planes = extended12_planes_for_sequential_plan(plan, sof)?;
4282 let mut br = BitReader::new(scan_bytes);
4283 let mut prev_dc = [0i32; 3];
4284 let mut coeffs: [CoefficientBlock; 3] = core::array::from_fn(|_| CoefficientBlock::default());
4285 let mut pixels = [[0u16; 64]; 3];
4286 let mcu_cols = plan
4287 .dimensions
4288 .0
4289 .div_ceil(u32::from(plan.sampling.max_h) * 8);
4290 let mcu_rows = plan
4291 .dimensions
4292 .1
4293 .div_ceil(u32::from(plan.sampling.max_v) * 8);
4294 let restart = u32::from(plan.restart_interval.unwrap_or(0));
4295 let mut mcus_since_restart = 0u32;
4296 let mut expected_rst = 0u8;
4297 let total_mcus = mcu_cols * mcu_rows;
4298
4299 for mcu_y in 0..mcu_rows {
4300 for mcu_x in 0..mcu_cols {
4301 let current_mcu = mcu_y * mcu_cols + mcu_x;
4302 if restart > 0 && mcus_since_restart == restart {
4303 consume_extended12_restart(&mut br, current_mcu, total_mcus, &mut expected_rst)?;
4304 prev_dc.fill(0);
4305 mcus_since_restart = 0;
4306 }
4307 for component in &plan.components {
4308 let output_index = component.output_index;
4309 if output_index > 2 {
4310 return Err(JpegError::NotImplemented { sof });
4311 }
4312 for by in 0..u32::from(component.v) {
4313 for bx in 0..u32::from(component.h) {
4314 decode_extended12_block_pixels(
4315 &mut br,
4316 component,
4317 &mut prev_dc[output_index],
4318 &mut coeffs[output_index],
4319 &mut pixels[output_index],
4320 )?;
4321 deposit_extended12_block(
4322 &mut planes[output_index],
4323 (mcu_x * u32::from(component.h) + bx) as usize * 8,
4324 (mcu_y * u32::from(component.v) + by) as usize * 8,
4325 &pixels[output_index],
4326 );
4327 }
4328 }
4329 }
4330 mcus_since_restart += 1;
4331 }
4332 }
4333
4334 let scan_warnings = finish_scan(&mut br, true)?;
4335 Ok((planes, scan_warnings))
4336}
4337
4338fn decode_extended12_four_component_planes(
4339 plan: &PreparedDecodePlan,
4340 scan_bytes: &[u8],
4341 sof: SofKind,
4342) -> Result<([Extended12Plane; 4], Vec<Warning>), JpegError> {
4343 if plan.components.len() != 4 {
4344 return Err(JpegError::NotImplemented { sof });
4345 }
4346 let mut planes = extended12_four_component_planes_for_sequential_plan(plan, sof)?;
4347 let mut br = BitReader::new(scan_bytes);
4348 let mut prev_dc = [0i32; 4];
4349 let mut coeffs: [CoefficientBlock; 4] = core::array::from_fn(|_| CoefficientBlock::default());
4350 let mut pixels = [[0u16; 64]; 4];
4351 let mcu_cols = plan
4352 .dimensions
4353 .0
4354 .div_ceil(u32::from(plan.sampling.max_h) * 8);
4355 let mcu_rows = plan
4356 .dimensions
4357 .1
4358 .div_ceil(u32::from(plan.sampling.max_v) * 8);
4359 let restart = u32::from(plan.restart_interval.unwrap_or(0));
4360 let mut mcus_since_restart = 0u32;
4361 let mut expected_rst = 0u8;
4362 let total_mcus = mcu_cols * mcu_rows;
4363
4364 for mcu_y in 0..mcu_rows {
4365 for mcu_x in 0..mcu_cols {
4366 let current_mcu = mcu_y * mcu_cols + mcu_x;
4367 if restart > 0 && mcus_since_restart == restart {
4368 consume_extended12_restart(&mut br, current_mcu, total_mcus, &mut expected_rst)?;
4369 prev_dc.fill(0);
4370 mcus_since_restart = 0;
4371 }
4372 for component in &plan.components {
4373 let output_index = component.output_index;
4374 if output_index > 3 {
4375 return Err(JpegError::NotImplemented { sof });
4376 }
4377 for by in 0..u32::from(component.v) {
4378 for bx in 0..u32::from(component.h) {
4379 decode_extended12_block_pixels(
4380 &mut br,
4381 component,
4382 &mut prev_dc[output_index],
4383 &mut coeffs[output_index],
4384 &mut pixels[output_index],
4385 )?;
4386 deposit_extended12_block(
4387 &mut planes[output_index],
4388 (mcu_x * u32::from(component.h) + bx) as usize * 8,
4389 (mcu_y * u32::from(component.v) + by) as usize * 8,
4390 &pixels[output_index],
4391 );
4392 }
4393 }
4394 }
4395 mcus_since_restart += 1;
4396 }
4397 }
4398
4399 let scan_warnings = finish_scan(&mut br, true)?;
4400 Ok((planes, scan_warnings))
4401}
4402
4403fn extended12_planes_for_sequential_plan(
4404 plan: &PreparedDecodePlan,
4405 sof: SofKind,
4406) -> Result<[Extended12Plane; 3], JpegError> {
4407 let mcu_cols = plan
4408 .dimensions
4409 .0
4410 .div_ceil(u32::from(plan.sampling.max_h) * 8);
4411 let mcu_rows = plan
4412 .dimensions
4413 .1
4414 .div_ceil(u32::from(plan.sampling.max_v) * 8);
4415 let mut widths = [0usize; 3];
4416 let mut strides = [0usize; 3];
4417 let mut heights = [0usize; 3];
4418 let mut lens = [0usize; 3];
4419 for component in &plan.components {
4420 if component.output_index > 2 {
4421 return Err(JpegError::NotImplemented { sof });
4422 }
4423 widths[component.output_index] =
4424 plan.dimensions
4425 .0
4426 .saturating_mul(u32::from(component.h))
4427 .div_ceil(u32::from(plan.sampling.max_h)) as usize;
4428 strides[component.output_index] =
4429 checked_scratch_len(&[mcu_cols as usize, usize::from(component.h), 8])?;
4430 heights[component.output_index] =
4431 checked_scratch_len(&[mcu_rows as usize, usize::from(component.v), 8])?;
4432 lens[component.output_index] = checked_scratch_len(&[
4433 strides[component.output_index],
4434 heights[component.output_index],
4435 core::mem::size_of::<u16>(),
4436 ])? / core::mem::size_of::<u16>();
4437 }
4438 Ok(core::array::from_fn(|index| Extended12Plane {
4439 pixels: vec![0u16; lens[index]],
4440 stride: strides[index],
4441 width: widths[index],
4442 }))
4443}
4444
4445fn extended12_four_component_planes_for_sequential_plan(
4446 plan: &PreparedDecodePlan,
4447 sof: SofKind,
4448) -> Result<[Extended12Plane; 4], JpegError> {
4449 let mcu_cols = plan
4450 .dimensions
4451 .0
4452 .div_ceil(u32::from(plan.sampling.max_h) * 8);
4453 let mcu_rows = plan
4454 .dimensions
4455 .1
4456 .div_ceil(u32::from(plan.sampling.max_v) * 8);
4457 let mut widths = [0usize; 4];
4458 let mut strides = [0usize; 4];
4459 let mut heights = [0usize; 4];
4460 let mut lens = [0usize; 4];
4461 for component in &plan.components {
4462 if component.output_index > 3 {
4463 return Err(JpegError::NotImplemented { sof });
4464 }
4465 widths[component.output_index] =
4466 plan.dimensions
4467 .0
4468 .saturating_mul(u32::from(component.h))
4469 .div_ceil(u32::from(plan.sampling.max_h)) as usize;
4470 strides[component.output_index] =
4471 checked_scratch_len(&[mcu_cols as usize, usize::from(component.h), 8])?;
4472 heights[component.output_index] =
4473 checked_scratch_len(&[mcu_rows as usize, usize::from(component.v), 8])?;
4474 lens[component.output_index] = checked_scratch_len(&[
4475 strides[component.output_index],
4476 heights[component.output_index],
4477 core::mem::size_of::<u16>(),
4478 ])? / core::mem::size_of::<u16>();
4479 }
4480 Ok(core::array::from_fn(|index| Extended12Plane {
4481 pixels: vec![0u16; lens[index]],
4482 stride: strides[index],
4483 width: widths[index],
4484 }))
4485}
4486
4487fn render_progressive12_color_planes(
4488 plan: &PreparedProgressivePlan,
4489 coeffs: &[Vec<[i32; 64]>],
4490) -> Result<[Extended12Plane; 3], JpegError> {
4491 let mut planes = progressive12_color_planes(plan)?;
4492 let mut dequant = [0i16; 64];
4493 let mut pixels = [0u16; 64];
4494 for (component_index, component) in plan.components.iter().enumerate() {
4495 let output_index = component.output_index;
4496 if output_index > 2 {
4497 return Err(JpegError::NotImplemented {
4498 sof: SofKind::Progressive12,
4499 });
4500 }
4501 for by in 0..component.block_rows as usize {
4502 for bx in 0..component.block_cols as usize {
4503 let block_index = by * component.block_cols as usize + bx;
4504 dequantize_progressive12_block(
4505 &coeffs[component_index][block_index],
4506 &component.quant,
4507 &mut dequant,
4508 );
4509 if dequant[1..].iter().all(|&coeff| coeff == 0) {
4510 pixels.fill(crate::idct::idct_islow_12bit_dc_only_sample(dequant[0]));
4511 } else {
4512 crate::idct::idct_islow_12bit(&dequant, &mut pixels);
4513 }
4514 deposit_extended12_block(&mut planes[output_index], bx * 8, by * 8, &pixels);
4515 }
4516 }
4517 }
4518 Ok(planes)
4519}
4520
4521fn render_progressive12_four_component_planes(
4522 plan: &PreparedProgressivePlan,
4523 coeffs: &[Vec<[i32; 64]>],
4524) -> Result<[Extended12Plane; 4], JpegError> {
4525 let mut planes = progressive12_four_component_planes(plan)?;
4526 let mut dequant = [0i16; 64];
4527 let mut pixels = [0u16; 64];
4528 for (component_index, component) in plan.components.iter().enumerate() {
4529 let output_index = component.output_index;
4530 if output_index > 3 {
4531 return Err(JpegError::NotImplemented {
4532 sof: SofKind::Progressive12,
4533 });
4534 }
4535 for by in 0..component.block_rows as usize {
4536 for bx in 0..component.block_cols as usize {
4537 let block_index = by * component.block_cols as usize + bx;
4538 dequantize_progressive12_block(
4539 &coeffs[component_index][block_index],
4540 &component.quant,
4541 &mut dequant,
4542 );
4543 if dequant[1..].iter().all(|&coeff| coeff == 0) {
4544 pixels.fill(crate::idct::idct_islow_12bit_dc_only_sample(dequant[0]));
4545 } else {
4546 crate::idct::idct_islow_12bit(&dequant, &mut pixels);
4547 }
4548 deposit_extended12_block(&mut planes[output_index], bx * 8, by * 8, &pixels);
4549 }
4550 }
4551 }
4552 Ok(planes)
4553}
4554
4555fn progressive12_color_planes(
4556 plan: &PreparedProgressivePlan,
4557) -> Result<[Extended12Plane; 3], JpegError> {
4558 let mut widths = [0usize; 3];
4559 let mut strides = [0usize; 3];
4560 let mut heights = [0usize; 3];
4561 for component in &plan.components {
4562 if component.output_index > 2 {
4563 return Err(JpegError::NotImplemented {
4564 sof: SofKind::Progressive12,
4565 });
4566 }
4567 widths[component.output_index] = component.sample_width as usize;
4568 strides[component.output_index] = component.block_cols as usize * 8;
4569 heights[component.output_index] = component.block_rows as usize * 8;
4570 }
4571 Ok(core::array::from_fn(|index| Extended12Plane {
4572 pixels: vec![0u16; strides[index] * heights[index]],
4573 stride: strides[index],
4574 width: widths[index],
4575 }))
4576}
4577
4578fn progressive12_four_component_planes(
4579 plan: &PreparedProgressivePlan,
4580) -> Result<[Extended12Plane; 4], JpegError> {
4581 let mut widths = [0usize; 4];
4582 let mut strides = [0usize; 4];
4583 let mut heights = [0usize; 4];
4584 for component in &plan.components {
4585 if component.output_index > 3 {
4586 return Err(JpegError::NotImplemented {
4587 sof: SofKind::Progressive12,
4588 });
4589 }
4590 widths[component.output_index] = component.sample_width as usize;
4591 strides[component.output_index] = component.block_cols as usize * 8;
4592 heights[component.output_index] = component.block_rows as usize * 8;
4593 }
4594 Ok(core::array::from_fn(|index| Extended12Plane {
4595 pixels: vec![0u16; strides[index] * heights[index]],
4596 stride: strides[index],
4597 width: widths[index],
4598 }))
4599}
4600
4601fn deposit_extended12_block(plane: &mut Extended12Plane, x: usize, y: usize, block: &[u16; 64]) {
4602 for row in 0..8 {
4603 let dst_start = (y + row) * plane.stride + x;
4604 let src_start = row * 8;
4605 plane.pixels[dst_start..dst_start + 8].copy_from_slice(&block[src_start..src_start + 8]);
4606 }
4607}
4608
4609fn validate_extended12_color444_plan(
4610 plan: &PreparedDecodePlan,
4611 sof: SofKind,
4612) -> Result<(), JpegError> {
4613 if plan.components.len() != 3 || plan.sampling.max_h != 1 || plan.sampling.max_v != 1 {
4614 return Err(JpegError::NotImplemented { sof });
4615 }
4616 let mut seen = [false; 3];
4617 for component in &plan.components {
4618 if component.h != 1 || component.v != 1 || component.output_index > 2 {
4619 return Err(JpegError::NotImplemented { sof });
4620 }
4621 if seen[component.output_index] {
4622 return Err(JpegError::NotImplemented { sof });
4623 }
4624 seen[component.output_index] = true;
4625 }
4626 if seen.iter().any(|&present| !present) {
4627 return Err(JpegError::NotImplemented { sof });
4628 }
4629 Ok(())
4630}
4631
4632fn validate_extended12_four_component444_plan(
4633 plan: &PreparedDecodePlan,
4634 sof: SofKind,
4635) -> Result<(), JpegError> {
4636 if extended12_four_component_sampling(plan, sof)? != Extended12ColorSampling::S444 {
4637 return Err(JpegError::NotImplemented { sof });
4638 }
4639 Ok(())
4640}
4641
4642fn extended12_color_sampling(
4643 plan: &PreparedDecodePlan,
4644 sof: SofKind,
4645) -> Result<Extended12ColorSampling, JpegError> {
4646 if plan.components.len() != 3 {
4647 return Err(JpegError::NotImplemented { sof });
4648 }
4649 let components = color_component_sampling_from_sequential(plan, sof)?;
4650 color_sampling_from_components(plan.sampling.max_h, plan.sampling.max_v, components, sof)
4651}
4652
4653fn extended12_four_component_sampling(
4654 plan: &PreparedDecodePlan,
4655 sof: SofKind,
4656) -> Result<Extended12ColorSampling, JpegError> {
4657 if plan.components.len() != 4 {
4658 return Err(JpegError::NotImplemented { sof });
4659 }
4660 let components = four_component_sampling_from_sequential(plan, sof)?;
4661 four_component_sampling_from_components(
4662 plan.sampling.max_h,
4663 plan.sampling.max_v,
4664 components,
4665 sof,
4666 )
4667}
4668
4669fn color_component_sampling_from_sequential(
4670 plan: &PreparedDecodePlan,
4671 sof: SofKind,
4672) -> Result<[(u8, u8); 3], JpegError> {
4673 let mut components = [(0u8, 0u8); 3];
4674 let mut seen = [false; 3];
4675 for component in &plan.components {
4676 if component.output_index > 2 || seen[component.output_index] {
4677 return Err(JpegError::NotImplemented { sof });
4678 }
4679 seen[component.output_index] = true;
4680 components[component.output_index] = (component.h, component.v);
4681 }
4682 if seen.iter().any(|&present| !present) {
4683 return Err(JpegError::NotImplemented { sof });
4684 }
4685 Ok(components)
4686}
4687
4688fn four_component_sampling_from_sequential(
4689 plan: &PreparedDecodePlan,
4690 sof: SofKind,
4691) -> Result<[(u8, u8); 4], JpegError> {
4692 let mut components = [(0u8, 0u8); 4];
4693 let mut seen = [false; 4];
4694 for component in &plan.components {
4695 if component.output_index > 3 || seen[component.output_index] {
4696 return Err(JpegError::NotImplemented { sof });
4697 }
4698 seen[component.output_index] = true;
4699 components[component.output_index] = (component.h, component.v);
4700 }
4701 if seen.iter().any(|&present| !present) {
4702 return Err(JpegError::NotImplemented { sof });
4703 }
4704 Ok(components)
4705}
4706
4707fn progressive_color_sampling(
4708 plan: &PreparedProgressivePlan,
4709 sof: SofKind,
4710) -> Result<Extended12ColorSampling, JpegError> {
4711 if plan.components.len() != 3 {
4712 return Err(JpegError::NotImplemented { sof });
4713 }
4714 let components = color_component_sampling_from_progressive(plan, sof)?;
4715 color_sampling_from_components(plan.sampling.max_h, plan.sampling.max_v, components, sof)
4716}
4717
4718fn progressive_four_component_sampling(
4719 plan: &PreparedProgressivePlan,
4720 sof: SofKind,
4721) -> Result<Extended12ColorSampling, JpegError> {
4722 if plan.components.len() != 4 {
4723 return Err(JpegError::NotImplemented { sof });
4724 }
4725 let components = four_component_sampling_from_progressive(plan, sof)?;
4726 four_component_sampling_from_components(
4727 plan.sampling.max_h,
4728 plan.sampling.max_v,
4729 components,
4730 sof,
4731 )
4732}
4733
4734fn color_component_sampling_from_progressive(
4735 plan: &PreparedProgressivePlan,
4736 sof: SofKind,
4737) -> Result<[(u8, u8); 3], JpegError> {
4738 let mut components = [(0u8, 0u8); 3];
4739 let mut seen = [false; 3];
4740 for component in &plan.components {
4741 if component.output_index > 2 || seen[component.output_index] {
4742 return Err(JpegError::NotImplemented { sof });
4743 }
4744 seen[component.output_index] = true;
4745 components[component.output_index] = (component.h, component.v);
4746 }
4747 if seen.iter().any(|&present| !present) {
4748 return Err(JpegError::NotImplemented { sof });
4749 }
4750 Ok(components)
4751}
4752
4753fn four_component_sampling_from_progressive(
4754 plan: &PreparedProgressivePlan,
4755 sof: SofKind,
4756) -> Result<[(u8, u8); 4], JpegError> {
4757 let mut components = [(0u8, 0u8); 4];
4758 let mut seen = [false; 4];
4759 for component in &plan.components {
4760 if component.output_index > 3 || seen[component.output_index] {
4761 return Err(JpegError::NotImplemented { sof });
4762 }
4763 seen[component.output_index] = true;
4764 components[component.output_index] = (component.h, component.v);
4765 }
4766 if seen.iter().any(|&present| !present) {
4767 return Err(JpegError::NotImplemented { sof });
4768 }
4769 Ok(components)
4770}
4771
4772fn color_sampling_from_components(
4773 max_h: u8,
4774 max_v: u8,
4775 components: [(u8, u8); 3],
4776 sof: SofKind,
4777) -> Result<Extended12ColorSampling, JpegError> {
4778 match (max_h, max_v, components) {
4779 (1, 1, [(1, 1), (1, 1), (1, 1)]) => Ok(Extended12ColorSampling::S444),
4780 (2, 1, [(2, 1), (1, 1), (1, 1)]) => Ok(Extended12ColorSampling::S422),
4781 (2, 2, [(2, 2), (1, 1), (1, 1)]) => Ok(Extended12ColorSampling::S420),
4782 _ => Err(JpegError::NotImplemented { sof }),
4783 }
4784}
4785
4786fn four_component_sampling_from_components(
4787 max_h: u8,
4788 max_v: u8,
4789 components: [(u8, u8); 4],
4790 sof: SofKind,
4791) -> Result<Extended12ColorSampling, JpegError> {
4792 match (max_h, max_v, components) {
4793 (1, 1, [(1, 1), (1, 1), (1, 1), (1, 1)]) => Ok(Extended12ColorSampling::S444),
4794 (2, 1, [(2, 1), (1, 1), (1, 1), (1, 1)]) => Ok(Extended12ColorSampling::S422),
4795 (2, 2, [(2, 2), (1, 1), (1, 1), (1, 1)]) => Ok(Extended12ColorSampling::S420),
4796 _ => Err(JpegError::NotImplemented { sof }),
4797 }
4798}
4799
4800fn progressive_color_component_indices(
4801 plan: &PreparedProgressivePlan,
4802) -> Result<[usize; 3], JpegError> {
4803 let mut indices = [usize::MAX; 3];
4804 for (component_index, component) in plan.components.iter().enumerate() {
4805 if component.output_index < 3 {
4806 if indices[component.output_index] != usize::MAX {
4807 return Err(JpegError::NotImplemented {
4808 sof: SofKind::Progressive12,
4809 });
4810 }
4811 indices[component.output_index] = component_index;
4812 }
4813 }
4814 if indices.contains(&usize::MAX) {
4815 return Err(JpegError::NotImplemented {
4816 sof: SofKind::Progressive12,
4817 });
4818 }
4819 Ok(indices)
4820}
4821
4822fn dequantize_progressive12_block(coeffs: &[i32; 64], quant: &[u16; 64], out: &mut [i16; 64]) {
4823 out.fill(0);
4824 for k in 0..64 {
4825 let natural_idx = usize::from(ZIGZAG[k]);
4826 let value = coeffs[natural_idx].wrapping_mul(i32::from(quant[k]));
4827 out[natural_idx] = value.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
4828 }
4829}
4830
4831fn write_extended12_rgb_block_region(
4832 out: &mut [u8],
4833 stride: usize,
4834 region: Extended12WriteRegion,
4835 projection: Extended12RgbProjection,
4836 block_origin: (u32, u32),
4837 pixels: &[[u16; 64]; 3],
4838) {
4839 let (width, height) = region.dimensions;
4840 let (x0, y0) = block_origin;
4841 let block_x1 = (x0 + 8).min(width);
4842 let block_y1 = (y0 + 8).min(height);
4843 let denom = region.downscale.denominator();
4844 let output_rect = region.output_rect;
4845 for output_y in output_rect.y..output_rect.y + output_rect.h {
4846 let source_y = output_y.saturating_mul(denom).min(height - 1);
4847 if source_y < y0 || source_y >= block_y1 {
4848 continue;
4849 }
4850 let src_row = (source_y - y0) as usize;
4851 let dst_row = (output_y - output_rect.y) as usize;
4852 for output_x in output_rect.x..output_rect.x + output_rect.w {
4853 let source_x = output_x.saturating_mul(denom).min(width - 1);
4854 if source_x < x0 || source_x >= block_x1 {
4855 continue;
4856 }
4857 let src_col = (source_x - x0) as usize;
4858 let src_index = src_row * 8 + src_col;
4859 let dst_col = (output_x - output_rect.x) as usize;
4860 let dst_start = dst_row * stride + dst_col * 6;
4861 let dst = &mut out[dst_start..dst_start + 6];
4862 let (r, g, b) = match projection {
4863 Extended12RgbProjection::Identity => (
4864 pixels[0][src_index],
4865 pixels[1][src_index],
4866 pixels[2][src_index],
4867 ),
4868 Extended12RgbProjection::YCbCr => crate::color::ycbcr::ycbcr12_to_rgb16(
4869 pixels[0][src_index],
4870 pixels[1][src_index],
4871 pixels[2][src_index],
4872 ),
4873 };
4874 dst[0..2].copy_from_slice(&r.to_le_bytes());
4875 dst[2..4].copy_from_slice(&g.to_le_bytes());
4876 dst[4..6].copy_from_slice(&b.to_le_bytes());
4877 }
4878 }
4879}
4880
4881fn write_extended12_four_component_block_region(
4882 out: &mut [u8],
4883 stride: usize,
4884 region: Extended12WriteRegion,
4885 color_space: ColorSpace,
4886 block_origin: (u32, u32),
4887 pixels: &[[u16; 64]; 4],
4888) {
4889 let (width, height) = region.dimensions;
4890 let (x0, y0) = block_origin;
4891 let block_x1 = (x0 + 8).min(width);
4892 let block_y1 = (y0 + 8).min(height);
4893 let denom = region.downscale.denominator();
4894 let output_rect = region.output_rect;
4895 for output_y in output_rect.y..output_rect.y + output_rect.h {
4896 let source_y = output_y.saturating_mul(denom).min(height - 1);
4897 if source_y < y0 || source_y >= block_y1 {
4898 continue;
4899 }
4900 let src_row = (source_y - y0) as usize;
4901 let dst_row = (output_y - output_rect.y) as usize;
4902 for output_x in output_rect.x..output_rect.x + output_rect.w {
4903 let source_x = output_x.saturating_mul(denom).min(width - 1);
4904 if source_x < x0 || source_x >= block_x1 {
4905 continue;
4906 }
4907 let src_col = (source_x - x0) as usize;
4908 let src_index = src_row * 8 + src_col;
4909 let (r, g, b) = match color_space {
4910 ColorSpace::Cmyk => crate::color::cmyk::inverted_cmyk12_to_rgb16(
4911 pixels[0][src_index],
4912 pixels[1][src_index],
4913 pixels[2][src_index],
4914 pixels[3][src_index],
4915 ),
4916 ColorSpace::Ycck => crate::color::cmyk::ycck12_to_rgb16(
4917 pixels[0][src_index],
4918 pixels[1][src_index],
4919 pixels[2][src_index],
4920 pixels[3][src_index],
4921 ),
4922 _ => unreachable!("12-bit four-component path only accepts CMYK/YCCK"),
4923 };
4924 let dst_col = (output_x - output_rect.x) as usize;
4925 let dst_start = dst_row * stride + dst_col * 6;
4926 let dst = &mut out[dst_start..dst_start + 6];
4927 dst[0..2].copy_from_slice(&r.to_le_bytes());
4928 dst[2..4].copy_from_slice(&g.to_le_bytes());
4929 dst[4..6].copy_from_slice(&b.to_le_bytes());
4930 }
4931 }
4932}
4933
4934fn write_extended12_color422_planes_region(
4935 out: &mut [u8],
4936 stride: usize,
4937 region: Extended12WriteRegion,
4938 projection: Extended12RgbProjection,
4939 planes: &[Extended12Plane; 3],
4940) {
4941 let (width, height) = region.dimensions;
4942 let denom = region.downscale.denominator();
4943 let output_rect = region.output_rect;
4944 for output_y in output_rect.y..output_rect.y + output_rect.h {
4945 let source_y = output_y.saturating_mul(denom).min(height - 1) as usize;
4946 let dst_row = (output_y - output_rect.y) as usize;
4947 for output_x in output_rect.x..output_rect.x + output_rect.w {
4948 let source_x = output_x.saturating_mul(denom).min(width - 1) as usize;
4949 let y = planes[0].pixels[source_y * planes[0].stride + source_x];
4950 let chroma_y = source_y.min(planes[1].pixels.len() / planes[1].stride - 1);
4951 let cb_row = &planes[1].pixels
4952 [chroma_y * planes[1].stride..chroma_y * planes[1].stride + planes[1].width];
4953 let cr_row = &planes[2].pixels
4954 [chroma_y * planes[2].stride..chroma_y * planes[2].stride + planes[2].width];
4955 let c1 = upsample_h2v1_12bit_at(cb_row, source_x);
4956 let c2 = upsample_h2v1_12bit_at(cr_row, source_x);
4957 let (r, g, b) = match projection {
4958 Extended12RgbProjection::Identity => (y, c1, c2),
4959 Extended12RgbProjection::YCbCr => crate::color::ycbcr::ycbcr12_to_rgb16(y, c1, c2),
4960 };
4961 let dst_col = (output_x - output_rect.x) as usize;
4962 let dst_start = dst_row * stride + dst_col * 6;
4963 let dst = &mut out[dst_start..dst_start + 6];
4964 dst[0..2].copy_from_slice(&r.to_le_bytes());
4965 dst[2..4].copy_from_slice(&g.to_le_bytes());
4966 dst[4..6].copy_from_slice(&b.to_le_bytes());
4967 }
4968 }
4969}
4970
4971fn write_extended12_color420_planes_region(
4972 out: &mut [u8],
4973 stride: usize,
4974 region: Extended12WriteRegion,
4975 projection: Extended12RgbProjection,
4976 planes: &[Extended12Plane; 3],
4977) {
4978 let (width, height) = region.dimensions;
4979 let denom = region.downscale.denominator();
4980 let output_rect = region.output_rect;
4981 for output_y in output_rect.y..output_rect.y + output_rect.h {
4982 let source_y = output_y.saturating_mul(denom).min(height - 1) as usize;
4983 let dst_row = (output_y - output_rect.y) as usize;
4984 for output_x in output_rect.x..output_rect.x + output_rect.w {
4985 let source_x = output_x.saturating_mul(denom).min(width - 1) as usize;
4986 let y = planes[0].pixels[source_y * planes[0].stride + source_x];
4987 let chroma_height = planes[1].pixels.len() / planes[1].stride;
4988 let chroma_y = (source_y / 2).min(chroma_height - 1);
4989 let prev_y = chroma_y.saturating_sub(1);
4990 let next_y = (chroma_y + 1).min(chroma_height - 1);
4991 let c1 = upsample_h2v2_12bit_at(
4992 extended12_plane_row(&planes[1], prev_y),
4993 extended12_plane_row(&planes[1], chroma_y),
4994 extended12_plane_row(&planes[1], next_y),
4995 source_x,
4996 !source_y.is_multiple_of(2),
4997 );
4998 let c2 = upsample_h2v2_12bit_at(
4999 extended12_plane_row(&planes[2], prev_y),
5000 extended12_plane_row(&planes[2], chroma_y),
5001 extended12_plane_row(&planes[2], next_y),
5002 source_x,
5003 !source_y.is_multiple_of(2),
5004 );
5005 let (r, g, b) = match projection {
5006 Extended12RgbProjection::Identity => (y, c1, c2),
5007 Extended12RgbProjection::YCbCr => crate::color::ycbcr::ycbcr12_to_rgb16(y, c1, c2),
5008 };
5009 let dst_col = (output_x - output_rect.x) as usize;
5010 let dst_start = dst_row * stride + dst_col * 6;
5011 let dst = &mut out[dst_start..dst_start + 6];
5012 dst[0..2].copy_from_slice(&r.to_le_bytes());
5013 dst[2..4].copy_from_slice(&g.to_le_bytes());
5014 dst[4..6].copy_from_slice(&b.to_le_bytes());
5015 }
5016 }
5017}
5018
5019fn write_extended12_four_component_planes_region(
5020 out: &mut [u8],
5021 stride: usize,
5022 region: Extended12WriteRegion,
5023 color_space: ColorSpace,
5024 sampling: Extended12ColorSampling,
5025 planes: &[Extended12Plane; 4],
5026) {
5027 let (width, height) = region.dimensions;
5028 let denom = region.downscale.denominator();
5029 let output_rect = region.output_rect;
5030 for output_y in output_rect.y..output_rect.y + output_rect.h {
5031 let source_y = output_y.saturating_mul(denom).min(height - 1) as usize;
5032 let dst_row = (output_y - output_rect.y) as usize;
5033 for output_x in output_rect.x..output_rect.x + output_rect.w {
5034 let source_x = output_x.saturating_mul(denom).min(width - 1) as usize;
5035 let c0 = planes[0].pixels[source_y * planes[0].stride + source_x];
5036 let (c1, c2, c3) = match sampling {
5037 Extended12ColorSampling::S444 => (
5038 sample_extended12_plane_at(&planes[1], source_x, source_y),
5039 sample_extended12_plane_at(&planes[2], source_x, source_y),
5040 sample_extended12_plane_at(&planes[3], source_x, source_y),
5041 ),
5042 Extended12ColorSampling::S422 => (
5043 upsample_extended12_plane_h2v1_at(&planes[1], source_x, source_y),
5044 upsample_extended12_plane_h2v1_at(&planes[2], source_x, source_y),
5045 upsample_extended12_plane_h2v1_at(&planes[3], source_x, source_y),
5046 ),
5047 Extended12ColorSampling::S420 => (
5048 upsample_extended12_plane_h2v2_at(&planes[1], source_x, source_y),
5049 upsample_extended12_plane_h2v2_at(&planes[2], source_x, source_y),
5050 upsample_extended12_plane_h2v2_at(&planes[3], source_x, source_y),
5051 ),
5052 };
5053 let (r, g, b) = match color_space {
5054 ColorSpace::Cmyk => crate::color::cmyk::inverted_cmyk12_to_rgb16(c0, c1, c2, c3),
5055 ColorSpace::Ycck => crate::color::cmyk::ycck12_to_rgb16(c0, c1, c2, c3),
5056 _ => unreachable!("12-bit four-component plane path only accepts CMYK/YCCK"),
5057 };
5058 let dst_col = (output_x - output_rect.x) as usize;
5059 let dst_start = dst_row * stride + dst_col * 6;
5060 let dst = &mut out[dst_start..dst_start + 6];
5061 dst[0..2].copy_from_slice(&r.to_le_bytes());
5062 dst[2..4].copy_from_slice(&g.to_le_bytes());
5063 dst[4..6].copy_from_slice(&b.to_le_bytes());
5064 }
5065 }
5066}
5067
5068fn sample_extended12_plane_at(plane: &Extended12Plane, source_x: usize, source_y: usize) -> u16 {
5069 let height = plane.pixels.len() / plane.stride;
5070 let y = source_y.min(height - 1);
5071 let x = source_x.min(plane.width - 1);
5072 plane.pixels[y * plane.stride + x]
5073}
5074
5075fn upsample_extended12_plane_h2v1_at(
5076 plane: &Extended12Plane,
5077 source_x: usize,
5078 source_y: usize,
5079) -> u16 {
5080 let height = plane.pixels.len() / plane.stride;
5081 let y = source_y.min(height - 1);
5082 upsample_h2v1_12bit_at(extended12_plane_row(plane, y), source_x)
5083}
5084
5085fn upsample_extended12_plane_h2v2_at(
5086 plane: &Extended12Plane,
5087 source_x: usize,
5088 source_y: usize,
5089) -> u16 {
5090 let height = plane.pixels.len() / plane.stride;
5091 let chroma_y = (source_y / 2).min(height - 1);
5092 let prev_y = chroma_y.saturating_sub(1);
5093 let next_y = (chroma_y + 1).min(height - 1);
5094 upsample_h2v2_12bit_at(
5095 extended12_plane_row(plane, prev_y),
5096 extended12_plane_row(plane, chroma_y),
5097 extended12_plane_row(plane, next_y),
5098 source_x,
5099 !source_y.is_multiple_of(2),
5100 )
5101}
5102
5103fn extended12_plane_row(plane: &Extended12Plane, y: usize) -> &[u16] {
5104 let row_start = y * plane.stride;
5105 &plane.pixels[row_start..row_start + plane.width]
5106}
5107
5108fn upsample_h2v1_12bit_at(row: &[u16], output_x: usize) -> u16 {
5109 debug_assert!(!row.is_empty());
5110 if row.len() == 1 {
5111 return row[0];
5112 }
5113 let sample = output_x / 2;
5114 if output_x == 0 {
5115 row[0]
5116 } else if output_x == row.len() * 2 - 1 {
5117 row[row.len() - 1]
5118 } else if output_x.is_multiple_of(2) {
5119 ((3 * u32::from(row[sample]) + u32::from(row[sample - 1]) + 2) / 4) as u16
5120 } else {
5121 ((3 * u32::from(row[sample]) + u32::from(row[sample + 1]) + 2) / 4) as u16
5122 }
5123}
5124
5125fn upsample_h2v2_12bit_at(
5126 prev: &[u16],
5127 curr: &[u16],
5128 next: &[u16],
5129 output_x: usize,
5130 output_is_bottom: bool,
5131) -> u16 {
5132 debug_assert!(!curr.is_empty());
5133 debug_assert_eq!(prev.len(), curr.len());
5134 debug_assert_eq!(next.len(), curr.len());
5135 let near = if output_is_bottom { next } else { prev };
5136 let colsum = |index: usize| 3 * u32::from(curr[index]) + u32::from(near[index]);
5137 if curr.len() == 1 {
5138 return ((4 * colsum(0) + 8) >> 4) as u16;
5139 }
5140
5141 let sample = output_x / 2;
5142 let this = colsum(sample);
5143 match output_x {
5144 0 => ((this * 4 + 8) >> 4) as u16,
5145 _ if output_x == curr.len() * 2 - 1 => ((this * 4 + 7) >> 4) as u16,
5146 _ if output_x.is_multiple_of(2) => {
5147 let last = colsum(sample - 1);
5148 ((this * 3 + last + 8) >> 4) as u16
5149 }
5150 _ => {
5151 let next = colsum(sample + 1);
5152 ((this * 3 + next + 7) >> 4) as u16
5153 }
5154 }
5155}
5156
5157fn write_extended12_block_region(
5158 out: &mut [u8],
5159 stride: usize,
5160 region: Extended12WriteRegion,
5161 block_origin: (u32, u32),
5162 pixels: &[u16; 64],
5163) {
5164 let (width, height) = region.dimensions;
5165 let (x0, y0) = block_origin;
5166 let block_x1 = (x0 + 8).min(width);
5167 let block_y1 = (y0 + 8).min(height);
5168 let denom = region.downscale.denominator();
5169 let bytes_per_pixel = match region.output {
5170 Extended12Output::Gray16 => 2,
5171 Extended12Output::Rgb16 => 6,
5172 };
5173 let output_rect = region.output_rect;
5174 for output_y in output_rect.y..output_rect.y + output_rect.h {
5175 let source_y = output_y.saturating_mul(denom).min(height - 1);
5176 if source_y < y0 || source_y >= block_y1 {
5177 continue;
5178 }
5179 let src_row = (source_y - y0) as usize;
5180 let dst_row = (output_y - output_rect.y) as usize;
5181 for output_x in output_rect.x..output_rect.x + output_rect.w {
5182 let source_x = output_x.saturating_mul(denom).min(width - 1);
5183 if source_x < x0 || source_x >= block_x1 {
5184 continue;
5185 }
5186 let src_col = (source_x - x0) as usize;
5187 let sample = pixels[src_row * 8 + src_col].to_le_bytes();
5188 let dst_col = (output_x - output_rect.x) as usize;
5189 let dst_start = dst_row * stride + dst_col * bytes_per_pixel;
5190 let dst = &mut out[dst_start..dst_start + bytes_per_pixel];
5191 match region.output {
5192 Extended12Output::Gray16 => {
5193 dst.copy_from_slice(&sample);
5194 }
5195 Extended12Output::Rgb16 => {
5196 dst[0..2].copy_from_slice(&sample);
5197 dst[2..4].copy_from_slice(&sample);
5198 dst[4..6].copy_from_slice(&sample);
5199 }
5200 }
5201 }
5202 }
5203}
5204
5205fn restart_index_for_stream(
5206 bytes: &[u8],
5207 scan_data_offset: Option<usize>,
5208 info: &Info,
5209 restart_interval: Option<u16>,
5210) -> Result<Option<RestartIndex>, JpegError> {
5211 let Some(interval_mcus) = restart_interval
5212 .filter(|&interval| interval > 0)
5213 .map(u32::from)
5214 else {
5215 return Ok(None);
5216 };
5217 let scan_data_offset = scan_data_offset.ok_or(JpegError::MissingMarker {
5218 marker: MarkerKind::Sos,
5219 })?;
5220 if !matches!(info.sof_kind, SofKind::Baseline8 | SofKind::Extended8) || info.scan_count != 1 {
5221 return Err(JpegError::NotImplemented { sof: info.sof_kind });
5222 }
5223 let total_mcus = info.mcu_geometry.count;
5224 let expected_restarts = total_mcus.saturating_sub(1) / interval_mcus;
5225 let mut segments = Vec::new();
5226 segments.push(RestartSegment {
5227 start_mcu: 0,
5228 entropy_offset: scan_data_offset,
5229 marker_offset: None,
5230 marker: None,
5231 });
5232
5233 let mut found_restarts = 0u32;
5234 let mut expected_rst = 0xd0u8;
5235 let mut pos = scan_data_offset;
5236 while pos < bytes.len() {
5237 if bytes[pos] != 0xff {
5238 pos += 1;
5239 continue;
5240 }
5241
5242 let mut marker_code_pos = pos + 1;
5243 while marker_code_pos < bytes.len() && bytes[marker_code_pos] == 0xff {
5244 marker_code_pos += 1;
5245 }
5246 if marker_code_pos >= bytes.len() {
5247 return Err(JpegError::Truncated {
5248 offset: pos,
5249 expected: 1,
5250 });
5251 }
5252
5253 let marker = bytes[marker_code_pos];
5254 let marker_offset = marker_code_pos - 1;
5255 match marker {
5256 0x00 => pos = marker_code_pos + 1,
5257 0xd0..=0xd7 => {
5258 if found_restarts >= expected_restarts {
5259 return Err(JpegError::UnexpectedMarker {
5260 offset: marker_offset,
5261 expected: MarkerKind::Eoi,
5262 found: marker,
5263 });
5264 }
5265 if marker != expected_rst {
5266 return Err(JpegError::RestartMismatch {
5267 offset: marker_offset,
5268 expected: expected_rst & 0x07,
5269 found: marker,
5270 });
5271 }
5272 found_restarts += 1;
5273 segments.push(RestartSegment {
5274 start_mcu: found_restarts.saturating_mul(interval_mcus),
5275 entropy_offset: marker_code_pos + 1,
5276 marker_offset: Some(marker_offset),
5277 marker: Some(marker),
5278 });
5279 expected_rst = if expected_rst == 0xd7 {
5280 0xd0
5281 } else {
5282 expected_rst + 1
5283 };
5284 pos = marker_code_pos + 1;
5285 }
5286 0xd9 => {
5287 if found_restarts != expected_restarts {
5288 return Err(JpegError::UnexpectedEoi {
5289 mcu_at: found_restarts
5290 .saturating_add(1)
5291 .saturating_mul(interval_mcus),
5292 mcu_total: total_mcus,
5293 });
5294 }
5295 return Ok(Some(RestartIndex {
5296 scan_data_offset,
5297 interval_mcus,
5298 segments,
5299 }));
5300 }
5301 found => {
5302 return Err(JpegError::UnexpectedMarker {
5303 offset: marker_offset,
5304 expected: MarkerKind::Eoi,
5305 found,
5306 });
5307 }
5308 }
5309 }
5310
5311 Err(JpegError::MissingMarker {
5312 marker: MarkerKind::Eoi,
5313 })
5314}
5315
5316fn output_format_profile_name(fmt: OutputFormat) -> &'static str {
5317 match fmt {
5318 OutputFormat::Rgb8 | OutputFormat::Rgb8Scaled { .. } => "Rgb8",
5319 OutputFormat::Rgba8 { .. } | OutputFormat::Rgba8Scaled { .. } => "Rgba8",
5320 OutputFormat::Gray8 | OutputFormat::Gray8Scaled { .. } => "Gray8",
5321 OutputFormat::Gray16 | OutputFormat::Gray16Scaled { .. } => "Gray16",
5322 OutputFormat::Rgb16 | OutputFormat::Rgb16Scaled { .. } => "Rgb16",
5323 OutputFormat::Rgba16 { .. } | OutputFormat::Rgba16Scaled { .. } => "Rgba16",
5324 }
5325}
5326
5327fn downscale_profile_name(downscale: DownscaleFactor) -> &'static str {
5328 match downscale {
5329 DownscaleFactor::Full => "full",
5330 DownscaleFactor::Half => "half",
5331 DownscaleFactor::Quarter => "quarter",
5332 DownscaleFactor::Eighth => "eighth",
5333 }
5334}
5335
5336fn emit_decode_scan_profile(
5337 scan_path: &str,
5338 dimensions: (u32, u32),
5339 decoded: Rect,
5340 downscale: DownscaleFactor,
5341 elapsed: Duration,
5342) {
5343 let source_width_s = dimensions.0.to_string();
5344 let source_height_s = dimensions.1.to_string();
5345 let decoded_x_s = decoded.x.to_string();
5346 let decoded_y_s = decoded.y.to_string();
5347 let decoded_w_s = decoded.w.to_string();
5348 let decoded_h_s = decoded.h.to_string();
5349 let scan_us = duration_us_string(elapsed);
5350 emit_jpeg_profile_row(
5351 "decode_scan",
5352 "cpu",
5353 &[
5354 ("scan_path", scan_path),
5355 ("downscale", downscale_profile_name(downscale)),
5356 ("source_width", source_width_s.as_str()),
5357 ("source_height", source_height_s.as_str()),
5358 ("decoded_x", decoded_x_s.as_str()),
5359 ("decoded_y", decoded_y_s.as_str()),
5360 ("decoded_w", decoded_w_s.as_str()),
5361 ("decoded_h", decoded_h_s.as_str()),
5362 ("scan_us", scan_us.as_str()),
5363 ],
5364 );
5365}
5366
5367fn consume_lossless_restart(
5368 br: &mut BitReader<'_>,
5369 sample_index: u32,
5370 total_samples: u32,
5371 expected_rst: &mut u8,
5372) -> Result<(), JpegError> {
5373 br.reset_at_restart();
5374 let _ = br.ensure_bits(1);
5375 let marker = br.take_marker().ok_or(JpegError::UnexpectedEoi {
5376 mcu_at: sample_index,
5377 mcu_total: total_samples,
5378 })?;
5379 let expected = 0xD0 | *expected_rst;
5380 if marker != expected {
5381 return Err(JpegError::RestartMismatch {
5382 offset: br.position(),
5383 expected: *expected_rst,
5384 found: marker,
5385 });
5386 }
5387 *expected_rst = (*expected_rst + 1) & 0x07;
5388 br.reset_at_restart();
5389 Ok(())
5390}
5391
5392fn consume_extended12_restart(
5393 br: &mut BitReader<'_>,
5394 mcu_index: u32,
5395 total_mcus: u32,
5396 expected_rst: &mut u8,
5397) -> Result<(), JpegError> {
5398 let _ = br.ensure_bits(1);
5399 let marker = br.take_marker().ok_or(JpegError::UnexpectedEoi {
5400 mcu_at: mcu_index,
5401 mcu_total: total_mcus,
5402 })?;
5403 let expected = 0xD0 | *expected_rst;
5404 if marker != expected {
5405 return Err(JpegError::RestartMismatch {
5406 offset: br.position(),
5407 expected: *expected_rst,
5408 found: marker,
5409 });
5410 }
5411 *expected_rst = (*expected_rst + 1) & 0x07;
5412 br.reset_at_restart();
5413 Ok(())
5414}
5415
5416fn lossless_predictor_value(predictor: u8, out: &[u8], stride: usize, x: usize, y: usize) -> i32 {
5417 lossless_predict(predictor, 128, x, y, |sx, sy| {
5418 i32::from(out[sy * stride + sx])
5419 })
5420}
5421
5422fn lossless_predictor_color_into<P: LosslessSample>(
5423 predictor: u8,
5424 out: &[u8],
5425 stride: usize,
5426 x: usize,
5427 y: usize,
5428 component: usize,
5429) -> i32 {
5430 lossless_predict(predictor, P::RESTART_PREDICTOR, x, y, |sx, sy| {
5431 P::read_le(&out[sy * stride + (sx * 3 + component) * P::BYTES..])
5432 })
5433}
5434
5435fn lossless_predictor_gray_rows<P: LosslessSample>(
5436 predictor: u8,
5437 curr_row: &[u8],
5438 prev_row: &[u8],
5439 x: usize,
5440 y: usize,
5441) -> i32 {
5442 lossless_predict(predictor, P::RESTART_PREDICTOR, x, y, |sx, sy| {
5443 let row = if sy == y { curr_row } else { prev_row };
5444 P::read_le(&row[sx * P::BYTES..])
5445 })
5446}
5447
5448fn lossless_predictor_color_rows<P: LosslessSample>(
5449 predictor: u8,
5450 curr_row: &[u8],
5451 prev_row: &[u8],
5452 x: usize,
5453 y: usize,
5454 component: usize,
5455) -> i32 {
5456 lossless_predict(predictor, P::RESTART_PREDICTOR, x, y, |sx, sy| {
5457 let row = if sy == y { curr_row } else { prev_row };
5458 P::read_le(&row[(sx * 3 + component) * P::BYTES..])
5459 })
5460}
5461
5462#[derive(Clone, Copy)]
5463struct LosslessPlaneSample {
5464 x: usize,
5465 y: usize,
5466 restart_first_sample: bool,
5467}
5468
5469fn decode_lossless_plane_sample<P: LosslessSample>(
5470 br: &mut BitReader<'_>,
5471 table: &HuffmanTable,
5472 predictor: u8,
5473 plane: &mut [P],
5474 width: usize,
5475 sample: LosslessPlaneSample,
5476) -> Result<(), JpegError> {
5477 let predicted = if sample.restart_first_sample {
5478 P::RESTART_PREDICTOR
5479 } else {
5480 lossless_predictor_plane(predictor, plane, width, sample.x, sample.y)
5481 };
5482 let diff = table.decode_fast_dc(br)?;
5483 plane[sample.y * width + sample.x] = P::from_i32(predicted + diff)?;
5484 Ok(())
5485}
5486
5487fn lossless_predictor_plane<P: LosslessSample>(
5488 predictor: u8,
5489 plane: &[P],
5490 width: usize,
5491 x: usize,
5492 y: usize,
5493) -> i32 {
5494 lossless_predict(predictor, P::RESTART_PREDICTOR, x, y, |sx, sy| {
5495 plane[sy * width + sx].into()
5496 })
5497}
5498
5499struct LosslessColorPlanes<'a, P> {
5500 c0: &'a [P],
5501 c1: &'a [P],
5502 c2: &'a [P],
5503}
5504
5505fn write_lossless_color8_sampled_output(
5506 out: &mut [u8],
5507 stride: usize,
5508 color_space: ColorSpace,
5509 sampling: LosslessColorSampling,
5510 dimensions: (usize, usize),
5511 planes: LosslessColorPlanes<'_, u8>,
5512) {
5513 let (width, height) = dimensions;
5514 let chroma_width = width.div_ceil(2);
5515 let chroma_height = match sampling {
5516 LosslessColorSampling::S422 => height,
5517 LosslessColorSampling::S420 => height.div_ceil(2),
5518 LosslessColorSampling::S444 => unreachable!("sampled writer is not used for 4:4:4"),
5519 };
5520 for y in 0..height {
5521 for x in 0..width {
5522 let c0_sample = planes.c0[y * width + x];
5523 let (c1_sample, c2_sample) = match sampling {
5524 LosslessColorSampling::S422 => {
5525 let c1_row = &planes.c1[y * chroma_width..(y + 1) * chroma_width];
5526 let c2_row = &planes.c2[y * chroma_width..(y + 1) * chroma_width];
5527 (
5528 upsample_h2v1_u8_at(c1_row, x),
5529 upsample_h2v1_u8_at(c2_row, x),
5530 )
5531 }
5532 LosslessColorSampling::S420 => (
5533 upsample_h2v2_u8_at(planes.c1, chroma_width, chroma_height, width, x, y),
5534 upsample_h2v2_u8_at(planes.c2, chroma_width, chroma_height, width, x, y),
5535 ),
5536 LosslessColorSampling::S444 => unreachable!("sampled writer is not used for 4:4:4"),
5537 };
5538 let (r, g, b) = match color_space {
5539 ColorSpace::Rgb => (c0_sample, c1_sample, c2_sample),
5540 ColorSpace::YCbCr => {
5541 crate::color::ycbcr::ycbcr_to_rgb(c0_sample, c1_sample, c2_sample)
5542 }
5543 _ => unreachable!("lossless sampled color path only accepts RGB/YCbCr"),
5544 };
5545 let dst = y * stride + x * 3;
5546 out[dst..dst + 3].copy_from_slice(&[r, g, b]);
5547 }
5548 }
5549}
5550
5551fn write_lossless_color16_sampled_output(
5552 out: &mut [u8],
5553 stride: usize,
5554 color_space: ColorSpace,
5555 sampling: LosslessColorSampling,
5556 dimensions: (usize, usize),
5557 planes: LosslessColorPlanes<'_, u16>,
5558) {
5559 let (width, height) = dimensions;
5560 let chroma_width = width.div_ceil(2);
5561 let chroma_height = match sampling {
5562 LosslessColorSampling::S422 => height,
5563 LosslessColorSampling::S420 => height.div_ceil(2),
5564 LosslessColorSampling::S444 => unreachable!("sampled writer is not used for 4:4:4"),
5565 };
5566 for y in 0..height {
5567 for x in 0..width {
5568 let c0_sample = planes.c0[y * width + x];
5569 let (c1_sample, c2_sample) = match sampling {
5570 LosslessColorSampling::S422 => {
5571 let c1_row = &planes.c1[y * chroma_width..(y + 1) * chroma_width];
5572 let c2_row = &planes.c2[y * chroma_width..(y + 1) * chroma_width];
5573 (
5574 upsample_h2v1_u16_at(c1_row, x),
5575 upsample_h2v1_u16_at(c2_row, x),
5576 )
5577 }
5578 LosslessColorSampling::S420 => (
5579 upsample_h2v2_u16_at(planes.c1, chroma_width, chroma_height, width, x, y),
5580 upsample_h2v2_u16_at(planes.c2, chroma_width, chroma_height, width, x, y),
5581 ),
5582 LosslessColorSampling::S444 => unreachable!("sampled writer is not used for 4:4:4"),
5583 };
5584 let (r, g, b) = match color_space {
5585 ColorSpace::Rgb => (c0_sample, c1_sample, c2_sample),
5586 ColorSpace::YCbCr => {
5587 crate::color::ycbcr::ycbcr16_to_rgb16(c0_sample, c1_sample, c2_sample)
5588 }
5589 _ => unreachable!("lossless 4:2:2 color path only accepts RGB/YCbCr"),
5590 };
5591 let dst = y * stride + x * 6;
5592 out[dst..dst + 2].copy_from_slice(&r.to_le_bytes());
5593 out[dst + 2..dst + 4].copy_from_slice(&g.to_le_bytes());
5594 out[dst + 4..dst + 6].copy_from_slice(&b.to_le_bytes());
5595 }
5596 }
5597}
5598
5599fn upsample_h2v2_u8_at(
5600 plane: &[u8],
5601 chroma_width: usize,
5602 chroma_height: usize,
5603 output_width: usize,
5604 output_x: usize,
5605 output_y: usize,
5606) -> u8 {
5607 debug_assert!(!plane.is_empty());
5608 debug_assert!(chroma_width > 0);
5609 debug_assert!(chroma_height > 0);
5610 let chroma_y = output_y / 2;
5611 let current = &plane[chroma_y * chroma_width..(chroma_y + 1) * chroma_width];
5612 let near_y = if output_y.is_multiple_of(2) {
5613 chroma_y.saturating_sub(1)
5614 } else {
5615 (chroma_y + 1).min(chroma_height - 1)
5616 };
5617 let near = &plane[near_y * chroma_width..(near_y + 1) * chroma_width];
5618 let colsum = |index: usize| 3 * u32::from(current[index]) + u32::from(near[index]);
5619 if chroma_width == 1 {
5620 return ((4 * colsum(0) + 8) >> 4) as u8;
5621 }
5622
5623 let sample = output_x / 2;
5624 let this = colsum(sample);
5625 match output_x {
5626 0 => ((this * 4 + 8) >> 4) as u8,
5627 _ if output_x == output_width - 1 => ((this * 4 + 7) >> 4) as u8,
5628 _ if output_x.is_multiple_of(2) => {
5629 let last = colsum(sample - 1);
5630 ((this * 3 + last + 8) >> 4) as u8
5631 }
5632 _ => {
5633 let next = colsum(sample + 1);
5634 ((this * 3 + next + 7) >> 4) as u8
5635 }
5636 }
5637}
5638
5639fn upsample_h2v1_u8_at(row: &[u8], output_x: usize) -> u8 {
5640 debug_assert!(!row.is_empty());
5641 if row.len() == 1 {
5642 return row[0];
5643 }
5644 let sample = output_x / 2;
5645 if output_x == 0 {
5646 row[0]
5647 } else if output_x == row.len() * 2 - 1 {
5648 row[row.len() - 1]
5649 } else if output_x.is_multiple_of(2) {
5650 ((3 * u32::from(row[sample]) + u32::from(row[sample - 1]) + 2) / 4) as u8
5651 } else {
5652 ((3 * u32::from(row[sample]) + u32::from(row[sample + 1]) + 2) / 4) as u8
5653 }
5654}
5655
5656fn upsample_h2v2_u16_at(
5657 plane: &[u16],
5658 chroma_width: usize,
5659 chroma_height: usize,
5660 output_width: usize,
5661 output_x: usize,
5662 output_y: usize,
5663) -> u16 {
5664 debug_assert!(!plane.is_empty());
5665 debug_assert!(chroma_width > 0);
5666 debug_assert!(chroma_height > 0);
5667 let chroma_y = output_y / 2;
5668 let current = &plane[chroma_y * chroma_width..(chroma_y + 1) * chroma_width];
5669 let near_y = if output_y.is_multiple_of(2) {
5670 chroma_y.saturating_sub(1)
5671 } else {
5672 (chroma_y + 1).min(chroma_height - 1)
5673 };
5674 let near = &plane[near_y * chroma_width..(near_y + 1) * chroma_width];
5675 let colsum = |index: usize| 3 * u32::from(current[index]) + u32::from(near[index]);
5676 if chroma_width == 1 {
5677 return ((4 * colsum(0) + 8) >> 4) as u16;
5678 }
5679
5680 let sample = output_x / 2;
5681 let this = colsum(sample);
5682 match output_x {
5683 0 => ((this * 4 + 8) >> 4) as u16,
5684 _ if output_x == output_width - 1 => ((this * 4 + 7) >> 4) as u16,
5685 _ if output_x.is_multiple_of(2) => {
5686 let last = colsum(sample - 1);
5687 ((this * 3 + last + 8) >> 4) as u16
5688 }
5689 _ => {
5690 let next = colsum(sample + 1);
5691 ((this * 3 + next + 7) >> 4) as u16
5692 }
5693 }
5694}
5695
5696fn upsample_h2v1_u16_at(row: &[u16], output_x: usize) -> u16 {
5697 debug_assert!(!row.is_empty());
5698 if row.len() == 1 {
5699 return row[0];
5700 }
5701 let sample = output_x / 2;
5702 if output_x == 0 {
5703 row[0]
5704 } else if output_x == row.len() * 2 - 1 {
5705 row[row.len() - 1]
5706 } else if output_x.is_multiple_of(2) {
5707 ((3 * u32::from(row[sample]) + u32::from(row[sample - 1]) + 2) / 4) as u16
5708 } else {
5709 ((3 * u32::from(row[sample]) + u32::from(row[sample + 1]) + 2) / 4) as u16
5710 }
5711}
5712
5713fn lossless_predictor_value_u16(
5714 predictor: u8,
5715 out: &[u8],
5716 stride: usize,
5717 x: usize,
5718 y: usize,
5719) -> i32 {
5720 lossless_predict(predictor, 32768, x, y, |sx, sy| {
5721 i32::from(read_gray16_sample(out, sy * stride + sx * 2))
5722 })
5723}
5724
5725fn read_gray16_sample(out: &[u8], offset: usize) -> u16 {
5726 u16::from_le_bytes([out[offset], out[offset + 1]])
5727}
5728
5729fn merged_warnings(header_warnings: &[Warning], scan_warnings: Vec<Warning>) -> Vec<Warning> {
5730 if header_warnings.is_empty() {
5731 return scan_warnings;
5732 }
5733 if scan_warnings.is_empty() {
5734 return header_warnings.to_vec();
5735 }
5736 let mut warnings = Vec::with_capacity(header_warnings.len() + scan_warnings.len());
5737 warnings.extend_from_slice(header_warnings);
5738 warnings.extend(scan_warnings);
5739 warnings
5740}
5741
5742fn copy_gray8_scaled_rect(
5743 full: &[u8],
5744 dimensions: (u32, u32),
5745 output_rect: Rect,
5746 denom: u32,
5747 out: &mut [u8],
5748 stride: usize,
5749) {
5750 let (width, height) = dimensions;
5751 for output_y in output_rect.y..output_rect.y + output_rect.h {
5752 let source_y = output_y.saturating_mul(denom).min(height - 1);
5753 let dst_row = (output_y - output_rect.y) as usize;
5754 let dst_start = dst_row * stride;
5755 let dst = &mut out[dst_start..dst_start + output_rect.w as usize];
5756 for (dst_px, output_x) in (output_rect.x..output_rect.x + output_rect.w).enumerate() {
5757 let source_x = output_x.saturating_mul(denom).min(width - 1);
5758 let src = source_y as usize * width as usize + source_x as usize;
5759 dst[dst_px] = full[src];
5760 }
5761 }
5762}
5763
5764fn copy_rgb8_scaled_rect(
5765 full: &[u8],
5766 dimensions: (u32, u32),
5767 output_rect: Rect,
5768 denom: u32,
5769 out: &mut [u8],
5770 stride: usize,
5771) {
5772 let (width, height) = dimensions;
5773 for output_y in output_rect.y..output_rect.y + output_rect.h {
5774 let source_y = output_y.saturating_mul(denom).min(height - 1);
5775 let dst_row = (output_y - output_rect.y) as usize;
5776 for output_x in output_rect.x..output_rect.x + output_rect.w {
5777 let source_x = output_x.saturating_mul(denom).min(width - 1);
5778 let src = (source_y as usize * width as usize + source_x as usize) * 3;
5779 let dst = dst_row * stride + (output_x - output_rect.x) as usize * 3;
5780 out[dst..dst + 3].copy_from_slice(&full[src..src + 3]);
5781 }
5782 }
5783}
5784
5785fn convert_ycbcr8_to_rgb8_in_place(out: &mut [u8], stride: usize, dimensions: (u32, u32)) {
5786 let (width, height) = dimensions;
5787 let row_bytes = width as usize * 3;
5788 for y in 0..height as usize {
5789 let row = &mut out[y * stride..y * stride + row_bytes];
5790 for pixel in row.chunks_exact_mut(3) {
5791 let (r, g, b) = crate::color::ycbcr::ycbcr_to_rgb(pixel[0], pixel[1], pixel[2]);
5792 pixel.copy_from_slice(&[r, g, b]);
5793 }
5794 }
5795}
5796
5797fn copy_ycbcr8_row_to_rgb8(src: &[u8], dst: &mut [u8]) {
5798 debug_assert_eq!(src.len(), dst.len());
5799 for (source, target) in src.chunks_exact(3).zip(dst.chunks_exact_mut(3)) {
5800 let (r, g, b) = crate::color::ycbcr::ycbcr_to_rgb(source[0], source[1], source[2]);
5801 target.copy_from_slice(&[r, g, b]);
5802 }
5803}
5804
5805fn copy_rgb8_to_rgba8(
5806 src: &[u8],
5807 src_stride: usize,
5808 width: u32,
5809 height: u32,
5810 dst: &mut [u8],
5811 dst_stride: usize,
5812 alpha: u8,
5813) {
5814 let src_row_bytes = width as usize * 3;
5815 let dst_row_bytes = width as usize * 4;
5816 for y in 0..height as usize {
5817 let src_row = &src[y * src_stride..y * src_stride + src_row_bytes];
5818 let dst_row = &mut dst[y * dst_stride..y * dst_stride + dst_row_bytes];
5819 for (source, target) in src_row.chunks_exact(3).zip(dst_row.chunks_exact_mut(4)) {
5820 target.copy_from_slice(&[source[0], source[1], source[2], alpha]);
5821 }
5822 }
5823}
5824
5825fn copy_rgb16_to_rgba16(
5826 src: &[u8],
5827 src_stride: usize,
5828 width: u32,
5829 height: u32,
5830 dst: &mut [u8],
5831 dst_stride: usize,
5832 alpha: u16,
5833) {
5834 let src_row_bytes = width as usize * 6;
5835 let dst_row_bytes = width as usize * 8;
5836 let alpha = alpha.to_le_bytes();
5837 for y in 0..height as usize {
5838 let src_row = &src[y * src_stride..y * src_stride + src_row_bytes];
5839 let dst_row = &mut dst[y * dst_stride..y * dst_stride + dst_row_bytes];
5840 for (source, target) in src_row.chunks_exact(6).zip(dst_row.chunks_exact_mut(8)) {
5841 target[..6].copy_from_slice(source);
5842 target[6..8].copy_from_slice(&alpha);
5843 }
5844 }
5845}
5846
5847fn convert_ycbcr16_to_rgb16_in_place(out: &mut [u8], stride: usize, dimensions: (u32, u32)) {
5848 let (width, height) = dimensions;
5849 let row_bytes = width as usize * 6;
5850 for y in 0..height as usize {
5851 let row = &mut out[y * stride..y * stride + row_bytes];
5852 for pixel in row.chunks_exact_mut(6) {
5853 let y = u16::from_le_bytes([pixel[0], pixel[1]]);
5854 let cb = u16::from_le_bytes([pixel[2], pixel[3]]);
5855 let cr = u16::from_le_bytes([pixel[4], pixel[5]]);
5856 let (r, g, b) = crate::color::ycbcr::ycbcr16_to_rgb16(y, cb, cr);
5857 pixel[0..2].copy_from_slice(&r.to_le_bytes());
5858 pixel[2..4].copy_from_slice(&g.to_le_bytes());
5859 pixel[4..6].copy_from_slice(&b.to_le_bytes());
5860 }
5861 }
5862}
5863
5864fn copy_ycbcr16_row_to_rgb16(src: &[u8], dst: &mut [u8]) {
5865 debug_assert_eq!(src.len(), dst.len());
5866 for (source, target) in src.chunks_exact(6).zip(dst.chunks_exact_mut(6)) {
5867 let y = u16::from_le_bytes([source[0], source[1]]);
5868 let cb = u16::from_le_bytes([source[2], source[3]]);
5869 let cr = u16::from_le_bytes([source[4], source[5]]);
5870 let (r, g, b) = crate::color::ycbcr::ycbcr16_to_rgb16(y, cb, cr);
5871 target[0..2].copy_from_slice(&r.to_le_bytes());
5872 target[2..4].copy_from_slice(&g.to_le_bytes());
5873 target[4..6].copy_from_slice(&b.to_le_bytes());
5874 }
5875}
5876
5877fn copy_rgb16_scaled_rect(
5878 full: &[u8],
5879 dimensions: (u32, u32),
5880 output_rect: Rect,
5881 denom: u32,
5882 out: &mut [u8],
5883 stride: usize,
5884) {
5885 let (width, height) = dimensions;
5886 let full_stride = width as usize * 6;
5887 for output_y in output_rect.y..output_rect.y + output_rect.h {
5888 let source_y = output_y.saturating_mul(denom).min(height - 1);
5889 let dst_row = (output_y - output_rect.y) as usize;
5890 for output_x in output_rect.x..output_rect.x + output_rect.w {
5891 let source_x = output_x.saturating_mul(denom).min(width - 1);
5892 let src = source_y as usize * full_stride + source_x as usize * 6;
5893 let dst = dst_row * stride + (output_x - output_rect.x) as usize * 6;
5894 out[dst..dst + 6].copy_from_slice(&full[src..src + 6]);
5895 }
5896 }
5897}
5898
5899fn copy_gray16_scaled_rect(
5900 full: &[u8],
5901 dimensions: (u32, u32),
5902 output_rect: Rect,
5903 denom: u32,
5904 out: &mut [u8],
5905 stride: usize,
5906) {
5907 let (width, height) = dimensions;
5908 let full_stride = width as usize * 2;
5909 for output_y in output_rect.y..output_rect.y + output_rect.h {
5910 let source_y = output_y.saturating_mul(denom).min(height - 1);
5911 let dst_row = (output_y - output_rect.y) as usize;
5912 for output_x in output_rect.x..output_rect.x + output_rect.w {
5913 let source_x = output_x.saturating_mul(denom).min(width - 1);
5914 let src = source_y as usize * full_stride + source_x as usize * 2;
5915 let dst = dst_row * stride + (output_x - output_rect.x) as usize * 2;
5916 out[dst..dst + 2].copy_from_slice(&full[src..src + 2]);
5917 }
5918 }
5919}
5920
5921fn jpeg_passthrough_syntax(info: &Info) -> Option<CompressedTransferSyntax> {
5922 match info.sof_kind {
5923 SofKind::Baseline8 if info.bit_depth == 8 => Some(CompressedTransferSyntax::JpegBaseline8),
5924 SofKind::Extended8 | SofKind::Extended12 => {
5925 Some(CompressedTransferSyntax::JpegExtendedSequential)
5926 }
5927 SofKind::Baseline8 | SofKind::Progressive8 | SofKind::Progressive12 | SofKind::Lossless => {
5928 None
5929 }
5930 }
5931}
5932
5933fn core_outcome(outcome: DecodeOutcome) -> CoreDecodeOutcome<Warning> {
5934 outcome.into()
5935}
5936
5937fn jpeg_downscale(scale: Downscale) -> DownscaleFactor {
5938 match scale {
5939 Downscale::None => DownscaleFactor::Full,
5940 Downscale::Half => DownscaleFactor::Half,
5941 Downscale::Quarter => DownscaleFactor::Quarter,
5942 Downscale::Eighth => DownscaleFactor::Eighth,
5943 _ => unreachable!("unsupported Downscale variant"),
5944 }
5945}
5946
5947fn output_format_from_parts(
5948 sof_kind: SofKind,
5949 fmt: PixelFormat,
5950 scale: Downscale,
5951) -> Result<OutputFormat, JpegError> {
5952 if matches!(sof_kind, SofKind::Extended12 | SofKind::Progressive12) {
5953 return match (sof_kind, fmt, scale) {
5954 (SofKind::Extended12, PixelFormat::Gray16, Downscale::None) => Ok(OutputFormat::Gray16),
5955 (SofKind::Extended12, PixelFormat::Gray16, scale) => Ok(OutputFormat::Gray16Scaled {
5956 factor: jpeg_downscale(scale),
5957 }),
5958 (SofKind::Extended12, PixelFormat::Rgb16, Downscale::None) => Ok(OutputFormat::Rgb16),
5959 (SofKind::Extended12, PixelFormat::Rgb16, scale) => Ok(OutputFormat::Rgb16Scaled {
5960 factor: jpeg_downscale(scale),
5961 }),
5962 (SofKind::Extended12, PixelFormat::Rgba16, Downscale::None) => {
5963 Ok(OutputFormat::Rgba16 { alpha: u16::MAX })
5964 }
5965 (SofKind::Extended12, PixelFormat::Rgba16, scale) => Ok(OutputFormat::Rgba16Scaled {
5966 alpha: u16::MAX,
5967 factor: jpeg_downscale(scale),
5968 }),
5969 (SofKind::Progressive12, PixelFormat::Gray16, Downscale::None) => {
5970 Ok(OutputFormat::Gray16)
5971 }
5972 (SofKind::Progressive12, PixelFormat::Gray16, scale) => {
5973 Ok(OutputFormat::Gray16Scaled {
5974 factor: jpeg_downscale(scale),
5975 })
5976 }
5977 (SofKind::Progressive12, PixelFormat::Rgb16, Downscale::None) => {
5978 Ok(OutputFormat::Rgb16)
5979 }
5980 (SofKind::Progressive12, PixelFormat::Rgb16, scale) => Ok(OutputFormat::Rgb16Scaled {
5981 factor: jpeg_downscale(scale),
5982 }),
5983 (SofKind::Progressive12, PixelFormat::Rgba16, Downscale::None) => {
5984 Ok(OutputFormat::Rgba16 { alpha: u16::MAX })
5985 }
5986 (SofKind::Progressive12, PixelFormat::Rgba16, scale) => {
5987 Ok(OutputFormat::Rgba16Scaled {
5988 alpha: u16::MAX,
5989 factor: jpeg_downscale(scale),
5990 })
5991 }
5992 (_, PixelFormat::Rgb16 | PixelFormat::Rgba16 | PixelFormat::Gray16, _) => {
5993 Err(JpegError::NotImplemented { sof: sof_kind })
5994 }
5995 _ => Err(JpegError::UnsupportedBitDepth { depth: 12 }),
5996 };
5997 }
5998 if sof_kind == SofKind::Lossless {
5999 return match (fmt, scale) {
6000 (PixelFormat::Gray8, Downscale::None) => Ok(OutputFormat::Gray8),
6001 (PixelFormat::Gray8, scale) => Ok(OutputFormat::Gray8Scaled {
6002 factor: jpeg_downscale(scale),
6003 }),
6004 (PixelFormat::Gray16, Downscale::None) => Ok(OutputFormat::Gray16),
6005 (PixelFormat::Gray16, scale) => Ok(OutputFormat::Gray16Scaled {
6006 factor: jpeg_downscale(scale),
6007 }),
6008 (PixelFormat::Rgb8, Downscale::None) => Ok(OutputFormat::Rgb8),
6009 (PixelFormat::Rgb8, scale) => Ok(OutputFormat::Rgb8Scaled {
6010 factor: jpeg_downscale(scale),
6011 }),
6012 (PixelFormat::Rgba8, Downscale::None) => Ok(OutputFormat::Rgba8 { alpha: 255 }),
6013 (PixelFormat::Rgba8, scale) => Ok(OutputFormat::Rgba8Scaled {
6014 alpha: 255,
6015 factor: jpeg_downscale(scale),
6016 }),
6017 (PixelFormat::Rgb16, Downscale::None) => Ok(OutputFormat::Rgb16),
6018 (PixelFormat::Rgb16, scale) => Ok(OutputFormat::Rgb16Scaled {
6019 factor: jpeg_downscale(scale),
6020 }),
6021 (PixelFormat::Rgba16, Downscale::None) => Ok(OutputFormat::Rgba16 { alpha: u16::MAX }),
6022 (PixelFormat::Rgba16, scale) => Ok(OutputFormat::Rgba16Scaled {
6023 alpha: u16::MAX,
6024 factor: jpeg_downscale(scale),
6025 }),
6026 _ => Err(JpegError::NotImplemented { sof: sof_kind }),
6027 };
6028 }
6029
6030 match (fmt, scale) {
6031 (PixelFormat::Rgb8, Downscale::None) => Ok(OutputFormat::Rgb8),
6032 (PixelFormat::Rgb8, scale) => Ok(OutputFormat::Rgb8Scaled {
6033 factor: jpeg_downscale(scale),
6034 }),
6035 (PixelFormat::Gray8, Downscale::None) => Ok(OutputFormat::Gray8),
6036 (PixelFormat::Gray8, scale) => Ok(OutputFormat::Gray8Scaled {
6037 factor: jpeg_downscale(scale),
6038 }),
6039 (PixelFormat::Rgba8, Downscale::None) => Ok(OutputFormat::Rgba8 { alpha: 255 }),
6040 (PixelFormat::Rgba8, scale) => Ok(OutputFormat::Rgba8Scaled {
6041 alpha: 255,
6042 factor: jpeg_downscale(scale),
6043 }),
6044 (PixelFormat::Rgb16 | PixelFormat::Rgba16 | PixelFormat::Gray16, _) => {
6045 Err(JpegError::UnsupportedBitDepth { depth: 16 })
6046 }
6047 _ => Err(JpegError::DownscaleUnsupported { sof: sof_kind }),
6048 }
6049}
6050
6051impl ImageCodec for JpegCodec {
6052 type Error = JpegError;
6053 type Warning = Warning;
6054 type Pool = ScratchPool;
6055}
6056
6057impl ImageCodec for Decoder<'_> {
6058 type Error = JpegError;
6059 type Warning = Warning;
6060 type Pool = ScratchPool;
6061}
6062
6063impl<'a> ImageDecode<'a> for Decoder<'a> {
6064 type View = JpegView<'a>;
6065
6066 fn inspect(input: &'a [u8]) -> Result<j2k_core::Info, Self::Error> {
6067 Ok(Decoder::inspect(input)?.to_core_info())
6068 }
6069
6070 fn parse(input: &'a [u8]) -> Result<Self::View, Self::Error> {
6071 JpegView::parse(input)
6072 }
6073
6074 fn from_view(view: Self::View) -> Result<Self, Self::Error> {
6075 Decoder::from_view(view)
6076 }
6077
6078 fn decode_into(
6079 &mut self,
6080 out: &mut [u8],
6081 stride: usize,
6082 fmt: PixelFormat,
6083 ) -> Result<CoreDecodeOutcome<Self::Warning>, Self::Error> {
6084 Decoder::decode_into(self, out, stride, fmt).map(core_outcome)
6085 }
6086
6087 fn decode_into_with_scratch(
6088 &mut self,
6089 pool: &mut Self::Pool,
6090 out: &mut [u8],
6091 stride: usize,
6092 fmt: PixelFormat,
6093 ) -> Result<CoreDecodeOutcome<Self::Warning>, Self::Error> {
6094 Decoder::decode_into_with_scratch(self, pool, out, stride, fmt).map(core_outcome)
6095 }
6096
6097 fn decode_region_into(
6098 &mut self,
6099 pool: &mut Self::Pool,
6100 out: &mut [u8],
6101 stride: usize,
6102 fmt: PixelFormat,
6103 roi: j2k_core::Rect,
6104 ) -> Result<CoreDecodeOutcome<Self::Warning>, Self::Error> {
6105 Decoder::decode_region_into_with_scratch(self, pool, out, stride, fmt, roi.into())
6106 .map(core_outcome)
6107 }
6108
6109 fn decode_scaled_into(
6110 &mut self,
6111 pool: &mut Self::Pool,
6112 out: &mut [u8],
6113 stride: usize,
6114 fmt: PixelFormat,
6115 scale: Downscale,
6116 ) -> Result<CoreDecodeOutcome<Self::Warning>, Self::Error> {
6117 Decoder::decode_scaled_into_with_scratch(self, pool, out, stride, fmt, scale)
6118 .map(core_outcome)
6119 }
6120
6121 fn decode_region_scaled_into(
6122 &mut self,
6123 pool: &mut Self::Pool,
6124 out: &mut [u8],
6125 stride: usize,
6126 fmt: PixelFormat,
6127 roi: j2k_core::Rect,
6128 scale: Downscale,
6129 ) -> Result<CoreDecodeOutcome<Self::Warning>, Self::Error> {
6130 Decoder::decode_region_scaled_into_with_scratch(
6131 self,
6132 pool,
6133 out,
6134 stride,
6135 fmt,
6136 roi.into(),
6137 scale,
6138 )
6139 .map(core_outcome)
6140 }
6141}
6142
6143struct CoreRowSinkAdapter<'a, R: RowSink<u8>> {
6144 sink: &'a mut R,
6145 sink_error: Option<R::Error>,
6146}
6147
6148impl<R: RowSink<u8>> RowSink<u8> for CoreRowSinkAdapter<'_, R> {
6149 type Error = JpegError;
6150
6151 fn write_row(&mut self, y: u32, row: &[u8]) -> Result<(), JpegError> {
6152 match self.sink.write_row(y, row) {
6153 Ok(()) => Ok(()),
6154 Err(err) => {
6155 self.sink_error = Some(err);
6156 Err(JpegError::RowSinkAborted)
6157 }
6158 }
6159 }
6160}
6161
6162impl<'a> ImageDecodeRows<'a, u8> for Decoder<'a> {
6163 fn decode_rows<R: RowSink<u8>>(
6164 &mut self,
6165 sink: &mut R,
6166 ) -> Result<CoreDecodeOutcome<Self::Warning>, DecodeRowsError<Self::Error, R::Error>> {
6167 let mut adapter = CoreRowSinkAdapter {
6168 sink,
6169 sink_error: None,
6170 };
6171 match Decoder::decode_rows(self, &mut adapter) {
6172 Ok(outcome) => Ok(core_outcome(outcome)),
6173 Err(JpegError::RowSinkAborted) => Err(DecodeRowsError::Sink(
6174 adapter
6175 .sink_error
6176 .expect("row sink abort stores the original sink error"),
6177 )),
6178 Err(err) => Err(DecodeRowsError::Decode(err)),
6179 }
6180 }
6181}
6182
6183impl TileBatchDecode for JpegCodec {
6184 type Context = DecoderContext;
6185
6186 fn decode_tile(
6187 ctx: &mut CoreDecoderContext<Self::Context>,
6188 pool: &mut Self::Pool,
6189 input: &[u8],
6190 out: &mut [u8],
6191 stride: usize,
6192 fmt: PixelFormat,
6193 ) -> Result<CoreDecodeOutcome<Self::Warning>, Self::Error> {
6194 let dec = Decoder::from_view_in_context(JpegView::parse(input)?, ctx.codec_mut())?;
6195 dec.decode_into_with_scratch(pool, out, stride, fmt)
6196 .map(core_outcome)
6197 }
6198
6199 fn decode_tile_region(
6200 ctx: &mut CoreDecoderContext<Self::Context>,
6201 pool: &mut Self::Pool,
6202 input: &[u8],
6203 out: &mut [u8],
6204 stride: usize,
6205 fmt: PixelFormat,
6206 roi: j2k_core::Rect,
6207 ) -> Result<CoreDecodeOutcome<Self::Warning>, Self::Error> {
6208 let dec = Decoder::from_view_in_context(JpegView::parse(input)?, ctx.codec_mut())?;
6209 dec.decode_region_into_with_scratch(pool, out, stride, fmt, roi.into())
6210 .map(core_outcome)
6211 }
6212
6213 fn decode_tile_scaled(
6214 ctx: &mut CoreDecoderContext<Self::Context>,
6215 pool: &mut Self::Pool,
6216 input: &[u8],
6217 out: &mut [u8],
6218 stride: usize,
6219 fmt: PixelFormat,
6220 scale: Downscale,
6221 ) -> Result<CoreDecodeOutcome<Self::Warning>, Self::Error> {
6222 let dec = Decoder::from_view_in_context(JpegView::parse(input)?, ctx.codec_mut())?;
6223 dec.decode_scaled_into_with_scratch(pool, out, stride, fmt, scale)
6224 .map(core_outcome)
6225 }
6226
6227 fn decode_tile_region_scaled(
6228 ctx: &mut CoreDecoderContext<Self::Context>,
6229 pool: &mut Self::Pool,
6230 input: &[u8],
6231 out: &mut [u8],
6232 stride: usize,
6233 fmt: PixelFormat,
6234 roi: j2k_core::Rect,
6235 scale: Downscale,
6236 ) -> Result<CoreDecodeOutcome<Self::Warning>, Self::Error> {
6237 let dec = Decoder::from_view_in_context(JpegView::parse(input)?, ctx.codec_mut())?;
6238 dec.decode_region_scaled_into_with_scratch(pool, out, stride, fmt, roi.into(), scale)
6239 .map(core_outcome)
6240 }
6241}
6242
6243#[allow(clippy::uninit_vec)]
6244fn allocate_output_buffer(len: usize) -> Vec<u8> {
6245 let mut out = Vec::with_capacity(len);
6246 unsafe {
6251 out.set_len(len);
6252 }
6253 out
6254}
6255
6256fn scaled_dimensions(dims: (u32, u32), factor: DownscaleFactor) -> (u32, u32) {
6257 let denom = factor.denominator();
6258 (dims.0.div_ceil(denom), dims.1.div_ceil(denom))
6259}
6260
6261fn scaled_rect_covering(rect: Rect, factor: DownscaleFactor) -> Result<Rect, JpegError> {
6262 let denom = factor.denominator();
6263 let x_end = rect
6264 .x
6265 .checked_add(rect.w)
6266 .ok_or(JpegError::RectOutOfBounds {
6267 rect,
6268 width: u32::MAX,
6269 height: u32::MAX,
6270 })?;
6271 let y_end = rect
6272 .y
6273 .checked_add(rect.h)
6274 .ok_or(JpegError::RectOutOfBounds {
6275 rect,
6276 width: u32::MAX,
6277 height: u32::MAX,
6278 })?;
6279 let x0 = rect.x / denom;
6280 let y0 = rect.y / denom;
6281 let x1 = x_end.div_ceil(denom);
6282 let y1 = y_end.div_ceil(denom);
6283 Ok(Rect {
6284 x: x0,
6285 y: y0,
6286 w: x1.saturating_sub(x0),
6287 h: y1.saturating_sub(y0),
6288 })
6289}
6290
6291struct CroppedWriter<W> {
6292 inner: W,
6293 rect: Rect,
6294 source_x0: u32,
6295 source_width: u32,
6296 top_row: Vec<u8>,
6297 bottom_row: Vec<u8>,
6298}
6299
6300struct ProgressiveDownscaleWriter<'a, W> {
6301 inner: &'a mut W,
6302 denom: u32,
6303 scaled_width: usize,
6304 r: Vec<u8>,
6305 g: Vec<u8>,
6306 b: Vec<u8>,
6307}
6308
6309impl<'a, W> ProgressiveDownscaleWriter<'a, W> {
6310 fn new(inner: &'a mut W, downscale: DownscaleFactor, dimensions: (u32, u32)) -> Self {
6311 let denom = downscale.denominator();
6312 let scaled_width = dimensions.0.div_ceil(denom) as usize;
6313 Self {
6314 inner,
6315 denom,
6316 scaled_width,
6317 r: Vec::new(),
6318 g: Vec::new(),
6319 b: Vec::new(),
6320 }
6321 }
6322
6323 fn should_emit(&self, y: u32) -> bool {
6324 y.is_multiple_of(self.denom)
6325 }
6326
6327 fn sample_row(src: &[u8], denom: u32, width: usize, dst: &mut Vec<u8>) {
6328 dst.resize(width, 0);
6329 for (x, out) in dst.iter_mut().enumerate() {
6330 let src_x = (x as u32)
6331 .saturating_mul(denom)
6332 .min(src.len().saturating_sub(1) as u32);
6333 *out = src[src_x as usize];
6334 }
6335 }
6336}
6337
6338impl<W: OutputWriter> OutputWriter for ProgressiveDownscaleWriter<'_, W> {
6339 fn write_rgb_row(
6340 &mut self,
6341 y: u32,
6342 r_row: &[u8],
6343 g_row: &[u8],
6344 b_row: &[u8],
6345 ) -> Result<(), JpegError> {
6346 if !self.should_emit(y) {
6347 return Ok(());
6348 }
6349 Self::sample_row(r_row, self.denom, self.scaled_width, &mut self.r);
6350 Self::sample_row(g_row, self.denom, self.scaled_width, &mut self.g);
6351 Self::sample_row(b_row, self.denom, self.scaled_width, &mut self.b);
6352 self.inner
6353 .write_rgb_row(y / self.denom, &self.r, &self.g, &self.b)
6354 }
6355
6356 fn write_ycbcr_row(
6357 &mut self,
6358 y: u32,
6359 y_row: &[u8],
6360 cb_row: &[u8],
6361 cr_row: &[u8],
6362 ) -> Result<(), JpegError> {
6363 if !self.should_emit(y) {
6364 return Ok(());
6365 }
6366 Self::sample_row(y_row, self.denom, self.scaled_width, &mut self.r);
6367 Self::sample_row(cb_row, self.denom, self.scaled_width, &mut self.g);
6368 Self::sample_row(cr_row, self.denom, self.scaled_width, &mut self.b);
6369 self.inner
6370 .write_ycbcr_row(y / self.denom, &self.r, &self.g, &self.b)
6371 }
6372
6373 fn write_gray_row(&mut self, y: u32, gray_row: &[u8]) -> Result<(), JpegError> {
6374 if !self.should_emit(y) {
6375 return Ok(());
6376 }
6377 Self::sample_row(gray_row, self.denom, self.scaled_width, &mut self.r);
6378 self.inner.write_gray_row(y / self.denom, &self.r)
6379 }
6380}
6381
6382struct ComponentWriterAdapter<'a, W> {
6383 inner: &'a mut W,
6384}
6385
6386impl<W: ComponentRowWriter> OutputWriter for ComponentWriterAdapter<'_, W> {
6387 fn write_rgb_row(
6388 &mut self,
6389 y: u32,
6390 r_row: &[u8],
6391 g_row: &[u8],
6392 b_row: &[u8],
6393 ) -> Result<(), JpegError> {
6394 self.inner.write_rgb_row(y, r_row, g_row, b_row)
6395 }
6396
6397 fn write_ycbcr_row(
6398 &mut self,
6399 y: u32,
6400 y_row: &[u8],
6401 cb_row: &[u8],
6402 cr_row: &[u8],
6403 ) -> Result<(), JpegError> {
6404 self.inner.write_ycbcr_row(y, y_row, cb_row, cr_row)
6405 }
6406
6407 fn write_gray_row(&mut self, y: u32, gray_row: &[u8]) -> Result<(), JpegError> {
6408 self.inner.write_gray_row(y, gray_row)
6409 }
6410}
6411
6412impl<W> CroppedWriter<W> {
6413 fn new(inner: W, rect: Rect, source_x0: u32, source_width: u32) -> Self {
6414 let row_len = source_width as usize * 3;
6415 Self {
6416 inner,
6417 rect,
6418 source_x0,
6419 source_width,
6420 top_row: vec![0; row_len],
6421 bottom_row: vec![0; row_len],
6422 }
6423 }
6424}
6425
6426impl<W: OutputWriter> OutputWriter for CroppedWriter<W> {
6427 fn write_rgb_row(
6428 &mut self,
6429 y: u32,
6430 r_row: &[u8],
6431 g_row: &[u8],
6432 b_row: &[u8],
6433 ) -> Result<(), JpegError> {
6434 if y < self.rect.y || y >= self.rect.y + self.rect.h {
6435 return Ok(());
6436 }
6437 let x0 = self
6438 .rect
6439 .x
6440 .checked_sub(self.source_x0)
6441 .expect("crop window must cover requested rect") as usize;
6442 let x1 = x0 + self.rect.w as usize;
6443 self.inner.write_rgb_row(
6444 y - self.rect.y,
6445 &r_row[x0..x1],
6446 &g_row[x0..x1],
6447 &b_row[x0..x1],
6448 )
6449 }
6450
6451 fn write_ycbcr_row(
6452 &mut self,
6453 y: u32,
6454 y_row: &[u8],
6455 cb_row: &[u8],
6456 cr_row: &[u8],
6457 ) -> Result<(), JpegError> {
6458 if y < self.rect.y || y >= self.rect.y + self.rect.h {
6459 return Ok(());
6460 }
6461 let x0 = self
6462 .rect
6463 .x
6464 .checked_sub(self.source_x0)
6465 .expect("crop window must cover requested rect") as usize;
6466 let x1 = x0 + self.rect.w as usize;
6467 self.inner.write_ycbcr_row(
6468 y - self.rect.y,
6469 &y_row[x0..x1],
6470 &cb_row[x0..x1],
6471 &cr_row[x0..x1],
6472 )
6473 }
6474
6475 fn write_gray_row(&mut self, y: u32, gray_row: &[u8]) -> Result<(), JpegError> {
6476 if y < self.rect.y || y >= self.rect.y + self.rect.h {
6477 return Ok(());
6478 }
6479 let x0 = self
6480 .rect
6481 .x
6482 .checked_sub(self.source_x0)
6483 .expect("crop window must cover requested rect") as usize;
6484 let x1 = x0 + self.rect.w as usize;
6485 self.inner
6486 .write_gray_row(y - self.rect.y, &gray_row[x0..x1])
6487 }
6488}
6489
6490impl<W: InterleavedRgbWriter> InterleavedRgbWriter for CroppedWriter<W> {
6491 fn with_rgb_rows<R, F>(&mut self, y: u32, row_count: usize, fill: F) -> Result<R, JpegError>
6492 where
6493 F: FnOnce(&mut [u8], Option<&mut [u8]>) -> Result<R, JpegError>,
6494 {
6495 let row_len = self.source_width as usize * 3;
6496 if self.top_row.len() != row_len {
6497 self.top_row.resize(row_len, 0);
6498 self.bottom_row.resize(row_len, 0);
6499 }
6500
6501 let result = match row_count {
6502 1 => fill(&mut self.top_row, None)?,
6503 2 => fill(&mut self.top_row, Some(&mut self.bottom_row))?,
6504 _ => unreachable!("CroppedWriter only supports one or two rows"),
6505 };
6506
6507 let top_in = y >= self.rect.y && y < self.rect.y + self.rect.h;
6508 let bottom_y = y + 1;
6509 let bottom_in =
6510 row_count == 2 && bottom_y >= self.rect.y && bottom_y < self.rect.y + self.rect.h;
6511 let x0 = self
6512 .rect
6513 .x
6514 .checked_sub(self.source_x0)
6515 .expect("crop window must cover requested rect") as usize
6516 * 3;
6517 let x1 = x0 + self.rect.w as usize * 3;
6518
6519 match (top_in, bottom_in) {
6520 (false, false) => {}
6521 (true, false) => {
6522 self.inner.with_rgb_rows(y - self.rect.y, 1, |dst, _| {
6523 dst.copy_from_slice(&self.top_row[x0..x1]);
6524 Ok(())
6525 })?;
6526 }
6527 (false, true) => {
6528 self.inner
6529 .with_rgb_rows(bottom_y - self.rect.y, 1, |dst, _| {
6530 dst.copy_from_slice(&self.bottom_row[x0..x1]);
6531 Ok(())
6532 })?;
6533 }
6534 (true, true) => {
6535 self.inner
6536 .with_rgb_rows(y - self.rect.y, 2, |dst_top, dst_bottom| {
6537 dst_top.copy_from_slice(&self.top_row[x0..x1]);
6538 dst_bottom
6539 .expect("row_count=2 supplies bottom row")
6540 .copy_from_slice(&self.bottom_row[x0..x1]);
6541 Ok(())
6542 })?;
6543 }
6544 }
6545
6546 Ok(result)
6547 }
6548}
6549
6550fn build_decode_plan(
6551 header: &ParsedHeader,
6552 info: &Info,
6553 dc_tables: &[Option<Arc<HuffmanTable>>; 4],
6554 ac_tables: &[Option<Arc<HuffmanTable>>; 4],
6555 ctx: &mut DecoderContext,
6556) -> Result<PreparedDecodePlan, JpegError> {
6557 let scan = header.scan.as_ref().ok_or(JpegError::MissingMarker {
6558 marker: MarkerKind::Sos,
6559 })?;
6560 let scan_offset = header.sos_offset.ok_or(JpegError::MissingMarker {
6561 marker: MarkerKind::Sos,
6562 })?;
6563
6564 let mut components = Vec::with_capacity(scan.components.len());
6565 for scan_comp in scan.components.iter().copied() {
6566 let output_index = find_component_index(&header.component_ids, scan_comp.id).ok_or(
6567 JpegError::UnknownScanComponent {
6568 offset: scan_offset,
6569 component: scan_comp.id,
6570 },
6571 )?;
6572 let (h, v) = header
6573 .sampling
6574 .component(output_index)
6575 .ok_or(JpegError::MissingMarker {
6576 marker: MarkerKind::Sof,
6577 })?;
6578 let quant_id =
6579 *header
6580 .quant_table_ids
6581 .get(output_index)
6582 .ok_or(JpegError::MissingMarker {
6583 marker: MarkerKind::Sof,
6584 })? as usize;
6585 let quant = *header
6586 .quant_tables
6587 .entries
6588 .get(quant_id)
6589 .and_then(|q| q.as_ref())
6590 .ok_or(JpegError::MissingQuantTable {
6591 component: scan_comp.id,
6592 table_id: quant_id as u8,
6593 })?;
6594 let dc_table = dc_tables[scan_comp.dc_table as usize].as_ref().ok_or(
6595 JpegError::MissingHuffmanTable {
6596 component: scan_comp.id,
6597 class: 0,
6598 id: scan_comp.dc_table,
6599 },
6600 )?;
6601 let ac_table = ac_tables[scan_comp.ac_table as usize].as_ref().ok_or(
6602 JpegError::MissingHuffmanTable {
6603 component: scan_comp.id,
6604 class: 1,
6605 id: scan_comp.ac_table,
6606 },
6607 )?;
6608 components.push(PreparedComponentPlan {
6609 h,
6610 v,
6611 output_index,
6612 quant: ctx.resolve_quant_table(quant),
6613 dc_table: Arc::clone(dc_table),
6614 ac_table: Arc::clone(ac_table),
6615 });
6616 }
6617
6618 let mut scratch_bytes =
6619 compute_decode_scratch_bytes(info.dimensions, info.sampling, DEFAULT_MAX_DECODE_BYTES)?;
6620 if info.sof_kind == SofKind::Extended12 {
6621 scratch_bytes = scratch_bytes.max(compute_extended12_planes_scratch_bytes(
6624 &components,
6625 info.dimensions,
6626 info.sampling,
6627 DEFAULT_MAX_DECODE_BYTES,
6628 )?);
6629 }
6630
6631 Ok(PreparedDecodePlan {
6632 components,
6633 sampling: info.sampling,
6634 color_space: info.color_space,
6635 restart_interval: header.restart_interval,
6636 dimensions: info.dimensions,
6637 scan_offset,
6638 scratch_bytes,
6639 })
6640}
6641
6642fn validate_sampling_factors(header: &ParsedHeader, info: &Info) -> Result<(), JpegError> {
6643 validate_leading_component_sampling(header, info)?;
6644 for (h, v) in header.sampling.iter() {
6645 if h == 0 || v == 0 || h > 4 || v > 4 {
6646 return Err(JpegError::NotImplemented { sof: info.sof_kind });
6647 }
6648 if !header.sampling.max_h.is_multiple_of(h) || !header.sampling.max_v.is_multiple_of(v) {
6649 return Err(JpegError::NotImplemented { sof: info.sof_kind });
6650 }
6651 }
6652 Ok(())
6653}
6654
6655fn validate_leading_component_sampling(
6656 header: &ParsedHeader,
6657 info: &Info,
6658) -> Result<(), JpegError> {
6659 if !matches!(info.color_space, ColorSpace::YCbCr) {
6660 return Ok(());
6661 }
6662 if let Some((h, v)) = header.sampling.component(0) {
6663 if h != header.sampling.max_h || v != header.sampling.max_v {
6664 return Err(JpegError::NotImplemented { sof: info.sof_kind });
6665 }
6666 }
6667 Ok(())
6668}
6669
6670fn resolve_progressive_huffman(
6671 ctx: &mut DecoderContext,
6672 tables: &[Option<RawHuffmanTable>; 4],
6673 component: u8,
6674 class: u8,
6675 id: u8,
6676) -> Result<Arc<HuffmanTable>, JpegError> {
6677 let raw = tables
6678 .get(id as usize)
6679 .and_then(|table| table.as_ref())
6680 .ok_or(JpegError::MissingHuffmanTable {
6681 component,
6682 class,
6683 id,
6684 })?;
6685 ctx.resolve_huffman_table(raw)
6686}
6687
6688fn compute_progressive_scratch_bytes(
6689 components: &[PreparedProgressiveComponentPlan],
6690 output_width: usize,
6691) -> Result<usize, JpegError> {
6692 let cap = DEFAULT_MAX_DECODE_BYTES;
6693 let mut total = 0usize;
6694 for component in components {
6695 let blocks = checked_usize_product(
6696 &[component.block_cols as usize, component.block_rows as usize],
6697 cap,
6698 )?;
6699 let coeffs = checked_usize_product(&[blocks, 64, core::mem::size_of::<i32>()], cap)?;
6700 total = total
6701 .checked_add(coeffs)
6702 .ok_or(JpegError::MemoryCapExceeded {
6703 requested: usize::MAX,
6704 cap,
6705 })?;
6706
6707 let plane = checked_usize_product(
6708 &[
6709 component.block_cols as usize,
6710 component.block_rows as usize,
6711 64,
6712 ],
6713 cap,
6714 )?;
6715 total = total
6716 .checked_add(plane)
6717 .ok_or(JpegError::MemoryCapExceeded {
6718 requested: usize::MAX,
6719 cap,
6720 })?;
6721 if total > cap {
6722 return Err(JpegError::MemoryCapExceeded {
6723 requested: total,
6724 cap,
6725 });
6726 }
6727 }
6728 total =
6729 total
6730 .checked_add(output_width.saturating_mul(3))
6731 .ok_or(JpegError::MemoryCapExceeded {
6732 requested: usize::MAX,
6733 cap,
6734 })?;
6735 if total > cap {
6736 return Err(JpegError::MemoryCapExceeded {
6737 requested: total,
6738 cap,
6739 });
6740 }
6741 Ok(total)
6742}
6743
6744struct SinkWriter<'a, S> {
6745 sink: &'a mut S,
6746 rows: SinkRows,
6747 backend: Backend,
6748}
6749
6750impl<'a, S> SinkWriter<'a, S> {
6751 fn new(sink: &'a mut S, rows: SinkRows, backend: Backend) -> Self {
6752 debug_assert_eq!(rows.top_row.len(), rows.bottom_row.len());
6753 Self {
6754 sink,
6755 rows,
6756 backend,
6757 }
6758 }
6759
6760 fn into_rows(self) -> SinkRows {
6761 self.rows
6762 }
6763}
6764
6765impl<S> InterleavedRgbWriter for SinkWriter<'_, S>
6766where
6767 S: RowSink<u8, Error = JpegError>,
6768{
6769 fn with_rgb_rows<R, F>(&mut self, y: u32, row_count: usize, fill: F) -> Result<R, JpegError>
6770 where
6771 F: FnOnce(&mut [u8], Option<&mut [u8]>) -> Result<R, JpegError>,
6772 {
6773 let result = match row_count {
6774 1 => fill(&mut self.rows.top_row, None),
6775 2 => fill(&mut self.rows.top_row, Some(&mut self.rows.bottom_row)),
6776 _ => unreachable!("SinkWriter only supports one or two rows"),
6777 }?;
6778 self.sink.write_row(y, &self.rows.top_row)?;
6779 if row_count == 2 {
6780 self.sink.write_row(y + 1, &self.rows.bottom_row)?;
6781 }
6782 Ok(result)
6783 }
6784}
6785
6786impl<S> OutputWriter for SinkWriter<'_, S>
6787where
6788 S: RowSink<u8, Error = JpegError>,
6789{
6790 fn write_rgb_row(
6791 &mut self,
6792 y: u32,
6793 r_row: &[u8],
6794 g_row: &[u8],
6795 b_row: &[u8],
6796 ) -> Result<(), JpegError> {
6797 self.backend
6798 .fill_rgb_row_from_rgb(r_row, g_row, b_row, &mut self.rows.top_row);
6799 self.sink.write_row(y, &self.rows.top_row)
6800 }
6801
6802 fn write_ycbcr_row(
6803 &mut self,
6804 y: u32,
6805 y_row: &[u8],
6806 cb_row: &[u8],
6807 cr_row: &[u8],
6808 ) -> Result<(), JpegError> {
6809 self.backend
6810 .fill_rgb_row_from_ycbcr(y_row, cb_row, cr_row, &mut self.rows.top_row);
6811 self.sink.write_row(y, &self.rows.top_row)
6812 }
6813
6814 fn write_gray_row(&mut self, y: u32, gray_row: &[u8]) -> Result<(), JpegError> {
6815 self.backend
6816 .fill_rgb_row_from_gray(gray_row, &mut self.rows.top_row);
6817 self.sink.write_row(y, &self.rows.top_row)
6818 }
6819}
6820
6821fn find_component_index(component_ids: &[u8], id: u8) -> Option<usize> {
6822 component_ids
6823 .iter()
6824 .position(|&component_id| component_id == id)
6825}
6826
6827fn compute_decode_scratch_bytes(
6828 (width, height): (u32, u32),
6829 sampling: crate::info::SamplingFactors,
6830 cap: usize,
6831) -> Result<usize, JpegError> {
6832 let max_h = u32::from(sampling.max_h);
6833 let max_v = u32::from(sampling.max_v);
6834 let mcu_width = 8u32
6835 .checked_mul(max_h)
6836 .ok_or(JpegError::MemoryCapExceeded {
6837 requested: usize::MAX,
6838 cap,
6839 })?;
6840 let mcu_height = 8u32
6841 .checked_mul(max_v)
6842 .ok_or(JpegError::MemoryCapExceeded {
6843 requested: usize::MAX,
6844 cap,
6845 })?;
6846 let mcus_per_row = width.div_ceil(mcu_width);
6847 let _mcu_rows = height.div_ceil(mcu_height);
6848
6849 let mut stripe_total = 0usize;
6850 for (h, v) in sampling.iter() {
6851 let cols = checked_usize_product(&[mcus_per_row as usize, usize::from(h), 8usize], cap)?;
6852 let rows = checked_usize_product(&[usize::from(v), 8usize], cap)?;
6853 let plane = cols.checked_mul(rows).ok_or(JpegError::MemoryCapExceeded {
6854 requested: usize::MAX,
6855 cap,
6856 })?;
6857 stripe_total = stripe_total
6858 .checked_add(plane)
6859 .ok_or(JpegError::MemoryCapExceeded {
6860 requested: usize::MAX,
6861 cap,
6862 })?;
6863 if stripe_total > cap {
6864 return Err(JpegError::MemoryCapExceeded {
6865 requested: stripe_total,
6866 cap,
6867 });
6868 }
6869 }
6870
6871 let stripe_buffers = checked_usize_product(&[stripe_total, 3], cap)?;
6872 let row_scratch = checked_usize_product(&[width as usize, 7], cap)?;
6873 let total = stripe_buffers
6874 .checked_add(row_scratch)
6875 .ok_or(JpegError::MemoryCapExceeded {
6876 requested: usize::MAX,
6877 cap,
6878 })?;
6879 if total > cap {
6880 return Err(JpegError::MemoryCapExceeded {
6881 requested: total,
6882 cap,
6883 });
6884 }
6885
6886 Ok(total)
6887}
6888
6889fn checked_usize_product(factors: &[usize], cap: usize) -> Result<usize, JpegError> {
6890 let mut value = 1usize;
6891 for factor in factors {
6892 value = value
6893 .checked_mul(*factor)
6894 .ok_or(JpegError::MemoryCapExceeded {
6895 requested: usize::MAX,
6896 cap,
6897 })?;
6898 }
6899 Ok(value)
6900}
6901
6902fn checked_scratch_len(factors: &[usize]) -> Result<usize, JpegError> {
6905 let cap = DEFAULT_MAX_DECODE_BYTES;
6906 let len = checked_usize_product(factors, cap)?;
6907 if len > cap {
6908 return Err(JpegError::MemoryCapExceeded {
6909 requested: len,
6910 cap,
6911 });
6912 }
6913 Ok(len)
6914}
6915
6916fn output_cap_error(requested: usize) -> JpegError {
6917 JpegError::MemoryCapExceeded {
6918 requested,
6919 cap: DEFAULT_MAX_DECODE_BYTES,
6920 }
6921}
6922
6923#[inline]
6924fn checked_output_geometry(
6925 width: u32,
6926 height: u32,
6927 bytes_per_pixel: usize,
6928) -> Result<(usize, usize), JpegError> {
6929 #[cfg(target_pointer_width = "64")]
6930 {
6931 let stride = width as usize * bytes_per_pixel;
6934 let len = stride * height as usize;
6935 if len > DEFAULT_MAX_DECODE_BYTES {
6936 return Err(output_cap_error(len));
6937 }
6938 Ok((stride, len))
6939 }
6940
6941 #[cfg(not(target_pointer_width = "64"))]
6942 {
6943 let stride = checked_output_product(width as usize, bytes_per_pixel)?;
6944 let len = checked_output_product(stride, height as usize)?;
6945 Ok((stride, len))
6946 }
6947}
6948
6949#[cfg(not(target_pointer_width = "64"))]
6950#[inline]
6951fn checked_output_product(left: usize, right: usize) -> Result<usize, JpegError> {
6952 let len = left
6953 .checked_mul(right)
6954 .ok_or_else(|| output_cap_error(usize::MAX))?;
6955 if len > DEFAULT_MAX_DECODE_BYTES {
6956 return Err(output_cap_error(len));
6957 }
6958 Ok(len)
6959}
6960
6961fn compute_lossless_scratch_bytes(info: &Info, cap: usize) -> Result<usize, JpegError> {
6962 if !matches!(
6967 lossless_color_sampling(info),
6968 Some(LosslessColorSampling::S422 | LosslessColorSampling::S420)
6969 ) {
6970 return Ok(0);
6971 }
6972 let width = info.dimensions.0 as usize;
6973 let height = info.dimensions.1 as usize;
6974 let bytes_per_sample: usize = if info.bit_depth > 8 { 2 } else { 1 };
6975 let chroma_width = width.div_ceil(usize::from(info.sampling.max_h));
6976 let chroma_height = height.div_ceil(usize::from(info.sampling.max_v));
6977 let luma = checked_usize_product(&[width, height, bytes_per_sample], cap)?;
6978 let chroma = checked_usize_product(&[chroma_width, chroma_height, bytes_per_sample, 2], cap)?;
6979 let total = luma
6980 .checked_add(chroma)
6981 .ok_or(JpegError::MemoryCapExceeded {
6982 requested: usize::MAX,
6983 cap,
6984 })?;
6985 if total > cap {
6986 return Err(JpegError::MemoryCapExceeded {
6987 requested: total,
6988 cap,
6989 });
6990 }
6991 Ok(total)
6992}
6993
6994fn compute_extended12_planes_scratch_bytes(
6995 components: &[PreparedComponentPlan],
6996 (width, height): (u32, u32),
6997 sampling: crate::info::SamplingFactors,
6998 cap: usize,
6999) -> Result<usize, JpegError> {
7000 let mcu_cols = width.div_ceil(u32::from(sampling.max_h) * 8) as usize;
7001 let mcu_rows = height.div_ceil(u32::from(sampling.max_v) * 8) as usize;
7002 let mut total = 0usize;
7003 for component in components {
7004 let stride = checked_usize_product(&[mcu_cols, usize::from(component.h), 8], cap)?;
7005 let rows = checked_usize_product(&[mcu_rows, usize::from(component.v), 8], cap)?;
7006 let plane = checked_usize_product(&[stride, rows, core::mem::size_of::<u16>()], cap)?;
7007 total = total
7008 .checked_add(plane)
7009 .ok_or(JpegError::MemoryCapExceeded {
7010 requested: usize::MAX,
7011 cap,
7012 })?;
7013 }
7014 if total > cap {
7015 return Err(JpegError::MemoryCapExceeded {
7016 requested: total,
7017 cap,
7018 });
7019 }
7020 Ok(total)
7021}
7022
7023#[cfg(test)]
7024mod tests {
7025 use super::*;
7026 use crate::error::Warning;
7027 use crate::output::OutputWriter;
7028 use alloc::vec;
7029 use alloc::vec::Vec;
7030
7031 fn minimal_baseline_jpeg() -> Vec<u8> {
7032 let mut v = Vec::new();
7033 v.extend_from_slice(&[0xFF, 0xD8]);
7034 v.extend_from_slice(&[0xFF, 0xDB, 0x00, 67, 0x00]);
7035 v.extend(core::iter::repeat_n(1u8, 64));
7036 v.extend_from_slice(&[
7037 0xFF,
7038 0xC0,
7039 0x00,
7040 17,
7041 8,
7042 0,
7043 16,
7044 0,
7045 16,
7046 3,
7047 1,
7048 (2 << 4) | 2,
7049 0,
7050 2,
7051 (1 << 4) | 1,
7052 0,
7053 3,
7054 (1 << 4) | 1,
7055 0,
7056 ]);
7057 v.extend_from_slice(&[
7058 0xFF, 0xC4, 0x00, 20, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xAA,
7059 ]);
7060 v.extend_from_slice(&[
7061 0xFF, 0xC4, 0x00, 20, 0x10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xBB,
7062 ]);
7063 v.extend_from_slice(&[0xFF, 0xDA, 0x00, 12, 3, 1, 0x00, 2, 0x00, 3, 0x00, 0, 63, 0]);
7064 v.extend_from_slice(&[0x00, 0xFF, 0xD9]);
7065 v
7066 }
7067
7068 fn baseline_jpeg_with_dimensions(width: u16, height: u16) -> Vec<u8> {
7069 let mut bytes = minimal_baseline_jpeg();
7070 let sof = bytes
7071 .windows(2)
7072 .position(|w| w == [0xFF, 0xC0])
7073 .expect("SOF0 marker");
7074 bytes[sof + 5..sof + 7].copy_from_slice(&height.to_be_bytes());
7075 bytes[sof + 7..sof + 9].copy_from_slice(&width.to_be_bytes());
7076 bytes
7077 }
7078
7079 fn dc_only_420_jpeg(width: u16, height: u16) -> Vec<u8> {
7080 let mut v = Vec::new();
7081 v.extend_from_slice(&[0xFF, 0xD8]);
7082 v.extend_from_slice(&[0xFF, 0xDB, 0x00, 67, 0x00]);
7083 v.extend(core::iter::repeat_n(1u8, 64));
7084 v.extend_from_slice(&[
7085 0xFF,
7086 0xC0,
7087 0x00,
7088 17,
7089 8,
7090 (height >> 8) as u8,
7091 height as u8,
7092 (width >> 8) as u8,
7093 width as u8,
7094 3,
7095 1,
7096 (2 << 4) | 2,
7097 0,
7098 2,
7099 (1 << 4) | 1,
7100 0,
7101 3,
7102 (1 << 4) | 1,
7103 0,
7104 ]);
7105 v.extend_from_slice(&[
7106 0xFF, 0xC4, 0x00, 20, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
7107 ]);
7108 v.extend_from_slice(&[
7109 0xFF, 0xC4, 0x00, 20, 0x10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
7110 ]);
7111 v.extend_from_slice(&[0xFF, 0xDA, 0x00, 12, 3, 1, 0x00, 2, 0x00, 3, 0x00, 0, 63, 0]);
7112
7113 let mcus_per_row = u32::from(width).div_ceil(16);
7114 let mcu_rows = u32::from(height).div_ceil(16);
7115 let entropy_bits = mcus_per_row * mcu_rows * 12;
7116 let entropy_bytes = (entropy_bits as usize).div_ceil(8) + 8;
7117 v.extend(core::iter::repeat_n(0u8, entropy_bytes));
7118 v.extend_from_slice(&[0xFF, 0xD9]);
7119 v
7120 }
7121
7122 #[test]
7123 fn decoder_new_succeeds_on_baseline_stream() {
7124 let bytes = minimal_baseline_jpeg();
7125 let dec = Decoder::new(&bytes).unwrap();
7126 assert_eq!(dec.info().dimensions, (16, 16));
7127 }
7128
7129 #[test]
7130 fn owned_baseline_decode_with_huge_dimensions_errors_before_allocating() {
7131 let bytes = baseline_jpeg_with_dimensions(65_500, 65_500);
7132 let dec = Decoder::new(&bytes).expect("huge baseline header should parse");
7133
7134 let err = dec.decode(PixelFormat::Rgb8).unwrap_err();
7135
7136 assert!(
7137 matches!(err, JpegError::MemoryCapExceeded { .. }),
7138 "expected MemoryCapExceeded before owned output allocation, got {err:?}"
7139 );
7140 }
7141
7142 #[test]
7143 fn owned_baseline_region_decode_with_huge_dimensions_errors_before_allocating() {
7144 let bytes = baseline_jpeg_with_dimensions(65_500, 65_500);
7145 let dec = Decoder::new(&bytes).expect("huge baseline header should parse");
7146
7147 let err = dec
7148 .decode_region(PixelFormat::Rgb8, Rect::full(dec.info().dimensions))
7149 .unwrap_err();
7150
7151 assert!(
7152 matches!(err, JpegError::MemoryCapExceeded { .. }),
7153 "expected MemoryCapExceeded before region output allocation, got {err:?}"
7154 );
7155 }
7156
7157 fn minimal_lossless_jpeg(
7158 width: u16,
7159 height: u16,
7160 precision: u8,
7161 sampling_420: bool,
7162 ) -> Vec<u8> {
7163 let mut v = Vec::new();
7164 v.extend_from_slice(&[0xFF, 0xD8]);
7165 let first_sampling = if sampling_420 {
7166 (2 << 4) | 2
7167 } else {
7168 (1 << 4) | 1
7169 };
7170 v.extend_from_slice(&[
7171 0xFF,
7172 0xC3,
7173 0x00,
7174 17,
7175 precision,
7176 (height >> 8) as u8,
7177 height as u8,
7178 (width >> 8) as u8,
7179 width as u8,
7180 3,
7181 1,
7182 first_sampling,
7183 0,
7184 2,
7185 (1 << 4) | 1,
7186 0,
7187 3,
7188 (1 << 4) | 1,
7189 0,
7190 ]);
7191 v.extend_from_slice(&[
7192 0xFF, 0xC4, 0x00, 20, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x00,
7193 ]);
7194 v.extend_from_slice(&[0xFF, 0xDA, 0x00, 12, 3, 1, 0x00, 2, 0x00, 3, 0x00, 1, 0, 0]);
7196 v.extend_from_slice(&[0x00, 0x00, 0xFF, 0xD9]);
7197 v
7198 }
7199
7200 #[test]
7201 fn lossless_sampled_plan_with_huge_dimensions_is_rejected_by_memory_cap() {
7202 let bytes = minimal_lossless_jpeg(65_500, 65_500, 16, true);
7205 match Decoder::new(&bytes) {
7206 Err(JpegError::MemoryCapExceeded { .. }) => {}
7207 other => panic!("expected MemoryCapExceeded at plan build, got {other:?}"),
7208 }
7209 }
7210
7211 #[test]
7212 fn lossless_sampled_plan_with_small_dimensions_still_parses() {
7213 let bytes = minimal_lossless_jpeg(16, 16, 16, true);
7214 let dec = Decoder::new(&bytes).expect("small lossless stream should parse");
7215 assert_eq!(dec.info().dimensions, (16, 16));
7216 assert!(
7217 dec.plan.scratch_bytes > 0,
7218 "sampled lossless plan must report scratch"
7219 );
7220 }
7221
7222 #[test]
7223 fn extended12_plan_with_huge_dimensions_is_rejected_by_memory_cap() {
7224 let mut bytes = minimal_baseline_jpeg();
7227 let p = bytes.windows(2).position(|w| w == [0xFF, 0xC0]).unwrap();
7228 bytes[p + 1] = 0xC1;
7229 bytes[p + 4] = 12;
7230 bytes[p + 5] = (65_500u16 >> 8) as u8;
7231 bytes[p + 6] = 65_500u16 as u8;
7232 bytes[p + 7] = (65_500u16 >> 8) as u8;
7233 bytes[p + 8] = 65_500u16 as u8;
7234 match Decoder::new(&bytes) {
7235 Err(JpegError::MemoryCapExceeded { .. }) => {}
7236 other => panic!("expected MemoryCapExceeded at plan build, got {other:?}"),
7237 }
7238 }
7239
7240 #[test]
7241 fn lossless_gray16_region_scaled_with_huge_dimensions_errors_before_allocating() {
7242 let mut bytes = minimal_lossless_jpeg(65_500, 65_500, 16, false);
7246 let p = bytes.windows(2).position(|w| w == [0xFF, 0xC3]).unwrap();
7247 bytes[p + 3] = 11;
7249 bytes[p + 9] = 1;
7250 bytes[p + 10] = 1;
7251 bytes[p + 11] = (1 << 4) | 1;
7252 bytes[p + 12] = 0;
7253 bytes.splice(p + 13..p + 19, core::iter::empty());
7254 let p = bytes.windows(2).position(|w| w == [0xFF, 0xDA]).unwrap();
7255 bytes.splice(
7256 p..p + 14,
7257 [0xFF, 0xDA, 0x00, 8, 1, 1, 0x00, 1, 0, 0].iter().copied(),
7258 );
7259 let dec = Decoder::new(&bytes).expect("huge lossless gray stream should parse");
7260 let roi = Rect {
7261 x: 0,
7262 y: 0,
7263 w: 16,
7264 h: 16,
7265 };
7266 let mut out = vec![0u8; 16 * 16 * 2];
7267 let err = dec
7268 .decode_region_scaled_into(&mut out, 16 * 2, PixelFormat::Gray16, roi, Downscale::Half)
7269 .unwrap_err();
7270 assert!(
7271 matches!(err, JpegError::MemoryCapExceeded { .. }),
7272 "expected MemoryCapExceeded, got {err:?}"
7273 );
7274 }
7275
7276 #[test]
7277 fn decode_into_rejects_unsupported_extended12_ycbcr_sampling_with_not_implemented() {
7278 let mut bytes = minimal_baseline_jpeg();
7279 let p = bytes.windows(2).position(|w| w == [0xFF, 0xC0]).unwrap();
7280 bytes[p + 1] = 0xC1;
7281 bytes[p + 4] = 12;
7282 bytes[p + 11] = (1 << 4) | 2;
7283 let dec = Decoder::new(&bytes).expect("unsupported Extended12 YCbCr sampling should parse");
7284 let stride = dec.info().dimensions.0 as usize * PixelFormat::Rgb16.bytes_per_pixel();
7285 let mut out = vec![0u8; stride * dec.info().dimensions.1 as usize];
7286
7287 let err = dec
7288 .decode_into(&mut out, stride, PixelFormat::Rgb16)
7289 .unwrap_err();
7290
7291 assert!(err.is_not_implemented());
7292 }
7293
7294 #[test]
7295 fn decoder_new_rejects_arithmetic_as_unsupported() {
7296 let mut bytes = minimal_baseline_jpeg();
7297 let p = bytes.windows(2).position(|w| w == [0xFF, 0xC0]).unwrap();
7298 bytes[p + 1] = 0xC9;
7299 let err = Decoder::new(&bytes).unwrap_err();
7300 assert!(err.is_unsupported());
7301 }
7302
7303 #[test]
7304 fn decode_outcome_carries_rect_and_warnings() {
7305 let outcome = DecodeOutcome {
7306 decoded: Rect {
7307 x: 0,
7308 y: 0,
7309 w: 16,
7310 h: 16,
7311 },
7312 warnings: vec![Warning::MissingEoi],
7313 };
7314 assert_eq!(outcome.decoded.w, 16);
7315 assert_eq!(outcome.warnings.len(), 1);
7316 }
7317
7318 #[test]
7319 fn decode_into_rejects_undersized_buffer() {
7320 let bytes = minimal_baseline_jpeg();
7321 let dec = Decoder::new(&bytes).unwrap();
7322 let mut buf = vec![0u8; 4];
7323 let err = dec
7324 .decode_into(&mut buf, 48, PixelFormat::Rgb8)
7325 .unwrap_err();
7326 assert!(matches!(err, JpegError::OutputBufferTooSmall { .. }));
7327 }
7328
7329 #[test]
7330 fn decode_into_rejects_invalid_stride() {
7331 let bytes = minimal_baseline_jpeg();
7332 let dec = Decoder::new(&bytes).unwrap();
7333 let mut buf = vec![0u8; 16 * 16 * 3];
7334 let err = dec
7335 .decode_into(&mut buf, 10, PixelFormat::Rgb8)
7336 .unwrap_err();
7337 assert!(matches!(err, JpegError::InvalidStride { .. }));
7338 }
7339
7340 #[test]
7341 fn large_fast_420_region_decode_populates_cpu_entropy_checkpoints() {
7342 let bytes = dc_only_420_jpeg(1024, 2048);
7343 let dec = Decoder::new(&bytes).expect("decoder");
7344 assert!(dec.plan.matches_fast_tile_shape());
7345
7346 let roi = Rect {
7347 x: 64,
7348 y: 1536,
7349 w: 64,
7350 h: 64,
7351 };
7352 let mut out = vec![0u8; roi.w as usize * roi.h as usize * 3];
7353 let mut pool = ScratchPool::new();
7354 dec.decode_region_into_with_scratch(
7355 &mut pool,
7356 &mut out,
7357 roi.w as usize * 3,
7358 PixelFormat::Rgb8,
7359 roi,
7360 )
7361 .expect("deep ROI decode");
7362
7363 let cache = dec
7364 .cpu_entropy_checkpoints
7365 .lock()
7366 .expect("checkpoint cache mutex");
7367 assert!(cache
7368 .checkpoints
7369 .iter()
7370 .any(|checkpoint| checkpoint.mcu_index >= CPU_ROI_CHECKPOINT_MIN_TARGET_MCUS));
7371 }
7372
7373 #[derive(Default)]
7374 struct GrayRows {
7375 rows: Vec<(u32, Vec<u8>)>,
7376 }
7377
7378 impl OutputWriter for GrayRows {
7379 fn write_rgb_row(
7380 &mut self,
7381 _y: u32,
7382 _r_row: &[u8],
7383 _g_row: &[u8],
7384 _b_row: &[u8],
7385 ) -> Result<(), JpegError> {
7386 unreachable!("gray test writer should not receive rgb rows");
7387 }
7388
7389 fn write_ycbcr_row(
7390 &mut self,
7391 _y: u32,
7392 _y_row: &[u8],
7393 _cb_row: &[u8],
7394 _cr_row: &[u8],
7395 ) -> Result<(), JpegError> {
7396 unreachable!("gray test writer should not receive ycbcr rows");
7397 }
7398
7399 fn write_gray_row(&mut self, y: u32, gray_row: &[u8]) -> Result<(), JpegError> {
7400 self.rows.push((y, gray_row.to_vec()));
7401 Ok(())
7402 }
7403 }
7404
7405 #[test]
7406 fn cropped_writer_honors_source_window_origin() {
7407 let inner = GrayRows::default();
7408 let rect = Rect {
7409 x: 6,
7410 y: 1,
7411 w: 2,
7412 h: 1,
7413 };
7414 let mut writer = CroppedWriter::new(inner, rect, 4, 4);
7415
7416 writer
7417 .write_gray_row(1, &[10, 20, 30, 40])
7418 .expect("crop write must succeed");
7419
7420 assert_eq!(writer.inner.rows, vec![(0, vec![30, 40])]);
7421 }
7422}