Skip to content

Commit

Permalink
Merge pull request #268 from hfiguiere/xmp
Browse files Browse the repository at this point in the history
Read the XMP packet
  • Loading branch information
fintelia authored May 16, 2023
2 parents 269cd7d + 763ae22 commit c17977d
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 14 deletions.
1 change: 1 addition & 0 deletions examples/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ fn main() {

eprintln!("{:?}", info);
eprintln!("Exif: {}", decoder.exif_data().is_some());
eprintln!("XMP: {}", decoder.xmp_data().is_some());
eprintln!("ICC: {}", decoder.icc_profile().is_some());

let output_file = File::create(output_path).unwrap();
Expand Down
13 changes: 13 additions & 0 deletions src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ pub struct Decoder<R> {
icc_markers: Vec<IccChunk>,

exif_data: Option<Vec<u8>>,
xmp_data: Option<Vec<u8>>,
psir_data: Option<Vec<u8>>,

// Used for progressive JPEGs.
coefficients: Vec<Vec<i16>>,
Expand All @@ -144,6 +146,8 @@ impl<R: Read> Decoder<R> {
is_mjpeg: false,
icc_markers: Vec::new(),
exif_data: None,
xmp_data: None,
psir_data: None,
coefficients: Vec::new(),
coefficients_finished: [0; MAX_COMPONENTS],
decoding_buffer_size_limit: usize::MAX,
Expand Down Expand Up @@ -197,6 +201,13 @@ impl<R: Read> Decoder<R> {
self.exif_data.as_deref()
}

/// Returns the raw XMP packet if there is any.
///
/// The returned value will be `None` until a call to `decode` has returned `Ok`.
pub fn xmp_data(&self) -> Option<&[u8]> {
self.xmp_data.as_deref()
}

/// 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 @@ -542,6 +553,8 @@ impl<R: Read> Decoder<R> {
AppData::Avi1 => self.is_mjpeg = true,
AppData::Icc(icc) => self.icc_markers.push(icc),
AppData::Exif(data) => self.exif_data = Some(data),
AppData::Xmp(data) => self.xmp_data = Some(data),
AppData::Psir(data) => self.psir_data = Some(data),
}
}
}
Expand Down
45 changes: 31 additions & 14 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ pub enum AppData {
Avi1,
Icc(IccChunk),
Exif(Vec<u8>),
Xmp(Vec<u8>),
Psir(Vec<u8>),
}

// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
Expand Down Expand Up @@ -623,21 +625,20 @@ pub fn parse_app<R: Read>(reader: &mut R, marker: Marker) -> Result<Option<AppDa
}
}
}
// 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));
}
let mut buffer = vec![0u8; length];
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 length >= 6 && buffer[0..6] == *b"Exif\x00\x00" {
result = Some(AppData::Exif(buffer[6..].to_vec()));
}
// XMP packet
// https://github.com/adobe/XMP-Toolkit-SDK/blob/main/docs/XMPSpecificationPart3.pdf
else if length >= 29 && buffer[0..29] == *b"http://ns.adobe.com/xap/1.0/\0" {
result = Some(AppData::Xmp(buffer[29..].to_vec()));
}
}
APP(2) => {
Expand All @@ -660,6 +661,22 @@ pub fn parse_app<R: Read>(reader: &mut R, marker: Marker) -> Result<Option<AppDa
}
}
}
APP(13) => {
if length >= 14 {
let mut buffer = [0u8; 14];
reader.read_exact(&mut buffer)?;
bytes_read = buffer.len();

// PSIR (Photoshop)
// https://github.com/adobe/XMP-Toolkit-SDK/blob/main/docs/XMPSpecificationPart3.pdf
if buffer[0..14] == *b"Photoshop 3.0\0" {
let mut data = vec![0; length - bytes_read];
reader.read_exact(&mut data)?;
bytes_read += data.len();
result = Some(AppData::Psir(data));
}
}
}
APP(14) => {
if length >= 12 {
let mut buffer = [0u8; 12];
Expand Down
14 changes: 14 additions & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,17 @@ 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_xmp_data() {
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 xmp_data = decoder.xmp_data().unwrap();
assert_eq!(&xmp_data[0..9], b"<?xpacket");
}

0 comments on commit c17977d

Please sign in to comment.