diff --git a/src/decoder.rs b/src/decoder.rs index 795ad1e4..852dbd33 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -4,7 +4,7 @@ use crate::marker::Marker; use crate::parser::{ parse_app, parse_com, parse_dht, parse_dqt, parse_dri, parse_sof, parse_sos, AdobeColorTransform, AppData, CodingProcess, Component, Dimensions, EntropyCoding, FrameInfo, - IccChunk, ScanInfo, + IccChunk, ScanInfo, AppSegment, }; use crate::read_u8; use crate::upsampler::Upsampler; @@ -118,6 +118,7 @@ pub struct Decoder { icc_markers: Vec, exif_data: Option>, + app_segments: Vec, // Used for progressive JPEGs. coefficients: Vec>, @@ -144,6 +145,7 @@ impl Decoder { is_mjpeg: false, icc_markers: Vec::new(), exif_data: None, + app_segments: Vec::new(), coefficients: Vec::new(), coefficients_finished: [0; MAX_COMPONENTS], decoding_buffer_size_limit: usize::MAX, @@ -197,6 +199,11 @@ impl Decoder { self.exif_data.as_deref() } + /// Returns collection of all captured app segments + pub fn app_segments(&self) -> &[AppSegment] { + self.app_segments.as_slice() + } + /// Returns the embeded icc profile if the image contains one. pub fn icc_profile(&self) -> Option> { let mut marker_present: [Option<&IccChunk>; 256] = [None; 256]; @@ -520,7 +527,10 @@ impl Decoder { } // Application data Marker::APP(..) => { - if let Some(data) = parse_app(&mut self.reader, marker)? { + let (app_segment, app_data) = parse_app(&mut self.reader, marker)?; + self.app_segments.push(app_segment); + + if let Some(data) = app_data { match data { AppData::Adobe(color_transform) => { self.adobe_color_transform = Some(color_transform) diff --git a/src/parser.rs b/src/parser.rs index 72ba00dc..53c1fd74 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,7 +2,7 @@ use alloc::borrow::ToOwned; use alloc::{format, vec}; use alloc::vec::Vec; use core::ops::{self, Range}; -use std::io::{self, Read}; +use std::io::Read; use crate::{read_u16_from_be, read_u8}; use crate::error::{Error, Result, UnsupportedFeature}; use crate::huffman::{HuffmanTable, HuffmanTableClass}; @@ -88,6 +88,12 @@ pub struct Component { pub block_size: Dimensions, } +#[derive(Debug)] +pub struct AppSegment { + pub kind: u8, + pub data: Vec, +} + #[derive(Debug)] pub enum AppData { Adobe(AdobeColorTransform), @@ -143,17 +149,6 @@ fn read_length(reader: &mut R, marker: Marker) -> Result { Ok(length - 2) } -fn skip_bytes(reader: &mut R, length: usize) -> Result<()> { - let length = length as u64; - let to_skip = &mut reader.by_ref().take(length); - let copied = io::copy(to_skip, &mut io::sink())?; - if copied < length { - Err(Error::Io(io::ErrorKind::UnexpectedEof.into())) - } else { - Ok(()) - } -} - // Section B.2.2 pub fn parse_sof(reader: &mut R, marker: Marker) -> Result { let length = read_length(reader, marker)?; @@ -600,86 +595,66 @@ pub fn parse_com(reader: &mut R) -> Result> { } // Section B.2.4.6 -pub fn parse_app(reader: &mut R, marker: Marker) -> Result> { +pub fn parse_app(reader: &mut R, marker: Marker) -> Result<(AppSegment, Option)> { let length = read_length(reader, marker)?; - let mut bytes_read = 0; + let mut data = vec![0u8; length]; + + reader.read_exact(&mut data)?; + let mut result = None; match marker { APP(0) => { - if length >= 5 { - let mut buffer = [0u8; 5]; - reader.read_exact(&mut buffer)?; - bytes_read = buffer.len(); - - // http://www.w3.org/Graphics/JPEG/jfif3.pdf - if buffer[0..5] == *b"JFIF\0" { - result = Some(AppData::Jfif); - // https://sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#AVI1 - } else if buffer[0..5] == *b"AVI1\0" { - result = Some(AppData::Avi1); - } + // http://www.w3.org/Graphics/JPEG/jfif3.pdf + if length >= 5 && data[0..5] == *b"JFIF\0" { + result = Some(AppData::Jfif); + // https://sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#AVI1 + } else if length >= 5 && data[0..5] == *b"AVI1\0" { + result = Some(AppData::Avi1); } } // Exif Data APP(1) => { - if length >= 6 { - let mut buffer = [0u8; 6]; - reader.read_exact(&mut buffer)?; - bytes_read = buffer.len(); - - // https://web.archive.org/web/20190624045241if_/http://www.cipa.jp:80/std/documents/e/DC-008-Translation-2019-E.pdf - // 4.5.4 Basic Structure of JPEG Compressed Data - if buffer == *b"Exif\x00\x00" { - let mut data = vec![0; length - bytes_read]; - reader.read_exact(&mut data)?; - bytes_read += data.len(); - result = Some(AppData::Exif(data)); - } + // https://web.archive.org/web/20190624045241if_/http://www.cipa.jp:80/std/documents/e/DC-008-Translation-2019-E.pdf + // 4.5.4 Basic Structure of JPEG Compressed Data + if length >= 6 && &data[0..6] == *b"Exif\x00\x00" { + result = Some(AppData::Exif(data[6..].to_vec())); } } APP(2) => { - if length > 14 { - let mut buffer = [0u8; 14]; - reader.read_exact(&mut buffer)?; - bytes_read = buffer.len(); - - // http://www.color.org/ICC_Minor_Revision_for_Web.pdf - // B.4 Embedding ICC profiles in JFIF files - if buffer[0..12] == *b"ICC_PROFILE\0" { - let mut data = vec![0; length - bytes_read]; - reader.read_exact(&mut data)?; - bytes_read += data.len(); - result = Some(AppData::Icc(IccChunk { - seq_no: buffer[12], - num_markers: buffer[13], - data, - })); - } + // http://www.color.org/ICC_Minor_Revision_for_Web.pdf + // B.4 Embedding ICC profiles in JFIF files + if length >= 12 && &data[0..12] == *b"ICC_PROFILE\0" { + result = Some(AppData::Icc(IccChunk { + seq_no: data[12], + num_markers: data[13], + data: data[14..].to_vec(), + })); } } APP(14) => { - if length >= 12 { - let mut buffer = [0u8; 12]; - reader.read_exact(&mut buffer)?; - bytes_read = buffer.len(); - + if length >= 12 && &data[0..6] == *b"Adobe\0" { // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe - if buffer[0 .. 6] == *b"Adobe\0" { - let color_transform = match buffer[11] { - 0 => AdobeColorTransform::Unknown, - 1 => AdobeColorTransform::YCbCr, - 2 => AdobeColorTransform::YCCK, - _ => return Err(Error::Format("invalid color transform in adobe app segment".to_owned())), - }; - - result = Some(AppData::Adobe(color_transform)); - } + let color_transform = match &data[11] { + 0 => AdobeColorTransform::Unknown, + 1 => AdobeColorTransform::YCbCr, + 2 => AdobeColorTransform::YCCK, + _ => return Err(Error::Format("invalid color transform in adobe app segment".to_owned())), + }; + + result = Some(AppData::Adobe(color_transform)); } }, _ => {}, } - skip_bytes(reader, length - bytes_read)?; - Ok(result) + let app_segment = AppSegment { + kind: match marker { + APP(kind) => kind, + _ => return Err(Error::Format("non APP marker".to_owned())) + }, + data + }; + + Ok((app_segment, result)) } diff --git a/tests/lib.rs b/tests/lib.rs index 3ee186be..53737fe7 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -154,3 +154,24 @@ fn read_exif_data() { // exif data start as a TIFF header assert_eq!(&exif_data[0..8], b"\x49\x49\x2A\x00\x08\x00\x00\x00"); } + +#[test] +fn read_app_segments() { + let path = Path::new("tests") + .join("reftest") + .join("images") + .join("ycck.jpg"); + + let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap()); + decoder.decode().unwrap(); + + let segments = decoder.app_segments(); + + // search for xmp data + assert!(segments + .into_iter() + .find(|segment| { segment.kind == 1 && segment.data.starts_with(b"http://ns.adobe.com/xap/1.0/") }) + .is_some() + ); + assert_eq!(segments.len(), 22); +}