From 2e7bfa4ebc80d840527384580b14e236c090ecef Mon Sep 17 00:00:00 2001 From: Joshua Holmer Date: Sun, 10 Apr 2016 20:24:44 -0400 Subject: [PATCH] Add palette reduction Closes #11 --- CHANGELOG.md | 3 + src/lib.rs | 18 +- src/png.rs | 202 +++++++++++++++++- .../palette_should_be_reduced_with_both.png | Bin 0 -> 19618 bytes .../palette_should_be_reduced_with_dupes.png | Bin 0 -> 20260 bytes .../palette_should_be_reduced_with_unused.png | Bin 0 -> 19601 bytes tests/reduction.rs | 120 +++++++++++ 7 files changed, 330 insertions(+), 13 deletions(-) create mode 100644 tests/files/palette_should_be_reduced_with_both.png create mode 100644 tests/files/palette_should_be_reduced_with_dupes.png create mode 100644 tests/files/palette_should_be_reduced_with_unused.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 40666d10..20db757e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +**Version 0.5.0 (unreleased)** + - [SEMVER_MINOR] Palette entries can now reduced, on by default ([#11](https://github.com/shssoichiro/oxipng/issues/11)) + **Version 0.4.0** - Performance optimizations - [SEMVER_MAJOR] `-s` automatically infers `--strip safe` ([#31](https://github.com/shssoichiro/oxipng/issues/31)) diff --git a/src/lib.rs b/src/lib.rs index c95000e5..0823e372 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -153,6 +153,15 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { let mut something_changed = false; + if opts.palette_reduction { + if png.reduce_palette() { + something_changed = true; + if opts.verbosity == Some(1) { + report_reduction(&png); + } + }; + } + if opts.bit_depth_reduction { if png.reduce_bit_depth() { something_changed = true; @@ -171,15 +180,6 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { }; } - if opts.palette_reduction { - if png.reduce_palette() { - something_changed = true; - if opts.verbosity == Some(1) { - report_reduction(&png); - } - }; - } - if something_changed && opts.verbosity.is_some() { report_reduction(&png); } diff --git a/src/png.rs b/src/png.rs index 770a0bbb..5ae5af0d 100644 --- a/src/png.rs +++ b/src/png.rs @@ -1,7 +1,7 @@ use bit_vec::BitVec; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use crc::crc32; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt; use std::fs::File; use std::io::Cursor; @@ -553,8 +553,202 @@ impl PngData { /// Attempt to reduce the number of colors in the palette /// Returns true if the palette was reduced, false otherwise pub fn reduce_palette(&mut self) -> bool { - // TODO: Implement - false + if self.ihdr_data.color_type != ColorType::Indexed { + // Can't reduce if there is no palette + return false; + } + if self.ihdr_data.bit_depth == BitDepth::One { + // Gains from 1-bit images will be at most 1 byte + // Not worth the CPU time + return false; + } + + // A palette with RGB slices + let palette = self.palette.clone().unwrap(); + let mut indexed_palette: Vec<&[u8]> = palette.chunks(3).collect(); + // A map of old indexes to new ones, for any moved + let mut index_map: HashMap = HashMap::new(); + + // A list of (original) indices that are duplicates and no longer needed + let mut duplicates: Vec = Vec::new(); + { + // Find duplicate entries in the palette + let mut seen: HashMap<&[u8], u8> = HashMap::with_capacity(indexed_palette.len()); + for (i, color) in indexed_palette.iter().enumerate() { + if seen.contains_key(color) { + let index = seen.get(color).unwrap(); + duplicates.push(i as u8); + index_map.insert(i as u8, *index); + } else { + seen.insert(*color, i as u8); + } + } + } + + // Remove duplicates from the data + if !duplicates.is_empty() { + self.do_palette_reduction(&mut duplicates, &mut index_map, &mut indexed_palette); + } + + // A list of unused palette indices + let mut unused: Vec = Vec::new(); + { + // Find palette entries that are never used + let mut seen = HashSet::with_capacity(indexed_palette.len()); + for line in self.scan_lines() { + match self.ihdr_data.bit_depth { + BitDepth::Eight => { + for byte in &line.data { + seen.insert(*byte); + } + } + BitDepth::Four => { + let bitvec = BitVec::from_bytes(&line.data); + let mut current = 0u8; + for (i, bit) in bitvec.iter().enumerate() { + let mod_i = i % 4; + if bit { + current += 2u8.pow(3u32 - mod_i as u32); + } + if mod_i == 3 { + seen.insert(current); + current = 0; + } + } + } + BitDepth::Two => { + let bitvec = BitVec::from_bytes(&line.data); + let mut current = 0u8; + for (i, bit) in bitvec.iter().enumerate() { + let mod_i = i % 2; + if bit { + current += 2u8.pow(1u32 - mod_i as u32); + } + if mod_i == 1 { + seen.insert(current); + current = 0; + } + } + } + _ => unreachable!(), + } + + if seen.len() == indexed_palette.len() { + // Exit early if no further possible optimizations + // Check at the end of each line + // Checking after every pixel would be overly expensive + return !duplicates.is_empty(); + } + } + for i in 0..indexed_palette.len() as u8 { + if !seen.contains(&i) { + unused.push(i); + } + } + } + + // Remove unused palette indices + self.do_palette_reduction(&mut unused, &mut index_map, &mut indexed_palette); + + true + } + fn do_palette_reduction(&mut self, + indices: &mut Vec, + index_map: &mut HashMap, + indexed_palette: &mut Vec<&[u8]>) { + let mut new_data = Vec::with_capacity(self.raw_data.len()); + let mut alpha_palette = self.aux_headers.get("tRNS").cloned(); + let original_len = indexed_palette.len(); + indices.sort_by(|a, b| b.cmp(a)); + for idx in indices { + for i in (*idx as usize + 1)..original_len { + let existing = index_map.entry(i as u8).or_insert(i as u8); + if *existing >= *idx { + *existing -= 1; + } + } + indexed_palette.remove(*idx as usize); + if let Some(ref mut alpha) = alpha_palette { + alpha.remove(*idx as usize); + } + } + if alpha_palette.is_some() { + let alpha_header = self.aux_headers.get_mut("tRNS"); + if let Some(alpha_hdr) = alpha_header { + *alpha_hdr = alpha_palette.unwrap(); + } + } + // Reassign data bytes to new indices + for line in self.scan_lines() { + new_data.push(line.filter); + match self.ihdr_data.bit_depth { + BitDepth::Eight => { + for byte in &line.data { + if let Some(new_idx) = index_map.get(byte) { + new_data.push(*new_idx); + } else { + new_data.push(*byte); + } + } + } + BitDepth::Four => { + for byte in &line.data { + let upper = *byte >> 4; + let lower = *byte & 0b00001111; + let mut new_byte = 0u8; + if let Some(new_idx) = index_map.get(&upper) { + new_byte = new_byte & (*new_idx << 4); + } else { + new_byte = new_byte & (upper << 4); + } + if let Some(new_idx) = index_map.get(&lower) { + new_byte = new_byte & *new_idx; + } else { + new_byte = new_byte & lower; + } + new_data.push(new_byte); + } + } + BitDepth::Two => { + for byte in &line.data { + let one = *byte >> 6; + let two = (*byte >> 4) & 0b00000011; + let three = (*byte >> 2) & 0b00000011; + let four = *byte & 0b00000011; + let mut new_byte = 0u8; + if let Some(new_idx) = index_map.get(&one) { + new_byte = new_byte & (*new_idx << 6); + } else { + new_byte = new_byte & (one << 6); + } + if let Some(new_idx) = index_map.get(&two) { + new_byte = new_byte & (*new_idx << 4); + } else { + new_byte = new_byte & (two << 4); + } + if let Some(new_idx) = index_map.get(&three) { + new_byte = new_byte & (*new_idx << 2); + } else { + new_byte = new_byte & (three << 2); + } + if let Some(new_idx) = index_map.get(&four) { + new_byte = new_byte & *new_idx; + } else { + new_byte = new_byte & four; + } + new_data.push(new_byte); + } + } + _ => unreachable!(), + } + } + index_map.clear(); + self.raw_data = new_data; + let mut new_palette = Vec::with_capacity(indexed_palette.len() * 3); + for color in indexed_palette { + new_palette.extend_from_slice(color); + } + self.palette = Some(new_palette); } /// Attempt to reduce the color type of the image /// Returns true if the color type was reduced, false otherwise @@ -612,7 +806,7 @@ impl PngData { } if self.ihdr_data.color_type == ColorType::Indexed && self.transparency_palette.is_none() && - self.palette.as_ref().map(|x| x.len()).unwrap() > 128 { + self.palette.as_ref().map(|x| x.len()).unwrap() > 16 { if let Some(data) = reduce_palette_to_grayscale(self) { self.raw_data = data; self.palette = None; diff --git a/tests/files/palette_should_be_reduced_with_both.png b/tests/files/palette_should_be_reduced_with_both.png new file mode 100644 index 0000000000000000000000000000000000000000..ede9b2bc244c4982d21227737d827f2b62ac0099 GIT binary patch literal 19618 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 literal 0 HcmV?d00001 diff --git a/tests/files/palette_should_be_reduced_with_dupes.png b/tests/files/palette_should_be_reduced_with_dupes.png new file mode 100644 index 0000000000000000000000000000000000000000..bf8dfa92d013a986878b52bf0c8f7cb810f9ff03 GIT binary patch literal 20260 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 AJpcdz literal 0 HcmV?d00001 diff --git a/tests/reduction.rs b/tests/reduction.rs index 68704325..08d7e836 100644 --- a/tests/reduction.rs +++ b/tests/reduction.rs @@ -1274,3 +1274,123 @@ fn small_files() { png::ColorType::Indexed, png::BitDepth::One); } + +#[test] +fn palette_should_be_reduced_with_dupes() { + let input = PathBuf::from("tests/files/palette_should_be_reduced_with_dupes.png"); + let opts = get_opts(&input); + let output = opts.out_file.clone(); + + let png = png::PngData::new(&input).unwrap(); + + assert!(png.ihdr_data.color_type == png::ColorType::Indexed); + assert!(png.ihdr_data.bit_depth == png::BitDepth::Eight); + assert!(png.palette.unwrap().len() == 43 * 3); + + match oxipng::optimize(&input, &opts) { + Ok(_) => (), + Err(x) => panic!(x.to_owned()), + }; + assert!(output.exists()); + + let png = match png::PngData::new(&output) { + Ok(x) => x, + Err(x) => { + remove_file(&output).ok(); + panic!(x.to_owned()) + } + }; + + assert!(png.ihdr_data.color_type == png::ColorType::Indexed); + assert!(png.ihdr_data.bit_depth == png::BitDepth::Eight); + assert!(png.palette.unwrap().len() == 35 * 3); + + let old_png = image::open(&input).unwrap(); + let new_png = image::open(&output).unwrap(); + + // Conversion should be lossless + assert!(old_png.pixels().map(|x| x.2.channels().to_owned()).collect::>>() == + new_png.pixels().map(|x| x.2.channels().to_owned()).collect::>>()); + + remove_file(output).ok(); +} + +#[test] +fn palette_should_be_reduced_with_unused() { + let input = PathBuf::from("tests/files/palette_should_be_reduced_with_unused.png"); + let opts = get_opts(&input); + let output = opts.out_file.clone(); + + let png = png::PngData::new(&input).unwrap(); + + assert!(png.ihdr_data.color_type == png::ColorType::Indexed); + assert!(png.ihdr_data.bit_depth == png::BitDepth::Eight); + assert!(png.palette.unwrap().len() == 35 * 3); + + match oxipng::optimize(&input, &opts) { + Ok(_) => (), + Err(x) => panic!(x.to_owned()), + }; + assert!(output.exists()); + + let png = match png::PngData::new(&output) { + Ok(x) => x, + Err(x) => { + remove_file(&output).ok(); + panic!(x.to_owned()) + } + }; + + assert!(png.ihdr_data.color_type == png::ColorType::Indexed); + assert!(png.ihdr_data.bit_depth == png::BitDepth::Eight); + assert!(png.palette.unwrap().len() == 33 * 3); + + let old_png = image::open(&input).unwrap(); + let new_png = image::open(&output).unwrap(); + + // Conversion should be lossless + assert!(old_png.pixels().map(|x| x.2.channels().to_owned()).collect::>>() == + new_png.pixels().map(|x| x.2.channels().to_owned()).collect::>>()); + + remove_file(output).ok(); +} + +#[test] +fn palette_should_be_reduced_with_both() { + let input = PathBuf::from("tests/files/palette_should_be_reduced_with_both.png"); + let opts = get_opts(&input); + let output = opts.out_file.clone(); + + let png = png::PngData::new(&input).unwrap(); + + assert!(png.ihdr_data.color_type == png::ColorType::Indexed); + assert!(png.ihdr_data.bit_depth == png::BitDepth::Eight); + assert!(png.palette.unwrap().len() == 43 * 3); + + match oxipng::optimize(&input, &opts) { + Ok(_) => (), + Err(x) => panic!(x.to_owned()), + }; + assert!(output.exists()); + + let png = match png::PngData::new(&output) { + Ok(x) => x, + Err(x) => { + remove_file(&output).ok(); + panic!(x.to_owned()) + } + }; + + assert!(png.ihdr_data.color_type == png::ColorType::Indexed); + assert!(png.ihdr_data.bit_depth == png::BitDepth::Eight); + assert!(png.palette.unwrap().len() == 33 * 3); + + let old_png = image::open(&input).unwrap(); + let new_png = image::open(&output).unwrap(); + + // Conversion should be lossless + assert!(old_png.pixels().map(|x| x.2.channels().to_owned()).collect::>>() == + new_png.pixels().map(|x| x.2.channels().to_owned()).collect::>>()); + + remove_file(output).ok(); +}