Update crates and fix interlacing issue
This commit is contained in:
parent
8439fcf21d
commit
c78e3213d6
6 changed files with 86 additions and 28 deletions
|
|
@ -1,6 +1,8 @@
|
|||
**Version 0.4.0 (unreleased)**
|
||||
- Performance optimizations
|
||||
- [SEMVER_MAJOR] `-s` automatically infers `--strip safe`
|
||||
- Update byteorder and clap crates
|
||||
- Fix issue where interlaced images incorrectly applied filters on the first line of a pass
|
||||
|
||||
**Version 0.3.0**
|
||||
- Properly decode interlaced images
|
||||
|
|
|
|||
10
Cargo.toml
10
Cargo.toml
|
|
@ -22,14 +22,14 @@ doc = false
|
|||
|
||||
[dependencies]
|
||||
bit-vec = "^0.4.2"
|
||||
byteorder = "^0.4.0"
|
||||
clap = "^1.5.4"
|
||||
crc = "^1.0.0"
|
||||
byteorder = "^0.5.0"
|
||||
clap = "^2.2.5"
|
||||
crc = "^1.2.0"
|
||||
libc = "^0.2.4"
|
||||
libz-sys = "^1.0.0"
|
||||
num_cpus = "^0.2.11"
|
||||
regex = "^0.1.8"
|
||||
regex = "^0.1.63"
|
||||
scoped-pool = "^0.1.8"
|
||||
|
||||
[dev-dependencies]
|
||||
image = "^0.6.1"
|
||||
image = "^0.8.0"
|
||||
|
|
|
|||
|
|
@ -239,7 +239,6 @@ fn main() {
|
|||
|
||||
handle_optimization(matches.values_of("files")
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(PathBuf::from)
|
||||
.collect(),
|
||||
opts);
|
||||
|
|
|
|||
61
src/png.rs
61
src/png.rs
|
|
@ -197,6 +197,11 @@ impl<'a> Iterator for ScanLines<'a> {
|
|||
bits_per_line -= gap;
|
||||
}
|
||||
}
|
||||
let current_pass = if let Some(pass) = self.pass {
|
||||
Some(pass.0)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let bytes_per_line = (bits_per_line as f32 / 8f32).ceil() as usize;
|
||||
self.start = self.end;
|
||||
self.end = self.start + bytes_per_line + 1;
|
||||
|
|
@ -216,6 +221,7 @@ impl<'a> Iterator for ScanLines<'a> {
|
|||
Some(ScanLine {
|
||||
filter: self.png.raw_data[self.start],
|
||||
data: self.png.raw_data[(self.start + 1)..self.end].to_owned(),
|
||||
pass: current_pass,
|
||||
})
|
||||
} else {
|
||||
// Standard, non-interlaced PNG scanlines
|
||||
|
|
@ -228,6 +234,7 @@ impl<'a> Iterator for ScanLines<'a> {
|
|||
Some(ScanLine {
|
||||
filter: self.png.raw_data[self.start],
|
||||
data: self.png.raw_data[(self.start + 1)..self.end].to_owned(),
|
||||
pass: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -240,6 +247,8 @@ pub struct ScanLine {
|
|||
pub filter: u8,
|
||||
/// The byte data for the current scan line, encoded with the filter specified in the `filter` field
|
||||
pub data: Vec<u8>,
|
||||
/// The current pass if the image is interlaced
|
||||
pub pass: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
|
|
@ -456,34 +465,42 @@ impl PngData {
|
|||
8f32)
|
||||
.ceil() as usize;
|
||||
let mut last_line: Vec<u8> = Vec::new();
|
||||
let mut last_pass: Option<u8> = None;
|
||||
for line in self.scan_lines() {
|
||||
match filter {
|
||||
0 | 1 | 2 | 3 | 4 => {
|
||||
filtered.push(filter);
|
||||
filtered.extend(filter_line(filter, bpp, &line.data, &last_line));
|
||||
}
|
||||
5 => {
|
||||
// Heuristically guess best filter per line
|
||||
// Uses MSAD algorithm mentioned in libpng reference docs
|
||||
// http://www.libpng.org/pub/png/book/chapter09.html
|
||||
let mut trials: HashMap<u8, Vec<u8>> = HashMap::with_capacity(5);
|
||||
for filter in 0..5 {
|
||||
trials.insert(filter, filter_line(filter, bpp, &line.data, &last_line));
|
||||
if last_pass == line.pass {
|
||||
match filter {
|
||||
0 | 1 | 2 | 3 | 4 => {
|
||||
filtered.push(filter);
|
||||
filtered.extend(filter_line(filter, bpp, &line.data, &last_line));
|
||||
}
|
||||
let (best_filter, best_line) = trials.iter()
|
||||
.min_by_key(|x| {
|
||||
x.1.iter().fold(0u64, |acc, &x| {
|
||||
let signed = x as i8;
|
||||
acc + (signed as i16).abs() as u64
|
||||
5 => {
|
||||
// Heuristically guess best filter per line
|
||||
// Uses MSAD algorithm mentioned in libpng reference docs
|
||||
// http://www.libpng.org/pub/png/book/chapter09.html
|
||||
let mut trials: HashMap<u8, Vec<u8>> = HashMap::with_capacity(5);
|
||||
for filter in 0..5 {
|
||||
trials.insert(filter, filter_line(filter, bpp, &line.data, &last_line));
|
||||
}
|
||||
let (best_filter, best_line) = trials.iter()
|
||||
.min_by_key(|x| {
|
||||
x.1.iter().fold(0u64, |acc, &x| {
|
||||
let signed = x as i8;
|
||||
acc +
|
||||
(signed as i16).abs() as u64
|
||||
})
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
filtered.push(*best_filter);
|
||||
filtered.extend_from_slice(best_line);
|
||||
.unwrap();
|
||||
filtered.push(*best_filter);
|
||||
filtered.extend_from_slice(best_line);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
_ => unreachable!(),
|
||||
} else {
|
||||
filtered.push(0);
|
||||
filtered.extend(filter_line(0, bpp, &line.data, &last_line));
|
||||
}
|
||||
last_line = line.data.clone();
|
||||
last_pass = line.pass;
|
||||
}
|
||||
filtered
|
||||
}
|
||||
|
|
|
|||
BIN
tests/files/interlaced_0_to_1_other_filter_mode.png
Normal file
BIN
tests/files/interlaced_0_to_1_other_filter_mode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
|
|
@ -423,3 +423,43 @@ fn interlacing_1_to_0_small_files() {
|
|||
|
||||
remove_file(output).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interlaced_0_to_1_other_filter_mode() {
|
||||
let input = PathBuf::from("tests/files/interlaced_0_to_1_other_filter_mode.png");
|
||||
let mut opts = get_opts(&input);
|
||||
opts.interlace = Some(1);
|
||||
let mut filter = HashSet::new();
|
||||
filter.insert(4);
|
||||
opts.filter = filter;
|
||||
let output = opts.out_file.clone();
|
||||
|
||||
let png = png::PngData::new(&input).unwrap();
|
||||
|
||||
assert!(png.ihdr_data.interlaced == 0);
|
||||
|
||||
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.interlaced == 1);
|
||||
|
||||
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::<Vec<Vec<u8>>>() ==
|
||||
new_png.pixels().map(|x| x.2.channels().to_owned()).collect::<Vec<Vec<u8>>>());
|
||||
|
||||
remove_file(output).ok();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue