Fix issue for interlacing images smaller than 4px

Fixes #42
This commit is contained in:
Joshua Holmer 2016-05-25 23:10:44 -04:00
parent aff99f2f7c
commit 2659776bd5
8 changed files with 307 additions and 258 deletions

View file

@ -1,3 +1,6 @@
**Version 0.8.2**
- Fix issue where images smaller than 4px width would crash on interlacing ([#42](https://github.com/shssoichiro/oxipng/issues/42))
**Version 0.8.1** **Version 0.8.1**
- Minor optimizations - Minor optimizations
- Fix issue where interlaced images with certain widths would fail to optimize - Fix issue where interlaced images with certain widths would fail to optimize

2
Cargo.lock generated
View file

@ -1,6 +1,6 @@
[root] [root]
name = "oxipng" name = "oxipng"
version = "0.8.0" version = "0.8.2"
dependencies = [ dependencies = [
"bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "oxipng" name = "oxipng"
version = "0.8.1" version = "0.8.2"
authors = ["Joshua Holmer <jholmer.in@gmail.com>"] authors = ["Joshua Holmer <jholmer.in@gmail.com>"]
description = "A lossless PNG compression optimizer" description = "A lossless PNG compression optimizer"
license = "MIT" license = "MIT"

View file

@ -388,11 +388,8 @@ fn optimize_png(mut png: &mut png::PngData, file_original_size: usize, opts: &Op
let filtered = filters.get(&trial.0).unwrap(); let filtered = filters.get(&trial.0).unwrap();
let best = best.clone(); let best = best.clone();
scope.execute(move || { scope.execute(move || {
let new_idat = deflate::deflate::deflate(filtered, let new_idat =
trial.1, deflate::deflate::deflate(filtered, trial.1, trial.2, trial.3, opts.window)
trial.2,
trial.3,
opts.window)
.unwrap(); .unwrap();
if opts.verbosity == Some(1) { if opts.verbosity == Some(1) {

View file

@ -10,11 +10,10 @@ use std::fs::DirBuilder;
use std::io::{Write, stderr}; use std::io::{Write, stderr};
use std::path::PathBuf; use std::path::PathBuf;
const VERSION_STRING: &'static str = "0.8.1"; const VERSION_STRING: &'static str = "0.8.2";
fn main() { fn main() {
let matches = let matches = App::new("oxipng")
App::new("oxipng")
.version(VERSION_STRING) .version(VERSION_STRING)
.author("Joshua Holmer <jholmer.in@gmail.com>") .author("Joshua Holmer <jholmer.in@gmail.com>")
.about("Losslessly improves compression of PNG files") .about("Losslessly improves compression of PNG files")

View file

@ -416,9 +416,9 @@ impl PngData {
ihdr_data.write_u8(self.ihdr_data.interlaced).ok(); ihdr_data.write_u8(self.ihdr_data.interlaced).ok();
write_png_block(b"IHDR", &ihdr_data, &mut output); write_png_block(b"IHDR", &ihdr_data, &mut output);
// Ancillary headers // Ancillary headers
for (key, header) in self.aux_headers.iter().filter(|&(ref key, _)| { for (key, header) in self.aux_headers
!(**key == "bKGD" || **key == "hIST" || **key == "tRNS") .iter()
}) { .filter(|&(ref key, _)| !(**key == "bKGD" || **key == "hIST" || **key == "tRNS")) {
write_png_block(key.as_bytes(), header, &mut output); write_png_block(key.as_bytes(), header, &mut output);
} }
// Palette // Palette
@ -433,9 +433,9 @@ impl PngData {
write_png_block(b"tRNS", &transparency_pixel, &mut output); write_png_block(b"tRNS", &transparency_pixel, &mut output);
} }
// Special ancillary headers that need to come after PLTE but before IDAT // Special ancillary headers that need to come after PLTE but before IDAT
for (key, header) in self.aux_headers.iter().filter(|&(ref key, _)| { for (key, header) in self.aux_headers
**key == "bKGD" || **key == "hIST" || **key == "tRNS" .iter()
}) { .filter(|&(ref key, _)| **key == "bKGD" || **key == "hIST" || **key == "tRNS") {
write_png_block(key.as_bytes(), header, &mut output); write_png_block(key.as_bytes(), header, &mut output);
} }
// IDAT data // IDAT data
@ -548,10 +548,10 @@ impl PngData {
} }
// Reduce from 16 to 8 bits per channel per pixel // Reduce from 16 to 8 bits per channel per pixel
let mut reduced = let mut reduced = Vec::with_capacity((self.ihdr_data.width * self.ihdr_data.height *
Vec::with_capacity((self.ihdr_data.width * self.ihdr_data.height *
self.channels_per_pixel() as u32 + self.channels_per_pixel() as u32 +
self.ihdr_data.height) as usize); self.ihdr_data
.height) as usize);
let mut high_byte = 0; let mut high_byte = 0;
for line in self.scan_lines() { for line in self.scan_lines() {
@ -888,24 +888,34 @@ fn interlace_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();
for (index, line) in png.scan_lines().enumerate() { for (index, line) in png.scan_lines().enumerate() {
match index % 8 { match index % 8 {
// Add filter bytes to appropriate lines // Add filter bytes to passes that will be in the output image
0 => { 0 => {
passes[0].extend(BitVec::from_elem(8, false)); passes[0].extend(BitVec::from_elem(8, false));
passes[3].extend(BitVec::from_elem(8, false)); if png.ihdr_data.width >= 5 {
passes[5].extend(BitVec::from_elem(8, false));
if png.ihdr_data.width > 4 {
passes[1].extend(BitVec::from_elem(8, false)); passes[1].extend(BitVec::from_elem(8, false));
} }
if png.ihdr_data.width >= 3 {
passes[3].extend(BitVec::from_elem(8, false));
}
if png.ihdr_data.width >= 2 {
passes[5].extend(BitVec::from_elem(8, false));
}
} }
4 => { 4 => {
passes[3].extend(BitVec::from_elem(8, false));
passes[5].extend(BitVec::from_elem(8, false));
passes[2].extend(BitVec::from_elem(8, false)); passes[2].extend(BitVec::from_elem(8, false));
if png.ihdr_data.width >= 3 {
passes[3].extend(BitVec::from_elem(8, false));
}
if png.ihdr_data.width >= 2 {
passes[5].extend(BitVec::from_elem(8, false));
}
} }
2 | 6 => { 2 | 6 => {
passes[4].extend(BitVec::from_elem(8, false)); passes[4].extend(BitVec::from_elem(8, false));
if png.ihdr_data.width >= 2 {
passes[5].extend(BitVec::from_elem(8, false)); passes[5].extend(BitVec::from_elem(8, false));
} }
}
_ => { _ => {
passes[6].extend(BitVec::from_elem(8, false)); passes[6].extend(BitVec::from_elem(8, false));
} }
@ -975,8 +985,7 @@ fn deinterlace_image(png: &mut PngData) {
let bit_vec = BitVec::from_bytes(&line.data); let bit_vec = BitVec::from_bytes(&line.data);
let bits_in_line = ((png.ihdr_data.width - pass_constants.x_shift as u32) as f32 / let bits_in_line = ((png.ihdr_data.width - pass_constants.x_shift as u32) as f32 /
pass_constants.x_step as f32) pass_constants.x_step as f32)
.ceil() as usize * .ceil() as usize * bits_per_pixel as usize;
bits_per_pixel as usize;
for (i, bit) in bit_vec.iter().enumerate() { for (i, bit) in bit_vec.iter().enumerate() {
// Avoid moving padded 0's into new image // Avoid moving padded 0's into new image
if i >= bits_in_line { if i >= bits_in_line {

BIN
tests/files/issue_42.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View file

@ -486,3 +486,44 @@ fn fix_errors() {
// Cannot check if pixels are equal because image crate cannot read corrupt (input) PNGs // Cannot check if pixels are equal because image crate cannot read corrupt (input) PNGs
remove_file(output).ok(); remove_file(output).ok();
} }
#[test]
fn issue_42() {
let input = PathBuf::from("tests/files/issue_42.png");
let mut opts = get_opts(&input);
opts.interlace = Some(1);
let output = opts.out_file.clone();
let png = png::PngData::new(&input, opts.fix_errors).unwrap();
assert_eq!(png.ihdr_data.interlaced, 0);
assert_eq!(png.ihdr_data.color_type, png::ColorType::GrayscaleAlpha);
assert_eq!(png.ihdr_data.bit_depth, png::BitDepth::Eight);
match oxipng::optimize(&input, &opts) {
Ok(_) => (),
Err(x) => panic!(x.to_owned()),
};
assert!(output.exists());
let png = match png::PngData::new(&output, opts.fix_errors) {
Ok(x) => x,
Err(x) => {
remove_file(output).ok();
panic!(x.to_owned())
}
};
assert_eq!(png.ihdr_data.interlaced, 1);
assert_eq!(png.ihdr_data.color_type, png::ColorType::GrayscaleAlpha);
assert_eq!(png.ihdr_data.bit_depth, png::BitDepth::Eight);
let old_png = image::open(&input).unwrap();
let new_png = image::open(&output).unwrap();
// Conversion should be lossless
assert_eq!(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();
}