Skip to main content

zune_jpeg/
headers.rs

1/*
2 * Copyright (c) 2023.
3 *
4 * This software is free software;
5 *
6 * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license
7 */
8
9//! Decode Decoder markers/segments
10//!
11//! This file deals with decoding header information in a jpeg file
12//!
13use alloc::format;
14use alloc::string::ToString;
15use alloc::vec::Vec;
16
17use zune_core::bytestream::ZByteReaderTrait;
18use zune_core::colorspace::ColorSpace;
19use zune_core::log::{debug, trace, warn};
20
21use core::cmp::max;
22
23use crate::components::{Components, SampleRatios};
24use crate::decoder::{ExtendedXmpSegment, GainMapInfo, ICCChunk, JpegDecoder, MAX_COMPONENTS};
25use crate::errors::DecodeErrors;
26use crate::huffman::HuffmanTable;
27use crate::misc::{SOFMarkers, UN_ZIGZAG};
28
29///**B.2.4.2 Huffman table-specification syntax**
30#[allow(clippy::similar_names, clippy::cast_sign_loss)]
31pub(crate) fn parse_huffman<T: ZByteReaderTrait>(
32    decoder: &mut JpegDecoder<T>
33) -> Result<(), DecodeErrors>
34where
35{
36    // Read the length of the Huffman table
37    let mut dht_length = i32::from(decoder.stream.get_u16_be_err()?.checked_sub(2).ok_or(
38        DecodeErrors::FormatStatic("Invalid Huffman length in image")
39    )?);
40
41    while dht_length > 16 {
42        // HT information
43        let ht_info = decoder.stream.read_u8_err()?;
44        // third bit indicates whether the huffman encoding is DC or AC type
45        let dc_or_ac = (ht_info >> 4) & 0xF;
46        // Indicate the position of this table, should be less than 4;
47        let index = (ht_info & 0xF) as usize;
48        // read the number of symbols
49        let mut num_symbols: [u8; 17] = [0; 17];
50
51        if index >= MAX_COMPONENTS {
52            return Err(DecodeErrors::HuffmanDecode(format!(
53                "Invalid DHT index {index}, expected between 0 and 3"
54            )));
55        }
56
57        if dc_or_ac > 1 {
58            return Err(DecodeErrors::HuffmanDecode(format!(
59                "Invalid DHT position {dc_or_ac}, should be 0 or 1"
60            )));
61        }
62
63        decoder.stream.read_exact_bytes(&mut num_symbols[1..17])?;
64
65        dht_length -= 1 + 16;
66
67        let symbols_sum: i32 = num_symbols.iter().map(|f| i32::from(*f)).sum();
68
69        // The sum of the number of symbols cannot be greater than 256;
70        if symbols_sum > 256 {
71            return Err(DecodeErrors::FormatStatic(
72                "Encountered Huffman table with excessive length in DHT"
73            ));
74        }
75        if symbols_sum > dht_length {
76            return Err(DecodeErrors::HuffmanDecode(format!(
77                "Excessive Huffman table of length {symbols_sum} found when header length is {dht_length}"
78            )));
79        }
80        dht_length -= symbols_sum;
81        // A table containing symbols in increasing code length
82        let mut symbols = [0; 256];
83
84        decoder
85            .stream
86            .read_exact_bytes(&mut symbols[0..(symbols_sum as usize)])?;
87        // store
88        match dc_or_ac {
89            0 => {
90                decoder.dc_huffman_tables[index] = Some(HuffmanTable::new(
91                    &num_symbols,
92                    symbols,
93                    true,
94                    decoder.is_progressive
95                )?);
96            }
97            _ => {
98                decoder.ac_huffman_tables[index] = Some(HuffmanTable::new(
99                    &num_symbols,
100                    symbols,
101                    false,
102                    decoder.is_progressive
103                )?);
104            }
105        }
106    }
107
108    if dht_length > 0 {
109        return Err(DecodeErrors::FormatStatic("Bogus Huffman table definition"));
110    }
111
112    Ok(())
113}
114
115///**B.2.4.1 Quantization table-specification syntax**
116#[allow(clippy::cast_possible_truncation, clippy::needless_range_loop)]
117pub(crate) fn parse_dqt<T: ZByteReaderTrait>(img: &mut JpegDecoder<T>) -> Result<(), DecodeErrors> {
118    // read length
119    let mut qt_length =
120        img.stream
121            .get_u16_be_err()?
122            .checked_sub(2)
123            .ok_or(DecodeErrors::FormatStatic(
124                "Invalid DQT length. Length should be greater than 2"
125            ))?;
126    // A single DQT header may have multiple QT's
127    while qt_length > 0 {
128        let qt_info = img.stream.read_u8_err()?;
129        // 0 = 8 bit otherwise 16 bit dqt
130        let precision = (qt_info >> 4) as usize;
131        // last 4 bits give us position
132        let table_position = (qt_info & 0x0f) as usize;
133        let precision_value = 64 * (precision + 1);
134
135        if (precision_value + 1) as u16 > qt_length {
136            return Err(DecodeErrors::DqtError(format!("Invalid QT table bytes left :{}. Too small to construct a valid qt table which should be {} long", qt_length, precision_value + 1)));
137        }
138
139        let dct_table = match precision {
140            0 => {
141                let mut qt_values = [0; 64];
142
143                img.stream.read_exact_bytes(&mut qt_values)?;
144
145                qt_length -= (precision_value as u16) + 1 /*QT BIT*/;
146                // carry out un zig-zag here
147                un_zig_zag(&qt_values)
148            }
149            1 => {
150                // 16 bit quantization tables
151                let mut qt_values = [0_u16; 64];
152
153                for i in 0..64 {
154                    qt_values[i] = img.stream.get_u16_be_err()?;
155                }
156                qt_length -= (precision_value as u16) + 1;
157
158                un_zig_zag(&qt_values)
159            }
160            _ => {
161                return Err(DecodeErrors::DqtError(format!(
162                    "Expected QT precision value of either 0 or 1, found {precision:?}"
163                )));
164            }
165        };
166
167        if table_position >= MAX_COMPONENTS {
168            return Err(DecodeErrors::DqtError(format!(
169                "Too large table position for QT :{table_position}, expected between 0 and 3"
170            )));
171        }
172
173        trace!("Assigning qt table {table_position} with precision {precision}");
174        img.qt_tables[table_position] = Some(dct_table);
175    }
176
177    return Ok(());
178}
179
180/// Section:`B.2.2 Frame header syntax`
181
182pub(crate) fn parse_start_of_frame<T: ZByteReaderTrait>(
183    sof: SOFMarkers, img: &mut JpegDecoder<T>
184) -> Result<(), DecodeErrors> {
185    if img.seen_sof {
186        return Err(DecodeErrors::SofError(
187            "Two Start of Frame Markers".to_string()
188        ));
189    }
190    // Get length of the frame header
191    let length = img.stream.get_u16_be_err()?;
192    // usually 8, but can be 12 and 16, we currently support only 8
193    // so sorry about that 12 bit images
194    let dt_precision = img.stream.read_u8_err()?;
195
196    if dt_precision != 8 {
197        return Err(DecodeErrors::SofError(format!(
198            "The library can only parse 8-bit images, the image has {dt_precision} bits of precision"
199        )));
200    }
201
202    img.info.set_density(dt_precision);
203
204    // read  and set the image height.
205    let img_height = img.stream.get_u16_be_err()?;
206    img.info.set_height(img_height);
207
208    // read and set the image width
209    let img_width = img.stream.get_u16_be_err()?;
210    img.info.set_width(img_width);
211
212    trace!("Image width  :{}", img_width);
213    trace!("Image height :{}", img_height);
214
215    if usize::from(img_width) > img.options.max_width() {
216        return Err(DecodeErrors::Format(format!("Image width {} greater than width limit {}. If use `set_limits` if you want to support huge images", img_width, img.options.max_width())));
217    }
218
219    if usize::from(img_height) > img.options.max_height() {
220        return Err(DecodeErrors::Format(format!("Image height {} greater than height limit {}. If use `set_limits` if you want to support huge images", img_height, img.options.max_height())));
221    }
222
223    // Check image width or height is zero
224    if img_width == 0 || img_height == 0 {
225        return Err(DecodeErrors::ZeroError);
226    }
227
228    // Number of components for the image.
229    let num_components = img.stream.read_u8_err()?;
230
231    if num_components == 0 {
232        return Err(DecodeErrors::SofError(
233            "Number of components cannot be zero.".to_string()
234        ));
235    }
236
237    let expected = 8 + 3 * u16::from(num_components);
238    // length should be equal to num components
239    if length != expected {
240        return Err(DecodeErrors::SofError(format!(
241            "Length of start of frame differs from expected {expected},value is {length}"
242        )));
243    }
244
245    trace!("Image components : {}", num_components);
246
247    if num_components == 1 {
248        // SOF sets the number of image components
249        // and that to us translates to setting input and output
250        // colorspaces to zero
251        img.input_colorspace = ColorSpace::Luma;
252        //img.options = img.options.jpeg_set_out_colorspace(ColorSpace::Luma);
253        debug!("Overriding default colorspace set to Luma");
254    }
255    if num_components == 4 && img.input_colorspace == ColorSpace::YCbCr {
256        trace!("Input image has 4 components, defaulting to CMYK colorspace");
257        // https://entropymine.wordpress.com/2018/10/22/how-is-a-jpeg-images-color-type-determined/
258        img.input_colorspace = ColorSpace::CMYK;
259    }
260
261    // set number of components
262    img.info.components = num_components;
263
264    let mut components = Vec::with_capacity(num_components as usize);
265    let mut temp = [0; 3];
266
267    for pos in 0..num_components {
268        // read 3 bytes for each component
269        img.stream.read_exact_bytes(&mut temp)?;
270
271        // create a component.
272        let component = Components::from(temp, pos)?;
273
274        components.push(component);
275    }
276    img.seen_sof = true;
277
278    img.info.set_sof_marker(sof);
279
280    img.components = components;
281
282    let mut h_max = 1;
283    let mut v_max = 1;
284
285    for comp in &img.components {
286        h_max = max(h_max, comp.horizontal_sample);
287        v_max = max(v_max, comp.vertical_sample);
288    }
289
290    img.info.sample_ratio = match (h_max, v_max) {
291        (1, 1) => SampleRatios::None,
292        (1, 2) => SampleRatios::V,
293        (2, 1) => SampleRatios::H,
294        (2, 2) => SampleRatios::HV,
295        (hs, vs) => SampleRatios::Generic(hs, vs)
296    };
297
298    Ok(())
299}
300
301/// Parse a start of scan data
302pub(crate) fn parse_sos<T: ZByteReaderTrait>(
303    image: &mut JpegDecoder<T>
304) -> Result<(), DecodeErrors> {
305    // Scan header length
306    let ls = usize::from(image.stream.get_u16_be_err()?);
307    // Number of image components in scan
308    let ns = image.stream.read_u8_err()?;
309
310    let mut seen: [_; 5] = [-1; { MAX_COMPONENTS + 1 }];
311
312    image.num_scans = ns;
313    let smallest_size = 6 + 2 * usize::from(ns);
314
315    if ls != smallest_size {
316        return Err(DecodeErrors::SosError(format!(
317            "Bad SOS length {ls},corrupt jpeg"
318        )));
319    }
320
321    // Check number of components.
322    if !(1..5).contains(&ns) {
323        return Err(DecodeErrors::SosError(format!(
324            "Invalid number of components in start of scan {ns}, expected in range 1..5"
325        )));
326    }
327
328    if image.info.components == 0 {
329        return Err(DecodeErrors::FormatStatic(
330            "Error decoding SOF Marker, Number of components cannot be zero."
331        ));
332    }
333
334    // consume spec parameters
335    image.scan_subsampled = false;
336
337    for i in 0..ns {
338        let id = image.stream.read_u8_err()?;
339
340        if seen.contains(&i32::from(id)) {
341            return Err(DecodeErrors::SofError(format!(
342                "Duplicate ID {id} seen twice in the same component"
343            )));
344        }
345
346        seen[usize::from(i)] = i32::from(id);
347        // DC and AC huffman table position
348        // top 4 bits contain dc huffman destination table
349        // lower four bits contain ac huffman destination table
350        let y = image.stream.read_u8_err()?;
351
352        let mut j = 0;
353
354        while j < image.info.components {
355            if image.components[j as usize].id == id {
356                break;
357            }
358
359            j += 1;
360        }
361
362        if j == image.info.components {
363            return Err(DecodeErrors::SofError(format!(
364                "Invalid component id {}, expected one one of {:?}",
365                id,
366                image.components.iter().map(|c| c.id).collect::<Vec<_>>()
367            )));
368        }
369
370        let component = &mut image.components[usize::from(j)];
371        component.dc_huff_table = usize::from((y >> 4) & 0xF);
372        component.ac_huff_table = usize::from(y & 0xF);
373        image.z_order[i as usize] = j as usize;
374
375        if component.vertical_sample != 1 || component.horizontal_sample != 1 {
376            image.scan_subsampled = true;
377        }
378
379        trace!(
380            "Assigned huffman tables {}/{} to component {j}, id={}",
381            image.components[usize::from(j)].dc_huff_table,
382            image.components[usize::from(j)].ac_huff_table,
383            image.components[usize::from(j)].id,
384        );
385    }
386
387    // Collect the component spec parameters
388    // This is only needed for progressive images but I'll read
389    // them in order to ensure they are correct according to the spec
390
391    // Extract progressive information
392
393    // https://www.w3.org/Graphics/JPEG/itu-t81.pdf
394    // Page 42
395
396    // Start of spectral / predictor selection. (between 0 and 63)
397    image.spec_start = image.stream.read_u8_err()?;
398    // End of spectral selection
399    image.spec_end = image.stream.read_u8_err()?;
400
401    let bit_approx = image.stream.read_u8_err()?;
402    // successive approximation bit position high
403    image.succ_high = bit_approx >> 4;
404
405    if image.spec_end > 63 {
406        return Err(DecodeErrors::SosError(format!(
407            "Invalid Se parameter {}, range should be 0-63",
408            image.spec_end
409        )));
410    }
411    if image.spec_start > 63 {
412        return Err(DecodeErrors::SosError(format!(
413            "Invalid Ss parameter {}, range should be 0-63",
414            image.spec_start
415        )));
416    }
417    if image.succ_high > 13 {
418        return Err(DecodeErrors::SosError(format!(
419            "Invalid Ah parameter {}, range should be 0-13",
420            image.succ_low
421        )));
422    }
423    // successive approximation bit position low
424    image.succ_low = bit_approx & 0xF;
425
426    if image.succ_low > 13 {
427        return Err(DecodeErrors::SosError(format!(
428            "Invalid Al parameter {}, range should be 0-13",
429            image.succ_low
430        )));
431    }
432    // skip any bytes not read
433    image.stream.skip(smallest_size.saturating_sub(ls))?;
434
435    trace!(
436        "Ss={}, Se={} Ah={} Al={}",
437        image.spec_start,
438        image.spec_end,
439        image.succ_high,
440        image.succ_low
441    );
442
443    Ok(())
444}
445
446/// Parse the APP13 (IPTC) segment.
447pub(crate) fn parse_app13<T: ZByteReaderTrait>(
448    decoder: &mut JpegDecoder<T>
449) -> Result<(), DecodeErrors> {
450    const IPTC_PREFIX: &[u8] = b"Photoshop 3.0\0";
451    // skip length.
452    let mut length = usize::from(decoder.stream.get_u16_be());
453
454    if length < 2 {
455        return Err(DecodeErrors::FormatStatic("Too small APP13 length"));
456    }
457    // length bytes.
458    length -= 2;
459
460    if length > IPTC_PREFIX.len() && decoder.stream.peek_at(0, IPTC_PREFIX.len())? == IPTC_PREFIX {
461        // skip bytes we read above.
462        decoder.stream.skip(IPTC_PREFIX.len())?;
463        length -= IPTC_PREFIX.len();
464
465        let iptc_bytes = decoder.stream.peek_at(0, length)?.to_vec();
466
467        decoder.info.iptc_data = Some(iptc_bytes);
468    }
469
470    decoder.stream.skip(length)?;
471    Ok(())
472}
473
474/// Parse Adobe App14 segment
475pub(crate) fn parse_app14<T: ZByteReaderTrait>(
476    decoder: &mut JpegDecoder<T>
477) -> Result<(), DecodeErrors> {
478    // skip length
479    let mut length = usize::from(decoder.stream.get_u16_be());
480
481    if length < 2 {
482        return Err(DecodeErrors::FormatStatic("Too small APP14 length"));
483    }
484
485    if decoder.stream.peek_at(0, 5)? == b"Adobe" {
486        if length < 14 {
487            return Err(DecodeErrors::FormatStatic(
488                "Too short of a length for App14 segment"
489            ));
490        }
491        // move stream 6 bytes to remove adobe id
492        decoder.stream.skip(6)?;
493        // skip version, flags0 and flags1
494        decoder.stream.skip(5)?;
495        // get color transform
496        let transform = decoder.stream.read_u8();
497        // https://exiftool.org/TagNames/JPEG.html#Adobe
498        match transform {
499            0 => decoder.input_colorspace = ColorSpace::CMYK,
500            1 => decoder.input_colorspace = ColorSpace::YCbCr,
501            2 => decoder.input_colorspace = ColorSpace::YCCK,
502            _ => {
503                return Err(DecodeErrors::Format(format!(
504                    "Unknown Adobe colorspace {transform}"
505                )))
506            }
507        }
508        // length   = 2
509        // adobe id = 6
510        // version =  5
511        // transform = 1
512        length = length.saturating_sub(14);
513    } else {
514        warn!("Not a valid Adobe APP14 Segment, skipping {} bytes", length);
515        length = length.saturating_sub(2);
516    }
517    // skip any proceeding lengths.
518    // we do not need them
519    decoder.stream.skip(length)?;
520
521    Ok(())
522}
523
524/// Parse the APP1 segment
525///
526/// This contains the exif tag
527pub(crate) fn parse_app1<T: ZByteReaderTrait>(
528    decoder: &mut JpegDecoder<T>
529) -> Result<(), DecodeErrors> {
530    const XMP_NAMESPACE_PREFIX: &[u8] = b"http://ns.adobe.com/xap/1.0/\0";
531    const EXTENDED_XMP_NAMESPACE_PREFIX: &[u8] = b"http://ns.adobe.com/xmp/extension/\0";
532    const EXTENDED_XMP_GUID_SIZE: usize = 32;
533    const EXTENDED_XMP_TOTAL_SIZE_SIZE: usize = 4;
534    const EXTENDED_XMP_OFFSET_SIZE: usize = 4;
535    const EXTENDED_XMP_HEADER_SIZE: usize =
536        EXTENDED_XMP_GUID_SIZE + EXTENDED_XMP_TOTAL_SIZE_SIZE + EXTENDED_XMP_OFFSET_SIZE;
537
538    // contains exif data
539    let mut length = usize::from(decoder.stream.get_u16_be());
540
541    if length < 2 {
542        return Err(DecodeErrors::FormatStatic("Too small app1 length"));
543    }
544    // length bytes
545    length -= 2;
546
547    if length > 6 && decoder.stream.peek_at(0, 6)? == b"Exif\x00\x00" {
548        trace!("Exif segment present");
549        // skip bytes we read above
550        decoder.stream.skip(6)?;
551        length -= 6;
552
553        let exif_bytes = decoder.stream.peek_at(0, length)?.to_vec();
554
555        decoder.info.exif_data = Some(exif_bytes);
556    } else if length > XMP_NAMESPACE_PREFIX.len()
557        && decoder.stream.peek_at(0, XMP_NAMESPACE_PREFIX.len())? == XMP_NAMESPACE_PREFIX
558    {
559        trace!("XMP Data Present");
560        decoder.stream.skip(XMP_NAMESPACE_PREFIX.len())?;
561        length -= XMP_NAMESPACE_PREFIX.len();
562        let xmp_data = decoder.stream.peek_at(0, length)?.to_vec();
563        decoder.info.xmp_data = Some(xmp_data);
564    } else if length > EXTENDED_XMP_NAMESPACE_PREFIX.len()
565        && decoder.stream.peek_at(0, EXTENDED_XMP_NAMESPACE_PREFIX.len())?
566            == EXTENDED_XMP_NAMESPACE_PREFIX
567    {
568        trace!("Extended XMP Data Present");
569        decoder.stream.skip(EXTENDED_XMP_NAMESPACE_PREFIX.len())?;
570        length -= EXTENDED_XMP_NAMESPACE_PREFIX.len();
571
572        if length < EXTENDED_XMP_HEADER_SIZE {
573            return Err(DecodeErrors::FormatStatic("Too small Extended XMP segment"));
574        }
575
576        let header = decoder.stream.peek_at(0, EXTENDED_XMP_HEADER_SIZE)?;
577        let guid = header[0..EXTENDED_XMP_GUID_SIZE].to_vec();
578
579        let total_size_start = EXTENDED_XMP_GUID_SIZE;
580        let total_size_end = total_size_start + EXTENDED_XMP_TOTAL_SIZE_SIZE;
581        let total_size =
582            u32::from_be_bytes(header[total_size_start..total_size_end].try_into().unwrap());
583
584        let offset_start = total_size_end;
585        let offset_end = offset_start + EXTENDED_XMP_OFFSET_SIZE;
586        let offset = u32::from_be_bytes(header[offset_start..offset_end].try_into().unwrap());
587
588        let data = decoder
589            .stream
590            .peek_at(EXTENDED_XMP_HEADER_SIZE, length - EXTENDED_XMP_HEADER_SIZE)?
591            .to_vec();
592
593        decoder.extended_xmp_segments.push(ExtendedXmpSegment { offset, total_size, guid, data });
594    } else {
595        warn!("Unknown format for APP1 tag, skipping");
596    }
597
598    decoder.stream.skip(length)?;
599    Ok(())
600}
601
602pub(crate) fn parse_app2<T: ZByteReaderTrait>(
603    decoder: &mut JpegDecoder<T>
604) -> Result<(), DecodeErrors> {
605    static HDR_META: &[u8] = b"urn:iso:std:iso:ts:21496:-1\0";
606    static MPF_DATA: &[u8] = b"MPF\0";
607
608    let mut length = usize::from(decoder.stream.get_u16_be());
609
610    if length < 2 {
611        return Err(DecodeErrors::FormatStatic("Too small app2 segment"));
612    }
613    // length bytes
614    length -= 2;
615
616    if length > 14 && decoder.stream.peek_at(0, 12)? == *b"ICC_PROFILE\0" {
617        trace!("ICC Profile present");
618        // skip 12 bytes which indicate ICC profile
619        length -= 12;
620        decoder.stream.skip(12)?;
621        let seq_no = decoder.stream.read_u8();
622        let num_markers = decoder.stream.read_u8();
623        // deduct the two bytes we read above
624        length -= 2;
625
626        let data = decoder.stream.peek_at(0, length)?.to_vec();
627
628        let icc_chunk = ICCChunk {
629            seq_no,
630            num_markers,
631            data
632        };
633        decoder.icc_data.push(icc_chunk);
634    } else if length > HDR_META.len() && decoder.stream.peek_at(0, HDR_META.len())? == HDR_META {
635        length = length.saturating_sub(HDR_META.len());
636        decoder.stream.skip(HDR_META.len())?;
637        trace!("Gain Map metadata found");
638        match length {
639            4 => {
640                // If gain map metadata length == 4 then here it variables
641                // https://github.com/google/libultrahdr/blob/bf2aa439eea9ad5da483003fa44182f990f74091/lib/src/jpegr.cpp#L1076C1-L1077C35
642                // 2 bytes minimum_version: (00 00)
643                // 2 bytes writer_version: (00 00)
644                // Perhaps nothing to do with it ?
645                let _ = decoder.stream.get_u16_be();
646                let _ = decoder.stream.get_u16_be();
647                length -= 4;
648                decoder
649                    .info
650                    .gain_map_info
651                    .push(GainMapInfo { data: Vec::new() });
652            }
653            n if n > 4 => {
654                // If there is perhaps useful gain map info
655                // we'll read this until end
656                // https://github.com/google/libultrahdr/blob/bf2aa439eea9ad5da483003fa44182f990f74091/lib/src/jpegr.cpp#L1323
657                let data = decoder.stream.peek_at(0, length)?.to_vec();
658                length -= data.len();
659                decoder.stream.skip(data.len())?;
660
661                decoder.info.gain_map_info.push(GainMapInfo { data });
662            }
663            _ => {}
664        }
665    } else if length > MPF_DATA.len() && decoder.stream.peek_at(0, MPF_DATA.len())? == MPF_DATA {
666        trace!("MPF Signature present");
667        length = length.saturating_sub(MPF_DATA.len());
668        decoder.stream.skip(MPF_DATA.len())?;
669        decoder.info.multi_picture_information_offset = Some(decoder.stream.position()?);
670        // MPF signature taken from here
671        // https://github.com/google/libultrahdr/blob/bf2aa439eea9ad5da483003fa44182f990f74091/lib/include/ultrahdr/multipictureformat.h#L50
672        // https://github.com/google/libultrahdr/blob/bf2aa439eea9ad5da483003fa44182f990f74091/lib/src/multipictureformat.cpp#L36
673        // More info https://www.cipa.jp/std/documents/e/DC-X007-KEY_E.pdf
674        let data = decoder.stream.peek_at(0, length)?.to_vec();
675        length -= data.len();
676        decoder.stream.skip(data.len())?;
677        decoder.info.multi_picture_information = Some(data);
678    }
679
680    decoder.stream.skip(length)?;
681
682    Ok(())
683}
684
685/// Small utility function to print Un-zig-zagged quantization tables
686
687fn un_zig_zag<T>(a: &[T]) -> [i32; 64]
688where
689    T: Default + Copy,
690    i32: core::convert::From<T>
691{
692    let mut output = [i32::default(); 64];
693
694    for i in 0..64 {
695        output[UN_ZIGZAG[i]] = i32::from(a[i]);
696    }
697
698    output
699}