Skip to content

Commit 424c344

Browse files
author
HeroicKatora
authored
Merge pull request #255 from AlanRace/override-colour-transform
Override colour transform
2 parents dc19f7b + 4cb0c16 commit 424c344

File tree

5 files changed

+199
-72
lines changed

5 files changed

+199
-72
lines changed

src/decoder.rs

Lines changed: 173 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,30 @@ pub struct ImageInfo {
7474
pub coding_process: CodingProcess,
7575
}
7676

77+
/// Describes the colour transform to apply before binary data is returned
78+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
79+
#[non_exhaustive]
80+
pub enum ColorTransform {
81+
/// No transform should be applied and the data is returned as-is.
82+
None,
83+
/// Unknown colour transformation
84+
Unknown,
85+
/// Grayscale transform should be applied (expects 1 channel)
86+
Grayscale,
87+
/// RGB transform should be applied.
88+
RGB,
89+
/// YCbCr transform should be applied.
90+
YCbCr,
91+
/// CMYK transform should be applied.
92+
CMYK,
93+
/// YCCK transform should be applied.
94+
YCCK,
95+
/// big gamut Y/Cb/Cr, bg-sYCC
96+
JcsBgYcc,
97+
/// big gamut red/green/blue, bg-sRGB
98+
JcsBgRgb,
99+
}
100+
77101
/// JPEG decoder
78102
pub struct Decoder<R> {
79103
reader: R,
@@ -84,7 +108,10 @@ pub struct Decoder<R> {
84108
quantization_tables: [Option<Arc<[u16; 64]>>; 4],
85109

86110
restart_interval: u16,
87-
color_transform: Option<AdobeColorTransform>,
111+
112+
adobe_color_transform: Option<AdobeColorTransform>,
113+
color_transform: Option<ColorTransform>,
114+
88115
is_jfif: bool,
89116
is_mjpeg: bool,
90117

@@ -111,6 +138,7 @@ impl<R: Read> Decoder<R> {
111138
ac_huffman_tables: vec![None, None, None, None],
112139
quantization_tables: [None, None, None, None],
113140
restart_interval: 0,
141+
adobe_color_transform: None,
114142
color_transform: None,
115143
is_jfif: false,
116144
is_mjpeg: false,
@@ -122,6 +150,12 @@ impl<R: Read> Decoder<R> {
122150
}
123151
}
124152

153+
/// Colour transform to use when decoding the image. App segments relating to colour transforms
154+
/// will be ignored.
155+
pub fn set_color_transform(&mut self, transform: ColorTransform) {
156+
self.color_transform = Some(transform);
157+
}
158+
125159
/// Set maximum buffer size allowed for decoded images
126160
pub fn set_max_decoding_buffer_size(&mut self, max: usize) {
127161
self.decoding_buffer_size_limit = max;
@@ -211,18 +245,15 @@ impl<R: Read> Decoder<R> {
211245
} else {
212246
PreferWorkerKind::Immediate
213247
}
214-
},
248+
}
215249
}
216250
}
217251

218252
/// Tries to read metadata from the image without decoding it.
219253
///
220254
/// If successful, the metadata can be obtained using the `info` method.
221255
pub fn read_info(&mut self) -> Result<()> {
222-
WorkerScope::with(|worker| {
223-
self.decode_internal(true, worker)
224-
})
225-
.map(|_| ())
256+
WorkerScope::with(|worker| self.decode_internal(true, worker)).map(|_| ())
226257
}
227258

228259
/// Configure the decoder to scale the image during decoding.
@@ -250,9 +281,7 @@ impl<R: Read> Decoder<R> {
250281

251282
/// Decodes the image and returns the decoded pixels if successful.
252283
pub fn decode(&mut self) -> Result<Vec<u8>> {
253-
WorkerScope::with(|worker| {
254-
self.decode_internal(false, worker)
255-
})
284+
WorkerScope::with(|worker| self.decode_internal(false, worker))
256285
}
257286

258287
fn decode_internal(
@@ -415,11 +444,13 @@ impl<R: Read> Decoder<R> {
415444
}
416445
}
417446

418-
let preference = Self::select_worker(&frame, PreferWorkerKind::Multithreaded);
447+
let preference =
448+
Self::select_worker(&frame, PreferWorkerKind::Multithreaded);
419449

420-
let (marker, data) = worker_scope.get_or_init_worker(
421-
preference,
422-
|worker| self.decode_scan(&frame, &scan, worker, &finished))?;
450+
let (marker, data) = worker_scope
451+
.get_or_init_worker(preference, |worker| {
452+
self.decode_scan(&frame, &scan, worker, &finished)
453+
})?;
423454

424455
if let Some(data) = data {
425456
for (i, plane) in data
@@ -492,7 +523,7 @@ impl<R: Read> Decoder<R> {
492523
if let Some(data) = parse_app(&mut self.reader, marker)? {
493524
match data {
494525
AppData::Adobe(color_transform) => {
495-
self.color_transform = Some(color_transform)
526+
self.adobe_color_transform = Some(color_transform)
496527
}
497528
AppData::Jfif => {
498529
// From the JFIF spec:
@@ -566,10 +597,9 @@ impl<R: Read> Decoder<R> {
566597
let frame = self.frame.as_ref().unwrap();
567598
let preference = Self::select_worker(&frame, PreferWorkerKind::Multithreaded);
568599

569-
worker_scope.get_or_init_worker(
570-
preference,
571-
|worker| self.decode_planes(worker, planes, planes_u16)
572-
)
600+
worker_scope.get_or_init_worker(preference, |worker| {
601+
self.decode_planes(worker, planes, planes_u16)
602+
})
573603
}
574604

575605
fn decode_planes(
@@ -649,12 +679,79 @@ impl<R: Read> Decoder<R> {
649679
&frame.components,
650680
planes,
651681
frame.output_size,
652-
self.is_jfif,
653-
self.color_transform,
682+
self.determine_color_transform(),
654683
)
655684
}
656685
}
657686

687+
fn determine_color_transform(&self) -> ColorTransform {
688+
if let Some(color_transform) = self.color_transform {
689+
return color_transform;
690+
}
691+
692+
let frame = self.frame.as_ref().unwrap();
693+
694+
if frame.components.len() == 1 {
695+
return ColorTransform::Grayscale;
696+
}
697+
698+
// Using logic for determining colour as described here: https://entropymine.wordpress.com/2018/10/22/how-is-a-jpeg-images-color-type-determined/
699+
700+
if frame.components.len() == 3 {
701+
match (
702+
frame.components[0].identifier,
703+
frame.components[1].identifier,
704+
frame.components[2].identifier,
705+
) {
706+
(1, 2, 3) => {
707+
return ColorTransform::YCbCr;
708+
}
709+
(1, 34, 35) => {
710+
return ColorTransform::JcsBgYcc;
711+
}
712+
(82, 71, 66) => {
713+
return ColorTransform::RGB;
714+
}
715+
(114, 103, 98) => {
716+
return ColorTransform::JcsBgRgb;
717+
}
718+
_ => {}
719+
}
720+
721+
if self.is_jfif {
722+
return ColorTransform::YCbCr;
723+
}
724+
}
725+
726+
if let Some(colour_transform) = self.adobe_color_transform {
727+
match colour_transform {
728+
AdobeColorTransform::Unknown => {
729+
if frame.components.len() == 3 {
730+
return ColorTransform::RGB;
731+
} else if frame.components.len() == 4 {
732+
return ColorTransform::CMYK;
733+
}
734+
}
735+
AdobeColorTransform::YCbCr => {
736+
return ColorTransform::YCbCr;
737+
}
738+
AdobeColorTransform::YCCK => {
739+
return ColorTransform::YCCK;
740+
}
741+
}
742+
} else if frame.components.len() == 4 {
743+
return ColorTransform::CMYK;
744+
}
745+
746+
if frame.components.len() == 4 {
747+
ColorTransform::YCCK
748+
} else if frame.components.len() == 3 {
749+
ColorTransform::YCbCr
750+
} else {
751+
ColorTransform::Unknown
752+
}
753+
}
754+
658755
fn read_marker(&mut self) -> Result<Marker> {
659756
loop {
660757
// This should be an error as the JPEG spec doesn't allow extraneous data between marker segments.
@@ -1190,8 +1287,7 @@ fn compute_image(
11901287
components: &[Component],
11911288
mut data: Vec<Vec<u8>>,
11921289
output_size: Dimensions,
1193-
is_jfif: bool,
1194-
color_transform: Option<AdobeColorTransform>,
1290+
color_transform: ColorTransform,
11951291
) -> Result<Vec<u8>> {
11961292
if data.is_empty() || data.iter().any(Vec::is_empty) {
11971293
return Err(Error::Format("not all components have data".to_owned()));
@@ -1221,36 +1317,58 @@ fn compute_image(
12211317
decoded.resize(size, 0);
12221318
Ok(decoded)
12231319
} else {
1224-
compute_image_parallel(components, data, output_size, is_jfif, color_transform)
1320+
compute_image_parallel(components, data, output_size, color_transform)
12251321
}
12261322
}
12271323

12281324
pub(crate) fn choose_color_convert_func(
12291325
component_count: usize,
1230-
_is_jfif: bool,
1231-
color_transform: Option<AdobeColorTransform>,
1326+
color_transform: ColorTransform,
12321327
) -> Result<fn(&[Vec<u8>], &mut [u8])> {
12331328
match component_count {
1234-
3 => {
1235-
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
1236-
// Unknown means the data is RGB, so we don't need to perform any color conversion on it.
1237-
if color_transform == Some(AdobeColorTransform::Unknown) {
1238-
Ok(color_convert_line_rgb)
1239-
} else {
1240-
Ok(color_convert_line_ycbcr)
1241-
}
1242-
}
1243-
4 => {
1244-
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
1245-
match color_transform {
1246-
Some(AdobeColorTransform::Unknown) => Ok(color_convert_line_cmyk),
1247-
Some(_) => Ok(color_convert_line_ycck),
1248-
None => {
1249-
// Assume CMYK because no APP14 marker was found
1250-
Ok(color_convert_line_cmyk)
1251-
}
1252-
}
1253-
}
1329+
3 => match color_transform {
1330+
ColorTransform::None => Ok(color_no_convert),
1331+
ColorTransform::Grayscale => Err(Error::Format(
1332+
"Invalid number of channels (3) for Grayscale data".to_string(),
1333+
)),
1334+
ColorTransform::RGB => Ok(color_convert_line_rgb),
1335+
ColorTransform::YCbCr => Ok(color_convert_line_ycbcr),
1336+
ColorTransform::CMYK => Err(Error::Format(
1337+
"Invalid number of channels (3) for CMYK data".to_string(),
1338+
)),
1339+
ColorTransform::YCCK => Err(Error::Format(
1340+
"Invalid number of channels (3) for YCCK data".to_string(),
1341+
)),
1342+
ColorTransform::JcsBgYcc => Err(Error::Unsupported(
1343+
UnsupportedFeature::ColorTransform(ColorTransform::JcsBgYcc),
1344+
)),
1345+
ColorTransform::JcsBgRgb => Err(Error::Unsupported(
1346+
UnsupportedFeature::ColorTransform(ColorTransform::JcsBgRgb),
1347+
)),
1348+
ColorTransform::Unknown => Err(Error::Format("Unknown colour transform".to_string())),
1349+
},
1350+
4 => match color_transform {
1351+
ColorTransform::None => Ok(color_no_convert),
1352+
ColorTransform::Grayscale => Err(Error::Format(
1353+
"Invalid number of channels (4) for Grayscale data".to_string(),
1354+
)),
1355+
ColorTransform::RGB => Err(Error::Format(
1356+
"Invalid number of channels (4) for RGB data".to_string(),
1357+
)),
1358+
ColorTransform::YCbCr => Err(Error::Format(
1359+
"Invalid number of channels (4) for YCbCr data".to_string(),
1360+
)),
1361+
ColorTransform::CMYK => Ok(color_convert_line_cmyk),
1362+
ColorTransform::YCCK => Ok(color_convert_line_ycck),
1363+
1364+
ColorTransform::JcsBgYcc => Err(Error::Unsupported(
1365+
UnsupportedFeature::ColorTransform(ColorTransform::JcsBgYcc),
1366+
)),
1367+
ColorTransform::JcsBgRgb => Err(Error::Unsupported(
1368+
UnsupportedFeature::ColorTransform(ColorTransform::JcsBgRgb),
1369+
)),
1370+
ColorTransform::Unknown => Err(Error::Format("Unknown colour transform".to_string())),
1371+
},
12541372
_ => panic!(),
12551373
}
12561374
}
@@ -1340,6 +1458,16 @@ fn color_convert_line_cmyk(data: &[Vec<u8>], output: &mut [u8]) {
13401458
}
13411459
}
13421460

1461+
fn color_no_convert(data: &[Vec<u8>], output: &mut [u8]) {
1462+
let mut output_iter = output.iter_mut();
1463+
1464+
for pixel in data {
1465+
for d in pixel {
1466+
*(output_iter.next().unwrap()) = *d;
1467+
}
1468+
}
1469+
}
1470+
13431471
const FIXED_POINT_OFFSET: i32 = 20;
13441472
const HALF: i32 = (1 << FIXED_POINT_OFFSET) / 2;
13451473

src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use core::result;
55
use std::error::Error as StdError;
66
use std::io::Error as IoError;
77

8+
use crate::ColorTransform;
9+
810
pub type Result<T> = result::Result<T, Error>;
911

1012
/// An enumeration over JPEG features (currently) unsupported by this library.
@@ -27,6 +29,8 @@ pub enum UnsupportedFeature {
2729
SubsamplingRatio,
2830
/// A subsampling ratio not representable as an integer.
2931
NonIntegerSubsamplingRatio,
32+
/// Colour transform
33+
ColorTransform(ColorTransform),
3034
}
3135

3236
/// Errors that can occur while decoding a JPEG image.

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ extern crate core;
3636
#[cfg(feature = "rayon")]
3737
extern crate rayon;
3838

39-
pub use decoder::{Decoder, ImageInfo, PixelFormat};
39+
pub use decoder::{ColorTransform, Decoder, ImageInfo, PixelFormat};
4040
pub use error::{Error, UnsupportedFeature};
4141
pub use parser::CodingProcess;
4242

0 commit comments

Comments
 (0)