Skip to content

Commit

Permalink
Identify and drop useless sRGB profiles (#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
kornelski authored and shssoichiro committed Nov 13, 2018
1 parent 88035cc commit 96eda85
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 1 deletion.
63 changes: 63 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ extern crate zopfli;
use atomicmin::AtomicMin;
use image::{DynamicImage, GenericImageView, ImageFormat, Pixel};
use png::PngData;
use deflate::inflate;
use crc::crc32;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use std::collections::{HashMap, HashSet};
Expand Down Expand Up @@ -849,6 +851,67 @@ fn perform_strip(png: &mut PngData, opts: &Options) {
png.aux_headers = HashMap::new();
}
}

let may_replace_iccp = match opts.strip {
Headers::None => false,
Headers::Keep(ref hdrs) => hdrs.contains("sRGB"),
Headers::Strip(ref hdrs) => !hdrs.iter().any(|v| v == "sRGB"),
Headers::Safe => true,
Headers::All => false,
};

if may_replace_iccp {
if png.aux_headers.get(b"sRGB").is_some() {
// Files aren't supposed to have both chunks, so we chose to honor sRGB
png.aux_headers.remove(b"iCCP");
} else if let Some(intent) = png.aux_headers.get(b"iCCP")
.and_then(|iccp| srgb_rendering_intent(iccp)) {
// sRGB-like profile can be safely replaced with
// an sRGB chunk with the same rendering intent
png.aux_headers.remove(b"iCCP");
png.aux_headers.insert(*b"sRGB", vec![intent]);
}
}
}

/// If the profile is sRGB, extracts the rendering intent value from it
fn srgb_rendering_intent(mut iccp: &[u8]) -> Option<u8> {
// Skip (useless) profile name
loop {
let (&n, rest) = iccp.split_first()?;
iccp = rest;
if n == 0 {break;}
}

let (&compression_method, compressed_data) = iccp.split_first()?;
if compression_method != 0 {
return None; // The profile is supposed to be compressed (method 0)
}
let icc_data = inflate(compressed_data).ok()?;

let rendering_intent = *icc_data.get(67)?;

// The known profiles are the same as in libpng's `png_sRGB_checks`.
// The Profile ID header of ICC has a fixed layout,
// and is supposed to contain MD5 of profile data at this offset
match icc_data.get(84..100)? {
b"\x29\xf8\x3d\xde\xaf\xf2\x55\xae\x78\x42\xfa\xe4\xca\x83\x39\x0d" |
b"\xc9\x5b\xd6\x37\xe9\x5d\x8a\x3b\x0d\xf3\x8f\x99\xc1\x32\x03\x89" |
b"\xfc\x66\x33\x78\x37\xe2\x88\x6b\xfd\x72\xe9\x83\x82\x28\xf1\xb8" |
b"\x34\x56\x2a\xbf\x99\x4c\xcd\x06\x6d\x2c\x57\x21\xd0\xd6\x8c\x5d" => {
Some(rendering_intent)
},
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" => {
// Known-bad profiles are identified by their CRC
match (crc32::checksum_ieee(&icc_data), icc_data.len()) {
(0x5d5129ce, 3024) |
(0x182ea552, 3144) |
(0xf29e526d, 3144) => Some(rendering_intent),
_ => None,
}
},
_ => None,
}
}

/// Check if an image was already optimized prior to oxipng's operations
Expand Down
Binary file added tests/files/badsrgb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion tests/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ fn strip_headers_safe() {

assert!(!png.aux_headers.contains_key(b"tEXt"));
assert!(!png.aux_headers.contains_key(b"iTXt"));
assert!(png.aux_headers.contains_key(b"iCCP"));
assert!(png.aux_headers.contains_key(b"sRGB"));

remove_file(output).ok();
}
Expand Down
15 changes: 15 additions & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
extern crate oxipng;

use oxipng::OutFile;
use oxipng::Headers;
use std::default::Default;
use std::fs;
use std::fs::File;
use std::io::prelude::*;

Expand Down Expand Up @@ -82,3 +84,16 @@ fn optimize_apng() {
);
assert!(result.is_err());
}

#[test]
fn optimize_srgb_icc() {
let file = fs::read("tests/files/badsrgb.png").unwrap();
let mut opts: oxipng::Options = Default::default();

let result = oxipng::optimize_from_memory(&file, &opts);
assert!(result.unwrap().len() > 1000);

opts.strip = Headers::Safe;
let result = oxipng::optimize_from_memory(&file, &opts);
assert!(result.unwrap().len() < 1000);
}

0 comments on commit 96eda85

Please sign in to comment.