1use crate::decoder::Decoder;
4use crate::error::{JpegError, MarkerKind};
5use crate::info::{ColorSpace, SamplingFactors, SofKind};
6use crate::internal::checkpoint::{build_checkpoint_plan, DeviceCheckpoint};
7use crate::parse::header::parse_header;
8use crate::parse::scan::ScanComponent;
9use crate::parse::tables::RawHuffmanTable;
10use alloc::vec::Vec;
11
12const MAX_NONRESTART_ENTROPY_CHECKPOINTS: u32 = 2048;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum FastPacketError {
17 Decode(JpegError),
19 UnsupportedSof(SofKind),
21 UnsupportedColorSpace(ColorSpace),
23 UnsupportedSampling,
25 UnsupportedComponentOrder,
27 MissingScan,
29 MissingQuantTable {
31 slot: u8,
33 },
34 MissingHuffmanTable {
36 kind: TableKind,
38 slot: u8,
40 },
41 EntropyMarkerUnsupported {
43 marker: u8,
45 },
46 TruncatedEntropy,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum TableKind {
53 Dc,
55 Ac,
57}
58
59impl From<JpegError> for FastPacketError {
60 fn from(value: JpegError) -> Self {
61 Self::Decode(value)
62 }
63}
64
65#[repr(C)]
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct JpegHuffmanTable {
69 pub bits: [u8; 16],
71 pub values_len: u16,
73 pub values: [u8; 256],
75}
76
77#[repr(C)]
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct JpegEntropyCheckpointV1 {
81 pub mcu_index: u32,
83 pub entropy_pos: u32,
85 pub bit_acc: u64,
87 pub bit_count: u32,
89 pub y_prev_dc: i32,
91 pub cb_prev_dc: i32,
93 pub cr_prev_dc: i32,
95 pub reserved: u32,
97}
98
99impl JpegHuffmanTable {
100 fn from_raw(raw: &RawHuffmanTable) -> Self {
101 let mut values = [0u8; 256];
102 let slice = raw.values.as_slice();
103 values[..slice.len()].copy_from_slice(slice);
104 Self {
105 bits: raw.bits,
106 values_len: slice.len() as u16,
107 values,
108 }
109 }
110}
111
112#[repr(C)]
113#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct JpegFast420PacketV1 {
116 pub dimensions: (u32, u32),
118 pub mcus_per_row: u32,
120 pub mcu_rows: u32,
122 pub restart_interval_mcus: u32,
124 pub restart_offsets: Vec<u32>,
126 pub entropy_checkpoints: Vec<JpegEntropyCheckpointV1>,
128 pub y_quant: [u16; 64],
130 pub cb_quant: [u16; 64],
132 pub cr_quant: [u16; 64],
134 pub y_dc_table: JpegHuffmanTable,
136 pub y_ac_table: JpegHuffmanTable,
138 pub cb_dc_table: JpegHuffmanTable,
140 pub cb_ac_table: JpegHuffmanTable,
142 pub cr_dc_table: JpegHuffmanTable,
144 pub cr_ac_table: JpegHuffmanTable,
146 pub entropy_bytes: Vec<u8>,
148}
149
150#[repr(C)]
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct JpegFast422PacketV1 {
154 pub dimensions: (u32, u32),
156 pub mcus_per_row: u32,
158 pub mcu_rows: u32,
160 pub restart_interval_mcus: u32,
162 pub restart_offsets: Vec<u32>,
164 pub entropy_checkpoints: Vec<JpegEntropyCheckpointV1>,
166 pub y_quant: [u16; 64],
168 pub cb_quant: [u16; 64],
170 pub cr_quant: [u16; 64],
172 pub y_dc_table: JpegHuffmanTable,
174 pub y_ac_table: JpegHuffmanTable,
176 pub cb_dc_table: JpegHuffmanTable,
178 pub cb_ac_table: JpegHuffmanTable,
180 pub cr_dc_table: JpegHuffmanTable,
182 pub cr_ac_table: JpegHuffmanTable,
184 pub entropy_bytes: Vec<u8>,
186}
187
188#[repr(C)]
189#[derive(Debug, Clone, PartialEq, Eq)]
190pub struct JpegFast444PacketV1 {
192 pub dimensions: (u32, u32),
194 pub mcus_per_row: u32,
196 pub mcu_rows: u32,
198 pub restart_interval_mcus: u32,
200 pub restart_offsets: Vec<u32>,
202 pub entropy_checkpoints: Vec<JpegEntropyCheckpointV1>,
204 pub y_quant: [u16; 64],
206 pub cb_quant: [u16; 64],
208 pub cr_quant: [u16; 64],
210 pub y_dc_table: JpegHuffmanTable,
212 pub y_ac_table: JpegHuffmanTable,
214 pub cb_dc_table: JpegHuffmanTable,
216 pub cb_ac_table: JpegHuffmanTable,
218 pub cr_dc_table: JpegHuffmanTable,
220 pub cr_ac_table: JpegHuffmanTable,
222 pub entropy_bytes: Vec<u8>,
224}
225
226#[repr(C)]
227#[derive(Debug, Clone, PartialEq, Eq)]
228pub struct JpegGrayPacketV1 {
230 pub dimensions: (u32, u32),
232 pub mcus_per_row: u32,
234 pub mcu_rows: u32,
236 pub restart_interval_mcus: u32,
238 pub restart_offsets: Vec<u32>,
240 pub y_quant: [u16; 64],
242 pub y_dc_table: JpegHuffmanTable,
244 pub y_ac_table: JpegHuffmanTable,
246 pub entropy_bytes: Vec<u8>,
248}
249
250#[derive(Debug, Clone, Copy)]
251struct FastLayout {
252 sampling: &'static [(u8, u8)],
253 allow_rgb: bool,
254 mcu_width: u32,
255 mcu_height: u32,
256}
257
258const FAST420_LAYOUT: FastLayout = FastLayout {
259 sampling: &[(2, 2), (1, 1), (1, 1)],
260 allow_rgb: false,
261 mcu_width: 16,
262 mcu_height: 16,
263};
264
265const FAST422_LAYOUT: FastLayout = FastLayout {
266 sampling: &[(2, 1), (1, 1), (1, 1)],
267 allow_rgb: false,
268 mcu_width: 16,
269 mcu_height: 8,
270};
271
272const FAST444_LAYOUT: FastLayout = FastLayout {
273 sampling: &[(1, 1), (1, 1), (1, 1)],
274 allow_rgb: true,
275 mcu_width: 8,
276 mcu_height: 8,
277};
278
279#[derive(Debug, Clone, PartialEq, Eq)]
280struct ColorFastPacketParts {
281 dimensions: (u32, u32),
282 mcus_per_row: u32,
283 mcu_rows: u32,
284 restart_interval_mcus: u32,
285 restart_offsets: Vec<u32>,
286 entropy_checkpoints: Vec<JpegEntropyCheckpointV1>,
287 y_quant: [u16; 64],
288 cb_quant: [u16; 64],
289 cr_quant: [u16; 64],
290 y_dc_table: JpegHuffmanTable,
291 y_ac_table: JpegHuffmanTable,
292 cb_dc_table: JpegHuffmanTable,
293 cb_ac_table: JpegHuffmanTable,
294 cr_dc_table: JpegHuffmanTable,
295 cr_ac_table: JpegHuffmanTable,
296 entropy_bytes: Vec<u8>,
297}
298
299macro_rules! impl_from_color_fast_packet_parts {
300 ($packet:ty) => {
301 impl From<ColorFastPacketParts> for $packet {
302 fn from(parts: ColorFastPacketParts) -> Self {
303 Self {
304 dimensions: parts.dimensions,
305 mcus_per_row: parts.mcus_per_row,
306 mcu_rows: parts.mcu_rows,
307 restart_interval_mcus: parts.restart_interval_mcus,
308 restart_offsets: parts.restart_offsets,
309 entropy_checkpoints: parts.entropy_checkpoints,
310 y_quant: parts.y_quant,
311 cb_quant: parts.cb_quant,
312 cr_quant: parts.cr_quant,
313 y_dc_table: parts.y_dc_table,
314 y_ac_table: parts.y_ac_table,
315 cb_dc_table: parts.cb_dc_table,
316 cb_ac_table: parts.cb_ac_table,
317 cr_dc_table: parts.cr_dc_table,
318 cr_ac_table: parts.cr_ac_table,
319 entropy_bytes: parts.entropy_bytes,
320 }
321 }
322 }
323 };
324}
325
326impl_from_color_fast_packet_parts!(JpegFast420PacketV1);
327impl_from_color_fast_packet_parts!(JpegFast422PacketV1);
328impl_from_color_fast_packet_parts!(JpegFast444PacketV1);
329
330#[derive(Debug, Clone, PartialEq, Eq)]
331struct EntropySegments {
332 entropy_bytes: Vec<u8>,
333 restart_offsets: Vec<u32>,
334}
335
336pub fn build_fast420_packet(bytes: &[u8]) -> Result<JpegFast420PacketV1, FastPacketError> {
338 build_color_fast_packet(bytes, FAST420_LAYOUT).map(Into::into)
339}
340
341pub fn build_fast444_packet(bytes: &[u8]) -> Result<JpegFast444PacketV1, FastPacketError> {
343 build_color_fast_packet(bytes, FAST444_LAYOUT).map(Into::into)
344}
345
346pub fn build_fast422_packet(bytes: &[u8]) -> Result<JpegFast422PacketV1, FastPacketError> {
348 build_color_fast_packet(bytes, FAST422_LAYOUT).map(Into::into)
349}
350
351fn build_color_fast_packet(
352 bytes: &[u8],
353 layout: FastLayout,
354) -> Result<ColorFastPacketParts, FastPacketError> {
355 let decoder = Decoder::new(bytes)?;
356 let header = parse_header(bytes)?;
357 if !matches!(header.sof_kind, SofKind::Baseline8 | SofKind::Extended8) {
358 return Err(FastPacketError::UnsupportedSof(header.sof_kind));
359 }
360 if header.bit_depth != 8 {
361 return Err(FastPacketError::Decode(JpegError::UnsupportedBitDepth {
362 depth: header.bit_depth,
363 }));
364 }
365 let color_space = header.color_space();
366 if color_space != ColorSpace::YCbCr && !(layout.allow_rgb && color_space == ColorSpace::Rgb) {
367 return Err(FastPacketError::UnsupportedColorSpace(header.color_space()));
368 }
369 if header.sampling != SamplingFactors::from_validated_components(layout.sampling) {
370 return Err(FastPacketError::UnsupportedSampling);
371 }
372 let scan = header.scan.as_ref().ok_or(FastPacketError::MissingScan)?;
373 let [y_scan, cb_scan, cr_scan] = ordered_scan_triplet(&header.component_ids, &scan.components)?;
374
375 let y_quant = quant_for_component(&header.quant_table_ids, &header.quant_tables.entries, 0)?;
376 let cb_quant = quant_for_component(&header.quant_table_ids, &header.quant_tables.entries, 1)?;
377 let cr_quant = quant_for_component(&header.quant_table_ids, &header.quant_tables.entries, 2)?;
378 let y_dc_table = huffman_table(&header.huffman_tables.dc, TableKind::Dc, y_scan.dc_table)?;
379 let y_ac_table = huffman_table(&header.huffman_tables.ac, TableKind::Ac, y_scan.ac_table)?;
380 let cb_dc_table = huffman_table(&header.huffman_tables.dc, TableKind::Dc, cb_scan.dc_table)?;
381 let cb_ac_table = huffman_table(&header.huffman_tables.ac, TableKind::Ac, cb_scan.ac_table)?;
382 let cr_dc_table = huffman_table(&header.huffman_tables.dc, TableKind::Dc, cr_scan.dc_table)?;
383 let cr_ac_table = huffman_table(&header.huffman_tables.ac, TableKind::Ac, cr_scan.ac_table)?;
384
385 let entropy_offset = header.sos_offset.ok_or(FastPacketError::MissingScan)?;
386 let restart_interval_mcus = u32::from(header.restart_interval.unwrap_or(0));
387 let EntropySegments {
388 entropy_bytes,
389 restart_offsets,
390 } = extract_entropy_segments(&bytes[entropy_offset..], header.restart_interval)?;
391 let (width, height) = header.dimensions;
392 let mcus_per_row = width.div_ceil(layout.mcu_width);
393 let mcu_rows = height.div_ceil(layout.mcu_height);
394 let total_mcus = mcus_per_row
395 .checked_mul(mcu_rows)
396 .ok_or(FastPacketError::Decode(JpegError::DimensionOverflow {
397 width,
398 height,
399 }))?;
400 let entropy_checkpoints =
401 build_fast_entropy_checkpoints(&decoder, &bytes[entropy_offset..], total_mcus)?;
402
403 Ok(ColorFastPacketParts {
404 dimensions: header.dimensions,
405 mcus_per_row,
406 mcu_rows,
407 restart_interval_mcus,
408 restart_offsets,
409 entropy_checkpoints,
410 y_quant,
411 cb_quant,
412 cr_quant,
413 y_dc_table,
414 y_ac_table,
415 cb_dc_table,
416 cb_ac_table,
417 cr_dc_table,
418 cr_ac_table,
419 entropy_bytes,
420 })
421}
422
423pub fn build_gray_packet(bytes: &[u8]) -> Result<JpegGrayPacketV1, FastPacketError> {
425 let header = parse_header(bytes)?;
426 if !matches!(header.sof_kind, SofKind::Baseline8 | SofKind::Extended8) {
427 return Err(FastPacketError::UnsupportedSof(header.sof_kind));
428 }
429 if header.bit_depth != 8 {
430 return Err(FastPacketError::Decode(JpegError::UnsupportedBitDepth {
431 depth: header.bit_depth,
432 }));
433 }
434 if header.color_space() != ColorSpace::Grayscale {
435 return Err(FastPacketError::UnsupportedColorSpace(header.color_space()));
436 }
437 if header.sampling != SamplingFactors::from_validated_components(&[(1, 1)]) {
438 return Err(FastPacketError::UnsupportedSampling);
439 }
440
441 let scan = header.scan.as_ref().ok_or(FastPacketError::MissingScan)?;
442 if header.component_ids.len() != 1 || scan.components.len() != 1 {
443 return Err(FastPacketError::UnsupportedComponentOrder);
444 }
445 if scan.components[0].id != header.component_ids[0] {
446 return Err(FastPacketError::UnsupportedComponentOrder);
447 }
448
449 let y_quant = quant_for_component(&header.quant_table_ids, &header.quant_tables.entries, 0)?;
450 let y_dc_table = huffman_table(
451 &header.huffman_tables.dc,
452 TableKind::Dc,
453 scan.components[0].dc_table,
454 )?;
455 let y_ac_table = huffman_table(
456 &header.huffman_tables.ac,
457 TableKind::Ac,
458 scan.components[0].ac_table,
459 )?;
460
461 let entropy_offset = header.sos_offset.ok_or(FastPacketError::MissingScan)?;
462 let restart_interval_mcus = u32::from(header.restart_interval.unwrap_or(0));
463 let EntropySegments {
464 entropy_bytes,
465 restart_offsets,
466 } = extract_entropy_segments(&bytes[entropy_offset..], header.restart_interval)?;
467 let (width, height) = header.dimensions;
468
469 Ok(JpegGrayPacketV1 {
470 dimensions: header.dimensions,
471 mcus_per_row: width.div_ceil(8),
472 mcu_rows: height.div_ceil(8),
473 restart_interval_mcus,
474 restart_offsets,
475 y_quant,
476 y_dc_table,
477 y_ac_table,
478 entropy_bytes,
479 })
480}
481
482pub fn build_fast420_packet_for_decoder(
484 decoder: &crate::decoder::Decoder<'_>,
485) -> Result<JpegFast420PacketV1, FastPacketError> {
486 build_fast420_packet(decoder.bytes)
487}
488
489pub fn build_fast444_packet_for_decoder(
491 decoder: &crate::decoder::Decoder<'_>,
492) -> Result<JpegFast444PacketV1, FastPacketError> {
493 build_fast444_packet(decoder.bytes)
494}
495
496pub fn build_fast422_packet_for_decoder(
498 decoder: &crate::decoder::Decoder<'_>,
499) -> Result<JpegFast422PacketV1, FastPacketError> {
500 build_fast422_packet(decoder.bytes)
501}
502
503pub fn build_gray_packet_for_decoder(
505 decoder: &crate::decoder::Decoder<'_>,
506) -> Result<JpegGrayPacketV1, FastPacketError> {
507 build_gray_packet(decoder.bytes)
508}
509
510fn quant_for_component(
511 quant_table_ids: &[u8],
512 tables: &[Option<[u16; 64]>; 4],
513 component_idx: usize,
514) -> Result<[u16; 64], FastPacketError> {
515 let slot = *quant_table_ids
516 .get(component_idx)
517 .ok_or(FastPacketError::UnsupportedComponentOrder)?;
518 tables[slot as usize].ok_or(FastPacketError::MissingQuantTable { slot })
519}
520
521fn ordered_scan_triplet(
522 component_ids: &[u8],
523 scan_components: &[ScanComponent],
524) -> Result<[ScanComponent; 3], FastPacketError> {
525 if component_ids.len() != 3 || scan_components.len() != 3 {
526 return Err(FastPacketError::UnsupportedComponentOrder);
527 }
528
529 let mut ordered = [None; 3];
530 for (index, &component_id) in component_ids.iter().enumerate() {
531 let Some(component) = scan_components
532 .iter()
533 .copied()
534 .find(|component| component.id == component_id)
535 else {
536 return Err(FastPacketError::UnsupportedComponentOrder);
537 };
538 ordered[index] = Some(component);
539 }
540
541 match ordered {
542 [Some(first), Some(second), Some(third)] => Ok([first, second, third]),
543 _ => Err(FastPacketError::UnsupportedComponentOrder),
544 }
545}
546
547fn huffman_table(
548 tables: &[Option<RawHuffmanTable>; 4],
549 kind: TableKind,
550 slot: u8,
551) -> Result<JpegHuffmanTable, FastPacketError> {
552 let raw = tables[slot as usize]
553 .as_ref()
554 .ok_or(FastPacketError::MissingHuffmanTable { kind, slot })?;
555 Ok(JpegHuffmanTable::from_raw(raw))
556}
557
558fn nonrestart_entropy_chunk_mcus(total_mcus: u32) -> u32 {
559 total_mcus
560 .div_ceil(MAX_NONRESTART_ENTROPY_CHECKPOINTS)
561 .max(1)
562}
563
564fn build_fast_entropy_checkpoints(
565 decoder: &Decoder<'_>,
566 scan_bytes: &[u8],
567 total_mcus: u32,
568) -> Result<Vec<JpegEntropyCheckpointV1>, FastPacketError> {
569 let device_checkpoints = build_checkpoint_plan(
570 &decoder.plan,
571 scan_bytes,
572 nonrestart_entropy_chunk_mcus(total_mcus),
573 )?;
574 device_checkpoints
575 .iter()
576 .map(|checkpoint| packet_checkpoint_from_device(checkpoint, scan_bytes))
577 .collect()
578}
579
580fn packet_checkpoint_from_device(
581 checkpoint: &DeviceCheckpoint,
582 scan_bytes: &[u8],
583) -> Result<JpegEntropyCheckpointV1, FastPacketError> {
584 Ok(JpegEntropyCheckpointV1 {
585 mcu_index: checkpoint.mcu_index,
586 entropy_pos: destuffed_entropy_offset(scan_bytes, checkpoint.scan_offset)?,
587 bit_acc: checkpoint.bit_accumulator,
588 bit_count: u32::from(checkpoint.bits_buffered),
589 y_prev_dc: checkpoint.prev_dc[0],
590 cb_prev_dc: checkpoint.prev_dc[1],
591 cr_prev_dc: checkpoint.prev_dc[2],
592 reserved: 0,
593 })
594}
595
596fn destuffed_entropy_offset(scan_bytes: &[u8], target: usize) -> Result<u32, FastPacketError> {
597 if target > scan_bytes.len() {
598 return Err(FastPacketError::TruncatedEntropy);
599 }
600
601 let mut pos = 0usize;
602 let mut destuffed = 0usize;
603 while pos < target {
604 if scan_bytes[pos] != 0xff {
605 pos += 1;
606 destuffed += 1;
607 continue;
608 }
609
610 let marker = *scan_bytes
611 .get(pos + 1)
612 .ok_or(FastPacketError::TruncatedEntropy)?;
613 if pos + 2 > target {
614 return Err(FastPacketError::TruncatedEntropy);
615 }
616 match marker {
617 0x00 => {
618 pos += 2;
619 destuffed += 1;
620 }
621 0xd0..=0xd7 | 0xd9 => {
622 pos += 2;
623 }
624 marker => return Err(FastPacketError::EntropyMarkerUnsupported { marker }),
625 }
626 }
627
628 if pos != target {
629 return Err(FastPacketError::TruncatedEntropy);
630 }
631 u32::try_from(destuffed).map_err(|_| FastPacketError::TruncatedEntropy)
632}
633
634fn extract_entropy_segments(
635 bytes: &[u8],
636 restart_interval: Option<u16>,
637) -> Result<EntropySegments, FastPacketError> {
638 let mut out = Vec::with_capacity(bytes.len());
639 let mut restart_offsets = vec![0u32];
640 let mut pos = 0usize;
641 let mut expected_rst = 0xD0u8;
642 while pos < bytes.len() {
643 let byte = bytes[pos];
644 if byte != 0xFF {
645 out.push(byte);
646 pos += 1;
647 continue;
648 }
649 let next = *bytes
650 .get(pos + 1)
651 .ok_or(FastPacketError::TruncatedEntropy)?;
652 match next {
653 0x00 => {
654 out.push(0xFF);
655 pos += 2;
656 }
657 0xD9 => {
658 return Ok(EntropySegments {
659 entropy_bytes: out,
660 restart_offsets,
661 });
662 }
663 0xD0..=0xD7 if restart_interval.unwrap_or(0) != 0 => {
664 if next != expected_rst {
665 return Err(FastPacketError::EntropyMarkerUnsupported { marker: next });
666 }
667 restart_offsets
668 .push(u32::try_from(out.len()).map_err(|_| FastPacketError::TruncatedEntropy)?);
669 expected_rst = if expected_rst == 0xD7 {
670 0xD0
671 } else {
672 expected_rst + 1
673 };
674 pos += 2;
675 }
676 marker => {
677 return Err(FastPacketError::EntropyMarkerUnsupported { marker });
678 }
679 }
680 }
681 Err(FastPacketError::Decode(JpegError::MissingMarker {
682 marker: MarkerKind::Eoi,
683 }))
684}