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)**
|
**Version 0.4.0 (unreleased)**
|
||||||
- Performance optimizations
|
- Performance optimizations
|
||||||
- [SEMVER_MAJOR] `-s` automatically infers `--strip safe`
|
- [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**
|
**Version 0.3.0**
|
||||||
- Properly decode interlaced images
|
- Properly decode interlaced images
|
||||||
|
|
|
||||||
10
Cargo.toml
10
Cargo.toml
|
|
@ -22,14 +22,14 @@ doc = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bit-vec = "^0.4.2"
|
bit-vec = "^0.4.2"
|
||||||
byteorder = "^0.4.0"
|
byteorder = "^0.5.0"
|
||||||
clap = "^1.5.4"
|
clap = "^2.2.5"
|
||||||
crc = "^1.0.0"
|
crc = "^1.2.0"
|
||||||
libc = "^0.2.4"
|
libc = "^0.2.4"
|
||||||
libz-sys = "^1.0.0"
|
libz-sys = "^1.0.0"
|
||||||
num_cpus = "^0.2.11"
|
num_cpus = "^0.2.11"
|
||||||
regex = "^0.1.8"
|
regex = "^0.1.63"
|
||||||
scoped-pool = "^0.1.8"
|
scoped-pool = "^0.1.8"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
image = "^0.6.1"
|
image = "^0.8.0"
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,6 @@ fn main() {
|
||||||
|
|
||||||
handle_optimization(matches.values_of("files")
|
handle_optimization(matches.values_of("files")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
.collect(),
|
.collect(),
|
||||||
opts);
|
opts);
|
||||||
|
|
|
||||||
61
src/png.rs
61
src/png.rs
|
|
@ -197,6 +197,11 @@ impl<'a> Iterator for ScanLines<'a> {
|
||||||
bits_per_line -= gap;
|
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;
|
let bytes_per_line = (bits_per_line as f32 / 8f32).ceil() as usize;
|
||||||
self.start = self.end;
|
self.start = self.end;
|
||||||
self.end = self.start + bytes_per_line + 1;
|
self.end = self.start + bytes_per_line + 1;
|
||||||
|
|
@ -216,6 +221,7 @@ impl<'a> Iterator for ScanLines<'a> {
|
||||||
Some(ScanLine {
|
Some(ScanLine {
|
||||||
filter: self.png.raw_data[self.start],
|
filter: self.png.raw_data[self.start],
|
||||||
data: self.png.raw_data[(self.start + 1)..self.end].to_owned(),
|
data: self.png.raw_data[(self.start + 1)..self.end].to_owned(),
|
||||||
|
pass: current_pass,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Standard, non-interlaced PNG scanlines
|
// Standard, non-interlaced PNG scanlines
|
||||||
|
|
@ -228,6 +234,7 @@ impl<'a> Iterator for ScanLines<'a> {
|
||||||
Some(ScanLine {
|
Some(ScanLine {
|
||||||
filter: self.png.raw_data[self.start],
|
filter: self.png.raw_data[self.start],
|
||||||
data: self.png.raw_data[(self.start + 1)..self.end].to_owned(),
|
data: self.png.raw_data[(self.start + 1)..self.end].to_owned(),
|
||||||
|
pass: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -240,6 +247,8 @@ pub struct ScanLine {
|
||||||
pub filter: u8,
|
pub filter: u8,
|
||||||
/// The byte data for the current scan line, encoded with the filter specified in the `filter` field
|
/// The byte data for the current scan line, encoded with the filter specified in the `filter` field
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
|
/// The current pass if the image is interlaced
|
||||||
|
pub pass: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug,Clone)]
|
||||||
|
|
@ -456,34 +465,42 @@ impl PngData {
|
||||||
8f32)
|
8f32)
|
||||||
.ceil() as usize;
|
.ceil() as usize;
|
||||||
let mut last_line: Vec<u8> = Vec::new();
|
let mut last_line: Vec<u8> = Vec::new();
|
||||||
|
let mut last_pass: Option<u8> = None;
|
||||||
for line in self.scan_lines() {
|
for line in self.scan_lines() {
|
||||||
match filter {
|
if last_pass == line.pass {
|
||||||
0 | 1 | 2 | 3 | 4 => {
|
match filter {
|
||||||
filtered.push(filter);
|
0 | 1 | 2 | 3 | 4 => {
|
||||||
filtered.extend(filter_line(filter, bpp, &line.data, &last_line));
|
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));
|
|
||||||
}
|
}
|
||||||
let (best_filter, best_line) = trials.iter()
|
5 => {
|
||||||
.min_by_key(|x| {
|
// Heuristically guess best filter per line
|
||||||
x.1.iter().fold(0u64, |acc, &x| {
|
// Uses MSAD algorithm mentioned in libpng reference docs
|
||||||
let signed = x as i8;
|
// http://www.libpng.org/pub/png/book/chapter09.html
|
||||||
acc + (signed as i16).abs() as u64
|
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();
|
||||||
.unwrap();
|
filtered.push(*best_filter);
|
||||||
filtered.push(*best_filter);
|
filtered.extend_from_slice(best_line);
|
||||||
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_line = line.data.clone();
|
||||||
|
last_pass = line.pass;
|
||||||
}
|
}
|
||||||
filtered
|
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();
|
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