Identify and drop useless sRGB profiles (#143)
This commit is contained in:
parent
88035ccbae
commit
96eda85806
4 changed files with 79 additions and 1 deletions
63
src/lib.rs
63
src/lib.rs
|
|
@ -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
BIN
tests/files/badsrgb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
15
tests/lib.rs
15
tests/lib.rs
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue