Significant refactoring using itertools

This commit is contained in:
Josh Holmer 2016-07-14 08:09:58 -04:00
parent 0f790a1604
commit 332c27c7d0
6 changed files with 85 additions and 98 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@ target
*.bk *.bk
.DS_Store .DS_Store
*.out.png *.out.png
/.idea

View file

@ -1,3 +1,6 @@
**Version 0.8.3**
- Significant refactoring
**Version 0.8.2** **Version 0.8.2**
- Fix issue where images smaller than 4px width would crash on interlacing ([#42](https://github.com/shssoichiro/oxipng/issues/42)) - Fix issue where images smaller than 4px width would crash on interlacing ([#42](https://github.com/shssoichiro/oxipng/issues/42))

6
Cargo.lock generated
View file

@ -7,6 +7,7 @@ dependencies = [
"clap 2.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crc 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "crc 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"libz-sys 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "libz-sys 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "miniz-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -124,6 +125,11 @@ name = "inflate"
version = "0.1.1" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "itertools"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "kernel32-sys" name = "kernel32-sys"
version = "0.2.2" version = "0.2.2"

View file

@ -26,6 +26,7 @@ bit-vec = "^0.4.2"
byteorder = "^0.5.0" byteorder = "^0.5.0"
clap = "^2.2.5" clap = "^2.2.5"
crc = "^1.2.0" crc = "^1.2.0"
itertools = "^0.4.16"
libc = "^0.2.4" libc = "^0.2.4"
libz-sys = "^1.0.0" libz-sys = "^1.0.0"
miniz-sys = "^0.1.7" miniz-sys = "^0.1.7"

View file

@ -1,6 +1,7 @@
extern crate bit_vec; extern crate bit_vec;
extern crate byteorder; extern crate byteorder;
extern crate crc; extern crate crc;
extern crate itertools;
extern crate libc; extern crate libc;
extern crate libz_sys; extern crate libz_sys;
extern crate miniz_sys; extern crate miniz_sys;

View file

@ -1,6 +1,7 @@
use bit_vec::BitVec; use bit_vec::BitVec;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use crc::crc32; use crc::crc32;
use itertools::Itertools;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fmt; use std::fmt;
use std::fs::File; use std::fs::File;
@ -612,80 +613,73 @@ impl PngData {
// Remove duplicates from the data // Remove duplicates from the data
if !duplicates.is_empty() { if !duplicates.is_empty() {
self.do_palette_reduction(&mut duplicates, &mut index_map, &mut indexed_palette); self.do_palette_reduction(&duplicates, &mut index_map, &mut indexed_palette);
} }
// A list of unused palette indices // Find palette entries that are never used
let mut unused: Vec<u8> = Vec::new(); let mut seen = HashSet::with_capacity(indexed_palette.len());
{ for line in self.scan_lines() {
// Find palette entries that are never used match self.ihdr_data.bit_depth {
let mut seen = HashSet::with_capacity(indexed_palette.len()); BitDepth::Eight => {
for line in self.scan_lines() { for byte in &line.data {
match self.ihdr_data.bit_depth { seen.insert(*byte);
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!(),
} }
BitDepth::Four => {
if seen.len() == indexed_palette.len() { let bitvec = BitVec::from_bytes(&line.data);
// Exit early if no further possible optimizations let mut current = 0u8;
// Check at the end of each line for (i, bit) in bitvec.iter().enumerate() {
// Checking after every pixel would be overly expensive let mod_i = i % 4;
return !duplicates.is_empty(); 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!(),
} }
for i in 0..indexed_palette.len() as u8 {
if !seen.contains(&i) { if seen.len() == indexed_palette.len() {
unused.push(i); // 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();
} }
} }
let unused: Vec<u8> =
(0..indexed_palette.len() as u8).filter(|i| !seen.contains(&i)).collect();
// Remove unused palette indices // Remove unused palette indices
self.do_palette_reduction(&mut unused, &mut index_map, &mut indexed_palette); self.do_palette_reduction(&unused, &mut index_map, &mut indexed_palette);
true true
} }
fn do_palette_reduction(&mut self, fn do_palette_reduction(&mut self,
indices: &mut Vec<u8>, indices: &[u8],
index_map: &mut HashMap<u8, u8>, index_map: &mut HashMap<u8, u8>,
indexed_palette: &mut Vec<&[u8]>) { indexed_palette: &mut Vec<&[u8]>) {
let mut new_data = Vec::with_capacity(self.raw_data.len()); let mut new_data = Vec::with_capacity(self.raw_data.len());
let mut alpha_palette = self.aux_headers.get("tRNS").cloned(); let mut alpha_palette = self.aux_headers.get("tRNS").cloned();
let original_len = indexed_palette.len(); let original_len = indexed_palette.len();
indices.sort_by(|a, b| b.cmp(a)); for idx in indices.iter().sorted_by(|a, b| b.cmp(a)) {
for idx in indices {
for i in (*idx as usize + 1)..original_len { for i in (*idx as usize + 1)..original_len {
let existing = index_map.entry(i as u8).or_insert(i as u8); let existing = index_map.entry(i as u8).or_insert(i as u8);
if *existing >= *idx { if *existing >= *idx {
@ -769,10 +763,7 @@ impl PngData {
} }
index_map.clear(); index_map.clear();
self.raw_data = new_data; self.raw_data = new_data;
let mut new_palette = Vec::with_capacity(indexed_palette.len() * 3); let new_palette = indexed_palette.iter().cloned().flatten().cloned().collect::<Vec<u8>>();
for color in indexed_palette {
new_palette.extend_from_slice(color);
}
self.palette = Some(new_palette); self.palette = Some(new_palette);
} }
/// Attempt to reduce the color type of the image /// Attempt to reduce the color type of the image
@ -881,10 +872,7 @@ impl PngData {
} }
fn interlace_image(png: &mut PngData) { fn interlace_image(png: &mut PngData) {
let mut passes: Vec<BitVec> = Vec::with_capacity(7); let mut passes: Vec<BitVec> = vec![BitVec::new(); 7];
for _ in 0..7 {
passes.push(BitVec::new());
}
let bits_per_pixel = png.ihdr_data.bit_depth.as_u8() * png.channels_per_pixel(); let bits_per_pixel = png.ihdr_data.bit_depth.as_u8() * png.channels_per_pixel();
for (index, line) in png.scan_lines().enumerate() { for (index, line) in png.scan_lines().enumerate() {
match index % 8 { match index % 8 {
@ -971,13 +959,11 @@ fn interlace_image(png: &mut PngData) {
fn deinterlace_image(png: &mut PngData) { fn deinterlace_image(png: &mut PngData) {
let bits_per_pixel = png.ihdr_data.bit_depth.as_u8() * png.channels_per_pixel(); let bits_per_pixel = png.ihdr_data.bit_depth.as_u8() * png.channels_per_pixel();
let mut lines: Vec<BitVec> = Vec::with_capacity(png.ihdr_data.height as usize); let bits_per_line = 8 + bits_per_pixel as usize * png.ihdr_data.width as usize;
for _ in 0..png.ihdr_data.height { // Initialize each output line with a starting filter byte of 0
// Initialize each output line with a starting filter byte of 0 // as well as some blank data
// as well as some blank data let mut lines: Vec<BitVec> =
lines.push(BitVec::from_elem(8 + bits_per_pixel as usize * png.ihdr_data.width as usize, vec![BitVec::from_elem(bits_per_line, false); png.ihdr_data.height as usize];
false));
}
let mut current_pass = 1; let mut current_pass = 1;
let mut pass_constants = interlaced_constants(current_pass); let mut pass_constants = interlaced_constants(current_pass);
let mut current_y: usize = pass_constants.y_shift as usize; let mut current_y: usize = pass_constants.y_shift as usize;
@ -1024,6 +1010,7 @@ fn deinterlace_image(png: &mut PngData) {
png.raw_data = output; png.raw_data = output;
} }
#[derive(Clone, Copy)]
struct InterlacedConstants { struct InterlacedConstants {
x_shift: u8, x_shift: u8,
y_shift: u8, y_shift: u8,
@ -1321,15 +1308,11 @@ fn reduce_rgba_to_grayscale_alpha(png: &PngData) -> Option<Vec<u8>> {
} }
if i % bpp == bpp - 1 { if i % bpp == bpp - 1 {
low_bytes.sort(); if low_bytes.iter().unique().count() > 1 {
low_bytes.dedup();
if low_bytes.len() > 1 {
return None; return None;
} }
if byte_depth == 2 { if byte_depth == 2 {
high_bytes.sort(); if high_bytes.iter().unique().count() > 1 {
high_bytes.dedup();
if high_bytes.len() > 1 {
return None; return None;
} }
reduced.push(high_bytes[0]); reduced.push(high_bytes[0]);
@ -1359,8 +1342,7 @@ fn reduce_rgba_to_palette(png: &PngData) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)>
for (i, byte) in line.data.iter().enumerate() { for (i, byte) in line.data.iter().enumerate() {
cur_pixel.push(*byte); cur_pixel.push(*byte);
if i % bpp == bpp - 1 { if i % bpp == bpp - 1 {
if palette.contains(&cur_pixel) { if let Some(idx) = palette.iter().position(|x| x == &cur_pixel) {
let idx = palette.iter().enumerate().find(|&x| x.1 == &cur_pixel).unwrap().0;
reduced.push(idx as u8); reduced.push(idx as u8);
} else { } else {
let len = palette.len(); let len = palette.len();
@ -1403,8 +1385,7 @@ fn reduce_rgb_to_palette(png: &PngData) -> Option<(Vec<u8>, Vec<u8>)> {
for (i, byte) in line.data.iter().enumerate() { for (i, byte) in line.data.iter().enumerate() {
cur_pixel.push(*byte); cur_pixel.push(*byte);
if i % bpp == bpp - 1 { if i % bpp == bpp - 1 {
if palette.contains(&cur_pixel) { if let Some(idx) = palette.iter().position(|x| x == &cur_pixel) {
let idx = palette.iter().enumerate().find(|&x| x.1 == &cur_pixel).unwrap().0;
reduced.push(idx as u8); reduced.push(idx as u8);
} else { } else {
let len = palette.len(); let len = palette.len();
@ -1487,9 +1468,7 @@ fn reduce_palette_to_grayscale(png: &PngData) -> Option<Vec<u8>> {
for byte in &palette { for byte in &palette {
cur_pixel.push(*byte); cur_pixel.push(*byte);
if cur_pixel.len() == 3 { if cur_pixel.len() == 3 {
cur_pixel.sort(); if cur_pixel.iter().unique().count() > 1 {
cur_pixel.dedup();
if cur_pixel.len() > 1 {
return None; return None;
} }
cur_pixel.clear(); cur_pixel.clear();
@ -1536,29 +1515,25 @@ fn reduce_rgb_to_grayscale(png: &PngData) -> Option<Vec<u8>> {
cur_pixel.push(*byte); cur_pixel.push(*byte);
if i % bpp == bpp - 1 { if i % bpp == bpp - 1 {
if bpp == 3 { if bpp == 3 {
cur_pixel.sort(); if cur_pixel.iter().unique().count() > 1 {
cur_pixel.dedup();
if cur_pixel.len() > 1 {
return None; return None;
} }
reduced.push(cur_pixel[0]); reduced.push(cur_pixel[0]);
} else { } else {
let mut pixel_zip = cur_pixel.iter() let pixel_bytes = cur_pixel.iter()
.enumerate() .step(2)
.filter(|&(i, _)| i % 2 == 0) .cloned()
.map(|(_, x)| *x)
.zip(cur_pixel.iter() .zip(cur_pixel.iter()
.enumerate() .skip(1)
.filter(|&(i, _)| i % 2 == 1) .step(2)
.map(|(_, x)| *x)) .cloned())
.unique()
.collect::<Vec<(u8, u8)>>(); .collect::<Vec<(u8, u8)>>();
pixel_zip.sort(); if pixel_bytes.len() > 1 {
pixel_zip.dedup();
if pixel_zip.len() > 1 {
return None; return None;
} }
reduced.push(pixel_zip[0].0); reduced.push(pixel_bytes[0].0);
reduced.push(pixel_zip[0].1); reduced.push(pixel_bytes[0].1);
} }
cur_pixel.clear(); cur_pixel.clear();
} }