Skip to content

Commit 29b9c04

Browse files
author
HeroicKatora
authored
Merge pull request #174 from jrmuizel/icc
Add support for extracting the ICC profile.
2 parents 97742b3 + 7c5f1f4 commit 29b9c04

File tree

3 files changed

+83
-2
lines changed

3 files changed

+83
-2
lines changed

src/decoder.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use error::{Error, Result, UnsupportedFeature};
33
use huffman::{fill_default_mjpeg_tables, HuffmanDecoder, HuffmanTable};
44
use marker::Marker;
55
use parser::{AdobeColorTransform, AppData, CodingProcess, Component, Dimensions, EntropyCoding, FrameInfo,
6-
parse_app, parse_com, parse_dht, parse_dqt, parse_dri, parse_sof, parse_sos, ScanInfo};
6+
parse_app, parse_com, parse_dht, parse_dqt, parse_dri, parse_sof, parse_sos, IccChunk,
7+
ScanInfo};
78
use upsampler::Upsampler;
89
use std::cmp;
910
use std::io::Read;
@@ -72,6 +73,8 @@ pub struct Decoder<R> {
7273
is_jfif: bool,
7374
is_mjpeg: bool,
7475

76+
icc_markers: Vec<IccChunk>,
77+
7578
// Used for progressive JPEGs.
7679
coefficients: Vec<Vec<i16>>,
7780
// Bitmask of which coefficients has been completely decoded.
@@ -91,6 +94,7 @@ impl<R: Read> Decoder<R> {
9194
color_transform: None,
9295
is_jfif: false,
9396
is_mjpeg: false,
97+
icc_markers: Vec::new(),
9498
coefficients: Vec::new(),
9599
coefficients_finished: [0; MAX_COMPONENTS],
96100
}
@@ -120,6 +124,39 @@ impl<R: Read> Decoder<R> {
120124
}
121125
}
122126

127+
/// Returns the embeded icc profile if the image contains one.
128+
pub fn icc_profile(&self) -> Option<Vec<u8>> {
129+
let mut marker_present: [Option<&IccChunk>; 256] = [None; 256];
130+
let num_markers = self.icc_markers.len();
131+
if num_markers == 0 && num_markers < 256 {
132+
return None;
133+
}
134+
// check the validity of the markers
135+
for chunk in &self.icc_markers {
136+
if usize::from(chunk.num_markers) != num_markers {
137+
// all the lengths must match
138+
return None;
139+
}
140+
if chunk.seq_no == 0 {
141+
return None;
142+
}
143+
if marker_present[usize::from(chunk.seq_no)].is_some() {
144+
// duplicate seq_no
145+
return None;
146+
} else {
147+
marker_present[usize::from(chunk.seq_no)] = Some(chunk);
148+
}
149+
}
150+
151+
// assemble them together by seq_no failing if any are missing
152+
let mut data = Vec::new();
153+
// seq_no's start at 1
154+
for &chunk in marker_present.get(1..=num_markers)? {
155+
data.extend_from_slice(&chunk?.data);
156+
}
157+
Some(data)
158+
}
159+
123160
/// Tries to read metadata from the image without decoding it.
124161
///
125162
/// If successful, the metadata can be obtained using the `info` method.
@@ -336,6 +373,7 @@ impl<R: Read> Decoder<R> {
336373
self.is_jfif = true;
337374
},
338375
AppData::Avi1 => self.is_mjpeg = true,
376+
AppData::Icc(icc) => self.icc_markers.push(icc),
339377
}
340378
}
341379
},

src/parser.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ pub enum AppData {
7070
Adobe(AdobeColorTransform),
7171
Jfif,
7272
Avi1,
73+
Icc(IccChunk),
7374
}
7475

7576
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
@@ -81,6 +82,12 @@ pub enum AdobeColorTransform {
8182
// YCbCrK
8283
YCCK,
8384
}
85+
#[derive(Debug)]
86+
pub struct IccChunk {
87+
pub num_markers: u8,
88+
pub seq_no: u8,
89+
pub data: Vec<u8>,
90+
}
8491

8592
impl FrameInfo {
8693
pub(crate) fn update_idct_size(&mut self, idct_size: usize) -> Result<()> {
@@ -554,7 +561,27 @@ pub fn parse_app<R: Read>(reader: &mut R, marker: Marker) -> Result<Option<AppDa
554561
result = Some(AppData::Avi1);
555562
}
556563
}
557-
},
564+
}
565+
APP(2) => {
566+
if length > 14 {
567+
let mut buffer = [0u8; 14];
568+
reader.read_exact(&mut buffer)?;
569+
bytes_read = buffer.len();
570+
571+
// http://www.color.org/ICC_Minor_Revision_for_Web.pdf
572+
// B.4 Embedding ICC profiles in JFIF files
573+
if &buffer[0..12] == b"ICC_PROFILE\0" {
574+
let mut data = vec![0; length - bytes_read];
575+
reader.read_exact(&mut data)?;
576+
bytes_read += data.len();
577+
result = Some(AppData::Icc(IccChunk {
578+
seq_no: buffer[12],
579+
num_markers: buffer[13],
580+
data,
581+
}));
582+
}
583+
}
584+
}
558585
APP(14) => {
559586
if length >= 12 {
560587
let mut buffer = [0u8; 12];

tests/lib.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,19 @@ fn read_info() {
2626
assert_eq!(info, ref_info);
2727
assert_eq!(data, ref_data);
2828
}
29+
30+
#[test]
31+
fn read_icc_profile() {
32+
let path = Path::new("tests")
33+
.join("reftest")
34+
.join("images")
35+
.join("mozilla")
36+
.join("jpg-srgb-icc.jpg");
37+
38+
let mut decoder = jpeg::Decoder::new(File::open(&path).unwrap());
39+
decoder.decode().unwrap();
40+
41+
let profile = decoder.icc_profile().unwrap();
42+
// "acsp" is a mandatory string in ICC profile headers.
43+
assert_eq!(&profile[36..40], b"acsp");
44+
}

0 commit comments

Comments
 (0)