Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unknown app1 #269

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -118,6 +118,7 @@ pub struct Decoder<R> {
icc_markers: Vec<IccChunk>,

exif_data: Option<Vec<u8>>,
app_segments: Vec<AppSegment>,

// Used for progressive JPEGs.
coefficients: Vec<Vec<i16>>,
Expand All @@ -144,6 +145,7 @@ impl<R: Read> Decoder<R> {
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,
Expand Down Expand Up @@ -197,6 +199,11 @@ impl<R: Read> Decoder<R> {
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<Vec<u8>> {
let mut marker_present: [Option<&IccChunk>; 256] = [None; 256];
Expand Down Expand Up @@ -520,7 +527,10 @@ impl<R: Read> Decoder<R> {
}
// 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)
Expand Down
121 changes: 48 additions & 73 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -88,6 +88,12 @@ pub struct Component {
pub block_size: Dimensions,
}

#[derive(Debug)]
pub struct AppSegment {
pub kind: u8,
pub data: Vec<u8>,
}

#[derive(Debug)]
pub enum AppData {
Adobe(AdobeColorTransform),
Expand Down Expand Up @@ -143,17 +149,6 @@ fn read_length<R: Read>(reader: &mut R, marker: Marker) -> Result<usize> {
Ok(length - 2)
}

fn skip_bytes<R: Read>(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<R: Read>(reader: &mut R, marker: Marker) -> Result<FrameInfo> {
let length = read_length(reader, marker)?;
Expand Down Expand Up @@ -600,86 +595,66 @@ pub fn parse_com<R: Read>(reader: &mut R) -> Result<Vec<u8>> {
}

// Section B.2.4.6
pub fn parse_app<R: Read>(reader: &mut R, marker: Marker) -> Result<Option<AppData>> {
pub fn parse_app<R: Read>(reader: &mut R, marker: Marker) -> Result<(AppSegment, Option<AppData>)> {
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))
}
21 changes: 21 additions & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}