Identify and drop useless sRGB profiles (#143)

This commit is contained in:
Kornel 2018-11-13 20:37:02 +01:00 committed by Josh Holmer
parent 88035ccbae
commit 96eda85806
4 changed files with 79 additions and 1 deletions

View file

@ -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};
@ -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

BIN
tests/files/badsrgb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -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();
}

View file

@ -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::*;
@ -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);
}