/* * Copyright (c) 2023. * * This software is free software; * * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license */ //! Decode Decoder markers/segments //! //! This file deals with decoding header information in a jpeg file //! use alloc::format; use alloc::string::ToString; use alloc::vec::Vec; use zune_core::bytestream::ZByteReaderTrait; use zune_core::colorspace::ColorSpace; use zune_core::log::{debug, trace, warn}; use core::cmp::max; use crate::components::{Components, SampleRatios}; use crate::decoder::{GainMapInfo, ICCChunk, JpegDecoder, MAX_COMPONENTS}; use crate::errors::DecodeErrors; use crate::huffman::HuffmanTable; use crate::misc::{SOFMarkers, UN_ZIGZAG}; ///**B.2.4.2 Huffman table-specification syntax** #[allow(clippy::similar_names, clippy::cast_sign_loss)] pub(crate) fn parse_huffman( decoder: &mut JpegDecoder ) -> Result<(), DecodeErrors> where { // Read the length of the Huffman table let mut dht_length = i32::from(decoder.stream.get_u16_be_err()?.checked_sub(2).ok_or( DecodeErrors::FormatStatic("Invalid Huffman length in image") )?); while dht_length > 16 { // HT information let ht_info = decoder.stream.read_u8_err()?; // third bit indicates whether the huffman encoding is DC or AC type let dc_or_ac = (ht_info >> 4) & 0xF; // Indicate the position of this table, should be less than 4; let index = (ht_info & 0xF) as usize; // read the number of symbols let mut num_symbols: [u8; 17] = [0; 17]; if index >= MAX_COMPONENTS { return Err(DecodeErrors::HuffmanDecode(format!( "Invalid DHT index {index}, expected between 0 and 3" ))); } if dc_or_ac > 1 { return Err(DecodeErrors::HuffmanDecode(format!( "Invalid DHT position {dc_or_ac}, should be 0 or 1" ))); } decoder.stream.read_exact_bytes(&mut num_symbols[1..17])?; dht_length -= 1 + 16; let symbols_sum: i32 = num_symbols.iter().map(|f| i32::from(*f)).sum(); // The sum of the number of symbols cannot be greater than 256; if symbols_sum > 256 { return Err(DecodeErrors::FormatStatic( "Encountered Huffman table with excessive length in DHT" )); } if symbols_sum > dht_length { return Err(DecodeErrors::HuffmanDecode(format!( "Excessive Huffman table of length {symbols_sum} found when header length is {dht_length}" ))); } dht_length -= symbols_sum; // A table containing symbols in increasing code length let mut symbols = [0; 256]; decoder .stream .read_exact_bytes(&mut symbols[0..(symbols_sum as usize)])?; // store match dc_or_ac { 0 => { decoder.dc_huffman_tables[index] = Some(HuffmanTable::new( &num_symbols, symbols, true, decoder.is_progressive )?); } _ => { decoder.ac_huffman_tables[index] = Some(HuffmanTable::new( &num_symbols, symbols, false, decoder.is_progressive )?); } } } if dht_length > 0 { return Err(DecodeErrors::FormatStatic("Bogus Huffman table definition")); } Ok(()) } ///**B.2.4.1 Quantization table-specification syntax** #[allow(clippy::cast_possible_truncation, clippy::needless_range_loop)] pub(crate) fn parse_dqt(img: &mut JpegDecoder) -> Result<(), DecodeErrors> { // read length let mut qt_length = img.stream .get_u16_be_err()? .checked_sub(2) .ok_or(DecodeErrors::FormatStatic( "Invalid DQT length. Length should be greater than 2" ))?; // A single DQT header may have multiple QT's while qt_length > 0 { let qt_info = img.stream.read_u8_err()?; // 0 = 8 bit otherwise 16 bit dqt let precision = (qt_info >> 4) as usize; // last 4 bits give us position let table_position = (qt_info & 0x0f) as usize; let precision_value = 64 * (precision + 1); if (precision_value + 1) as u16 > qt_length { 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))); } let dct_table = match precision { 0 => { let mut qt_values = [0; 64]; img.stream.read_exact_bytes(&mut qt_values)?; qt_length -= (precision_value as u16) + 1 /*QT BIT*/; // carry out un zig-zag here un_zig_zag(&qt_values) } 1 => { // 16 bit quantization tables let mut qt_values = [0_u16; 64]; for i in 0..64 { qt_values[i] = img.stream.get_u16_be_err()?; } qt_length -= (precision_value as u16) + 1; un_zig_zag(&qt_values) } _ => { return Err(DecodeErrors::DqtError(format!( "Expected QT precision value of either 0 or 1, found {precision:?}" ))); } }; if table_position >= MAX_COMPONENTS { return Err(DecodeErrors::DqtError(format!( "Too large table position for QT :{table_position}, expected between 0 and 3" ))); } trace!("Assigning qt table {table_position} with precision {precision}"); img.qt_tables[table_position] = Some(dct_table); } return Ok(()); } /// Section:`B.2.2 Frame header syntax` pub(crate) fn parse_start_of_frame( sof: SOFMarkers, img: &mut JpegDecoder ) -> Result<(), DecodeErrors> { if img.seen_sof { return Err(DecodeErrors::SofError( "Two Start of Frame Markers".to_string() )); } // Get length of the frame header let length = img.stream.get_u16_be_err()?; // usually 8, but can be 12 and 16, we currently support only 8 // so sorry about that 12 bit images let dt_precision = img.stream.read_u8_err()?; if dt_precision != 8 { return Err(DecodeErrors::SofError(format!( "The library can only parse 8-bit images, the image has {dt_precision} bits of precision" ))); } img.info.set_density(dt_precision); // read and set the image height. let img_height = img.stream.get_u16_be_err()?; img.info.set_height(img_height); // read and set the image width let img_width = img.stream.get_u16_be_err()?; img.info.set_width(img_width); trace!("Image width :{}", img_width); trace!("Image height :{}", img_height); if usize::from(img_width) > img.options.max_width() { 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()))); } if usize::from(img_height) > img.options.max_height() { 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()))); } // Check image width or height is zero if img_width == 0 || img_height == 0 { return Err(DecodeErrors::ZeroError); } // Number of components for the image. let num_components = img.stream.read_u8_err()?; if num_components == 0 { return Err(DecodeErrors::SofError( "Number of components cannot be zero.".to_string() )); } let expected = 8 + 3 * u16::from(num_components); // length should be equal to num components if length != expected { return Err(DecodeErrors::SofError(format!( "Length of start of frame differs from expected {expected},value is {length}" ))); } trace!("Image components : {}", num_components); if num_components == 1 { // SOF sets the number of image components // and that to us translates to setting input and output // colorspaces to zero img.input_colorspace = ColorSpace::Luma; //img.options = img.options.jpeg_set_out_colorspace(ColorSpace::Luma); debug!("Overriding default colorspace set to Luma"); } if num_components == 4 && img.input_colorspace == ColorSpace::YCbCr { trace!("Input image has 4 components, defaulting to CMYK colorspace"); // https://entropymine.wordpress.com/2018/10/22/how-is-a-jpeg-images-color-type-determined/ img.input_colorspace = ColorSpace::CMYK; } // set number of components img.info.components = num_components; let mut components = Vec::with_capacity(num_components as usize); let mut temp = [0; 3]; for pos in 0..num_components { // read 3 bytes for each component img.stream.read_exact_bytes(&mut temp)?; // create a component. let component = Components::from(temp, pos)?; components.push(component); } img.seen_sof = true; img.info.set_sof_marker(sof); img.components = components; let mut h_max = 1; let mut v_max = 1; for comp in &img.components { h_max = max(h_max, comp.horizontal_sample); v_max = max(v_max, comp.vertical_sample); } img.info.sample_ratio = match (h_max, v_max) { (1, 1) => SampleRatios::None, (1, 2) => SampleRatios::V, (2, 1) => SampleRatios::H, (2, 2) => SampleRatios::HV, (hs, vs) => SampleRatios::Generic(hs, vs) }; Ok(()) } /// Parse a start of scan data pub(crate) fn parse_sos( image: &mut JpegDecoder ) -> Result<(), DecodeErrors> { // Scan header length let ls = usize::from(image.stream.get_u16_be_err()?); // Number of image components in scan let ns = image.stream.read_u8_err()?; let mut seen: [_; 5] = [-1; { MAX_COMPONENTS + 1 }]; image.num_scans = ns; let smallest_size = 6 + 2 * usize::from(ns); if ls != smallest_size { return Err(DecodeErrors::SosError(format!( "Bad SOS length {ls},corrupt jpeg" ))); } // Check number of components. if !(1..5).contains(&ns) { return Err(DecodeErrors::SosError(format!( "Invalid number of components in start of scan {ns}, expected in range 1..5" ))); } if image.info.components == 0 { return Err(DecodeErrors::FormatStatic( "Error decoding SOF Marker, Number of components cannot be zero." )); } // consume spec parameters image.scan_subsampled = false; for i in 0..ns { let id = image.stream.read_u8_err()?; if seen.contains(&i32::from(id)) { return Err(DecodeErrors::SofError(format!( "Duplicate ID {id} seen twice in the same component" ))); } seen[usize::from(i)] = i32::from(id); // DC and AC huffman table position // top 4 bits contain dc huffman destination table // lower four bits contain ac huffman destination table let y = image.stream.read_u8_err()?; let mut j = 0; while j < image.info.components { if image.components[j as usize].id == id { break; } j += 1; } if j == image.info.components { return Err(DecodeErrors::SofError(format!( "Invalid component id {}, expected one one of {:?}", id, image.components.iter().map(|c| c.id).collect::>() ))); } let component = &mut image.components[usize::from(j)]; component.dc_huff_table = usize::from((y >> 4) & 0xF); component.ac_huff_table = usize::from(y & 0xF); image.z_order[i as usize] = j as usize; if component.vertical_sample != 1 || component.horizontal_sample != 1 { image.scan_subsampled = true; } trace!( "Assigned huffman tables {}/{} to component {j}, id={}", image.components[usize::from(j)].dc_huff_table, image.components[usize::from(j)].ac_huff_table, image.components[usize::from(j)].id, ); } // Collect the component spec parameters // This is only needed for progressive images but I'll read // them in order to ensure they are correct according to the spec // Extract progressive information // https://www.w3.org/Graphics/JPEG/itu-t81.pdf // Page 42 // Start of spectral / predictor selection. (between 0 and 63) image.spec_start = image.stream.read_u8_err()?; // End of spectral selection image.spec_end = image.stream.read_u8_err()?; let bit_approx = image.stream.read_u8_err()?; // successive approximation bit position high image.succ_high = bit_approx >> 4; if image.spec_end > 63 { return Err(DecodeErrors::SosError(format!( "Invalid Se parameter {}, range should be 0-63", image.spec_end ))); } if image.spec_start > 63 { return Err(DecodeErrors::SosError(format!( "Invalid Ss parameter {}, range should be 0-63", image.spec_start ))); } if image.succ_high > 13 { return Err(DecodeErrors::SosError(format!( "Invalid Ah parameter {}, range should be 0-13", image.succ_low ))); } // successive approximation bit position low image.succ_low = bit_approx & 0xF; if image.succ_low > 13 { return Err(DecodeErrors::SosError(format!( "Invalid Al parameter {}, range should be 0-13", image.succ_low ))); } // skip any bytes not read image.stream.skip(smallest_size.saturating_sub(ls))?; trace!( "Ss={}, Se={} Ah={} Al={}", image.spec_start, image.spec_end, image.succ_high, image.succ_low ); Ok(()) } /// Parse the APP13 (IPTC) segment. pub(crate) fn parse_app13( decoder: &mut JpegDecoder ) -> Result<(), DecodeErrors> { const IPTC_PREFIX: &[u8] = b"Photoshop 3.0\0"; // skip length. let mut length = usize::from(decoder.stream.get_u16_be()); if length < 2 { return Err(DecodeErrors::FormatStatic("Too small APP13 length")); } // length bytes. length -= 2; if length > IPTC_PREFIX.len() && decoder.stream.peek_at(0, IPTC_PREFIX.len())? == IPTC_PREFIX { // skip bytes we read above. decoder.stream.skip(IPTC_PREFIX.len())?; length -= IPTC_PREFIX.len(); let iptc_bytes = decoder.stream.peek_at(0, length)?.to_vec(); decoder.info.iptc_data = Some(iptc_bytes); } decoder.stream.skip(length)?; Ok(()) } /// Parse Adobe App14 segment pub(crate) fn parse_app14( decoder: &mut JpegDecoder ) -> Result<(), DecodeErrors> { // skip length let mut length = usize::from(decoder.stream.get_u16_be()); if length < 2 { return Err(DecodeErrors::FormatStatic("Too small APP14 length")); } if decoder.stream.peek_at(0, 5)? == b"Adobe" { if length < 14 { return Err(DecodeErrors::FormatStatic( "Too short of a length for App14 segment" )); } // move stream 6 bytes to remove adobe id decoder.stream.skip(6)?; // skip version, flags0 and flags1 decoder.stream.skip(5)?; // get color transform let transform = decoder.stream.read_u8(); // https://exiftool.org/TagNames/JPEG.html#Adobe match transform { 0 => decoder.input_colorspace = ColorSpace::CMYK, 1 => decoder.input_colorspace = ColorSpace::YCbCr, 2 => decoder.input_colorspace = ColorSpace::YCCK, _ => { return Err(DecodeErrors::Format(format!( "Unknown Adobe colorspace {transform}" ))) } } // length = 2 // adobe id = 6 // version = 5 // transform = 1 length = length.saturating_sub(14); } else { warn!("Not a valid Adobe APP14 Segment, skipping {} bytes", length); length = length.saturating_sub(2); } // skip any proceeding lengths. // we do not need them decoder.stream.skip(length)?; Ok(()) } /// Parse the APP1 segment /// /// This contains the exif tag pub(crate) fn parse_app1( decoder: &mut JpegDecoder ) -> Result<(), DecodeErrors> { const XMP_NAMESPACE_PREFIX: &[u8] = b"http://ns.adobe.com/xap/1.0/\0"; // contains exif data let mut length = usize::from(decoder.stream.get_u16_be()); if length < 2 { return Err(DecodeErrors::FormatStatic("Too small app1 length")); } // length bytes length -= 2; if length > 6 && decoder.stream.peek_at(0, 6)? == b"Exif\x00\x00" { trace!("Exif segment present"); // skip bytes we read above decoder.stream.skip(6)?; length -= 6; let exif_bytes = decoder.stream.peek_at(0, length)?.to_vec(); decoder.info.exif_data = Some(exif_bytes); } else if length > XMP_NAMESPACE_PREFIX.len() && decoder.stream.peek_at(0, XMP_NAMESPACE_PREFIX.len())? == XMP_NAMESPACE_PREFIX { trace!("XMP Data Present"); decoder.stream.skip(XMP_NAMESPACE_PREFIX.len())?; length -= XMP_NAMESPACE_PREFIX.len(); let xmp_data = decoder.stream.peek_at(0, length)?.to_vec(); decoder.info.xmp_data = Some(xmp_data); } else { warn!("Unknown format for APP1 tag, skipping"); } decoder.stream.skip(length)?; Ok(()) } pub(crate) fn parse_app2( decoder: &mut JpegDecoder ) -> Result<(), DecodeErrors> { static HDR_META: &[u8] = b"urn:iso:std:iso:ts:21496:-1\0"; static MPF_DATA: &[u8] = b"MPF\0"; let mut length = usize::from(decoder.stream.get_u16_be()); if length < 2 { return Err(DecodeErrors::FormatStatic("Too small app2 segment")); } // length bytes length -= 2; if length > 14 && decoder.stream.peek_at(0, 12)? == *b"ICC_PROFILE\0" { trace!("ICC Profile present"); // skip 12 bytes which indicate ICC profile length -= 12; decoder.stream.skip(12)?; let seq_no = decoder.stream.read_u8(); let num_markers = decoder.stream.read_u8(); // deduct the two bytes we read above length -= 2; let data = decoder.stream.peek_at(0, length)?.to_vec(); let icc_chunk = ICCChunk { seq_no, num_markers, data }; decoder.icc_data.push(icc_chunk); } else if length > HDR_META.len() && decoder.stream.peek_at(0, HDR_META.len())? == HDR_META { length = length.saturating_sub(HDR_META.len()); decoder.stream.skip(HDR_META.len())?; trace!("Gain Map metadata found"); match length { 4 => { // If gain map metadata length == 4 then here it variables // https://github.com/google/libultrahdr/blob/bf2aa439eea9ad5da483003fa44182f990f74091/lib/src/jpegr.cpp#L1076C1-L1077C35 // 2 bytes minimum_version: (00 00) // 2 bytes writer_version: (00 00) // Perhaps nothing to do with it ? let _ = decoder.stream.get_u16_be(); let _ = decoder.stream.get_u16_be(); length -= 4; decoder .info .gain_map_info .push(GainMapInfo { data: Vec::new() }); } n if n > 4 => { // If there is perhaps useful gain map info // we'll read this until end // https://github.com/google/libultrahdr/blob/bf2aa439eea9ad5da483003fa44182f990f74091/lib/src/jpegr.cpp#L1323 let data = decoder.stream.peek_at(0, length)?.to_vec(); length -= data.len(); decoder.stream.skip(data.len())?; decoder.info.gain_map_info.push(GainMapInfo { data }); } _ => {} } } else if length > MPF_DATA.len() && decoder.stream.peek_at(0, MPF_DATA.len())? == MPF_DATA { trace!("MPF Signature present"); length = length.saturating_sub(MPF_DATA.len()); decoder.stream.skip(MPF_DATA.len())?; // MPF signature taken from here // https://github.com/google/libultrahdr/blob/bf2aa439eea9ad5da483003fa44182f990f74091/lib/include/ultrahdr/multipictureformat.h#L50 // https://github.com/google/libultrahdr/blob/bf2aa439eea9ad5da483003fa44182f990f74091/lib/src/multipictureformat.cpp#L36 // More info https://www.cipa.jp/std/documents/e/DC-X007-KEY_E.pdf let data = decoder.stream.peek_at(0, length)?.to_vec(); length -= data.len(); decoder.stream.skip(data.len())?; decoder.info.multi_picture_information = Some(data); } decoder.stream.skip(length)?; Ok(()) } /// Small utility function to print Un-zig-zagged quantization tables fn un_zig_zag(a: &[T]) -> [i32; 64] where T: Default + Copy, i32: core::convert::From { let mut output = [i32::default(); 64]; for i in 0..64 { output[UN_ZIGZAG[i]] = i32::from(a[i]); } output }