Skip to main content

j2k_jpeg/adapter/
fast_packet.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use 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)]
15/// Error while building a backend fast-path JPEG packet.
16pub enum FastPacketError {
17    /// Header or entropy decode failed.
18    Decode(JpegError),
19    /// JPEG SOF kind is not supported by the fast path.
20    UnsupportedSof(SofKind),
21    /// JPEG color space is not supported by the selected fast path.
22    UnsupportedColorSpace(ColorSpace),
23    /// JPEG component sampling does not match the selected fast path.
24    UnsupportedSampling,
25    /// Scan component order does not match SOF component order.
26    UnsupportedComponentOrder,
27    /// Stream does not contain a scan payload.
28    MissingScan,
29    /// Referenced quantization table is absent.
30    MissingQuantTable {
31        /// Quantization table slot.
32        slot: u8,
33    },
34    /// Referenced Huffman table is absent.
35    MissingHuffmanTable {
36        /// Huffman table class.
37        kind: TableKind,
38        /// Huffman table slot.
39        slot: u8,
40    },
41    /// Entropy payload contains a marker unsupported by the fast path.
42    EntropyMarkerUnsupported {
43        /// Raw marker byte following `0xff`.
44        marker: u8,
45    },
46    /// Entropy payload ended before the packet could be built.
47    TruncatedEntropy,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51/// Huffman table class used by fast-path packet builders.
52pub enum TableKind {
53    /// DC Huffman table.
54    Dc,
55    /// AC Huffman table.
56    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)]
67/// Huffman table payload copied into backend-compatible packet structs.
68pub struct JpegHuffmanTable {
69    /// JPEG BITS counts for code lengths 1 through 16.
70    pub bits: [u8; 16],
71    /// Number of populated entries in `values`.
72    pub values_len: u16,
73    /// JPEG HUFFVAL symbols padded to fixed capacity.
74    pub values: [u8; 256],
75}
76
77#[repr(C)]
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79/// Entropy decoder resume point for fast-path packet decoding.
80pub struct JpegEntropyCheckpointV1 {
81    /// MCU index for this checkpoint.
82    pub mcu_index: u32,
83    /// Byte offset into the entropy payload.
84    pub entropy_pos: u32,
85    /// Buffered entropy bits.
86    pub bit_acc: u64,
87    /// Number of valid bits in `bit_acc`.
88    pub bit_count: u32,
89    /// Previous Y DC predictor.
90    pub y_prev_dc: i32,
91    /// Previous Cb DC predictor.
92    pub cb_prev_dc: i32,
93    /// Previous Cr DC predictor.
94    pub cr_prev_dc: i32,
95    /// Reserved for ABI-compatible future expansion.
96    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)]
114/// Backend fast-path packet for 8-bit 4:2:0 JPEG tiles.
115pub struct JpegFast420PacketV1 {
116    /// Image dimensions as `(width, height)` in pixels.
117    pub dimensions: (u32, u32),
118    /// Number of MCUs per row.
119    pub mcus_per_row: u32,
120    /// Number of MCU rows.
121    pub mcu_rows: u32,
122    /// Restart interval in MCUs, or zero when absent.
123    pub restart_interval_mcus: u32,
124    /// Byte offsets of restart-addressable entropy segments.
125    pub restart_offsets: Vec<u32>,
126    /// Entropy resume checkpoints.
127    pub entropy_checkpoints: Vec<JpegEntropyCheckpointV1>,
128    /// Y quantization table in natural order.
129    pub y_quant: [u16; 64],
130    /// Cb quantization table in natural order.
131    pub cb_quant: [u16; 64],
132    /// Cr quantization table in natural order.
133    pub cr_quant: [u16; 64],
134    /// Y DC Huffman table.
135    pub y_dc_table: JpegHuffmanTable,
136    /// Y AC Huffman table.
137    pub y_ac_table: JpegHuffmanTable,
138    /// Cb DC Huffman table.
139    pub cb_dc_table: JpegHuffmanTable,
140    /// Cb AC Huffman table.
141    pub cb_ac_table: JpegHuffmanTable,
142    /// Cr DC Huffman table.
143    pub cr_dc_table: JpegHuffmanTable,
144    /// Cr AC Huffman table.
145    pub cr_ac_table: JpegHuffmanTable,
146    /// Entropy-coded scan bytes.
147    pub entropy_bytes: Vec<u8>,
148}
149
150#[repr(C)]
151#[derive(Debug, Clone, PartialEq, Eq)]
152/// Backend fast-path packet for 8-bit 4:2:2 JPEG tiles.
153pub struct JpegFast422PacketV1 {
154    /// Image dimensions as `(width, height)` in pixels.
155    pub dimensions: (u32, u32),
156    /// Number of MCUs per row.
157    pub mcus_per_row: u32,
158    /// Number of MCU rows.
159    pub mcu_rows: u32,
160    /// Restart interval in MCUs, or zero when absent.
161    pub restart_interval_mcus: u32,
162    /// Byte offsets of restart-addressable entropy segments.
163    pub restart_offsets: Vec<u32>,
164    /// Entropy resume checkpoints.
165    pub entropy_checkpoints: Vec<JpegEntropyCheckpointV1>,
166    /// Y quantization table in natural order.
167    pub y_quant: [u16; 64],
168    /// Cb quantization table in natural order.
169    pub cb_quant: [u16; 64],
170    /// Cr quantization table in natural order.
171    pub cr_quant: [u16; 64],
172    /// Y DC Huffman table.
173    pub y_dc_table: JpegHuffmanTable,
174    /// Y AC Huffman table.
175    pub y_ac_table: JpegHuffmanTable,
176    /// Cb DC Huffman table.
177    pub cb_dc_table: JpegHuffmanTable,
178    /// Cb AC Huffman table.
179    pub cb_ac_table: JpegHuffmanTable,
180    /// Cr DC Huffman table.
181    pub cr_dc_table: JpegHuffmanTable,
182    /// Cr AC Huffman table.
183    pub cr_ac_table: JpegHuffmanTable,
184    /// Entropy-coded scan bytes.
185    pub entropy_bytes: Vec<u8>,
186}
187
188#[repr(C)]
189#[derive(Debug, Clone, PartialEq, Eq)]
190/// Backend fast-path packet for 8-bit 4:4:4 JPEG tiles.
191pub struct JpegFast444PacketV1 {
192    /// Image dimensions as `(width, height)` in pixels.
193    pub dimensions: (u32, u32),
194    /// Number of MCUs per row.
195    pub mcus_per_row: u32,
196    /// Number of MCU rows.
197    pub mcu_rows: u32,
198    /// Restart interval in MCUs, or zero when absent.
199    pub restart_interval_mcus: u32,
200    /// Byte offsets of restart-addressable entropy segments.
201    pub restart_offsets: Vec<u32>,
202    /// Entropy resume checkpoints.
203    pub entropy_checkpoints: Vec<JpegEntropyCheckpointV1>,
204    /// Y quantization table in natural order.
205    pub y_quant: [u16; 64],
206    /// Cb quantization table in natural order.
207    pub cb_quant: [u16; 64],
208    /// Cr quantization table in natural order.
209    pub cr_quant: [u16; 64],
210    /// Y DC Huffman table.
211    pub y_dc_table: JpegHuffmanTable,
212    /// Y AC Huffman table.
213    pub y_ac_table: JpegHuffmanTable,
214    /// Cb DC Huffman table.
215    pub cb_dc_table: JpegHuffmanTable,
216    /// Cb AC Huffman table.
217    pub cb_ac_table: JpegHuffmanTable,
218    /// Cr DC Huffman table.
219    pub cr_dc_table: JpegHuffmanTable,
220    /// Cr AC Huffman table.
221    pub cr_ac_table: JpegHuffmanTable,
222    /// Entropy-coded scan bytes.
223    pub entropy_bytes: Vec<u8>,
224}
225
226#[repr(C)]
227#[derive(Debug, Clone, PartialEq, Eq)]
228/// Backend fast-path packet for 8-bit grayscale JPEG tiles.
229pub struct JpegGrayPacketV1 {
230    /// Image dimensions as `(width, height)` in pixels.
231    pub dimensions: (u32, u32),
232    /// Number of MCUs per row.
233    pub mcus_per_row: u32,
234    /// Number of MCU rows.
235    pub mcu_rows: u32,
236    /// Restart interval in MCUs, or zero when absent.
237    pub restart_interval_mcus: u32,
238    /// Byte offsets of restart-addressable entropy segments.
239    pub restart_offsets: Vec<u32>,
240    /// Y quantization table in natural order.
241    pub y_quant: [u16; 64],
242    /// Y DC Huffman table.
243    pub y_dc_table: JpegHuffmanTable,
244    /// Y AC Huffman table.
245    pub y_ac_table: JpegHuffmanTable,
246    /// Entropy-coded scan bytes.
247    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
336/// Build a 4:2:0 fast-path packet from JPEG bytes.
337pub fn build_fast420_packet(bytes: &[u8]) -> Result<JpegFast420PacketV1, FastPacketError> {
338    build_color_fast_packet(bytes, FAST420_LAYOUT).map(Into::into)
339}
340
341/// Build a 4:4:4 fast-path packet from JPEG bytes.
342pub fn build_fast444_packet(bytes: &[u8]) -> Result<JpegFast444PacketV1, FastPacketError> {
343    build_color_fast_packet(bytes, FAST444_LAYOUT).map(Into::into)
344}
345
346/// Build a 4:2:2 fast-path packet from JPEG bytes.
347pub 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
423/// Build a grayscale fast-path packet from JPEG bytes.
424pub 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
482/// Build a 4:2:0 fast-path packet from an inspected decoder.
483pub fn build_fast420_packet_for_decoder(
484    decoder: &crate::decoder::Decoder<'_>,
485) -> Result<JpegFast420PacketV1, FastPacketError> {
486    build_fast420_packet(decoder.bytes)
487}
488
489/// Build a 4:4:4 fast-path packet from an inspected decoder.
490pub fn build_fast444_packet_for_decoder(
491    decoder: &crate::decoder::Decoder<'_>,
492) -> Result<JpegFast444PacketV1, FastPacketError> {
493    build_fast444_packet(decoder.bytes)
494}
495
496/// Build a 4:2:2 fast-path packet from an inspected decoder.
497pub fn build_fast422_packet_for_decoder(
498    decoder: &crate::decoder::Decoder<'_>,
499) -> Result<JpegFast422PacketV1, FastPacketError> {
500    build_fast422_packet(decoder.bytes)
501}
502
503/// Build a grayscale fast-path packet from an inspected decoder.
504pub 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}