parent
aff99f2f7c
commit
2659776bd5
8 changed files with 307 additions and 258 deletions
|
|
@ -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**
|
||||
- Minor optimizations
|
||||
- Fix issue where interlaced images with certain widths would fail to optimize
|
||||
|
|
|
|||
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1,6 +1,6 @@
|
|||
[root]
|
||||
name = "oxipng"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"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)",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "oxipng"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
authors = ["Joshua Holmer <jholmer.in@gmail.com>"]
|
||||
description = "A lossless PNG compression optimizer"
|
||||
license = "MIT"
|
||||
|
|
|
|||
19
src/lib.rs
19
src/lib.rs
|
|
@ -185,9 +185,9 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
|
|||
match copy(in_file,
|
||||
in_file.with_extension(format!("bak.{}",
|
||||
in_file.extension()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()))) {
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()))) {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return Err(format!("Unable to write to backup file at {}",
|
||||
|
|
@ -222,9 +222,9 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
|
|||
match out_file.metadata() {
|
||||
Ok(out_meta) => {
|
||||
let readonly = metadata.permissions()
|
||||
.readonly();
|
||||
.readonly();
|
||||
out_meta.permissions()
|
||||
.set_readonly(readonly);
|
||||
.set_readonly(readonly);
|
||||
}
|
||||
Err(_) => {
|
||||
if opts.verbosity.is_some() {
|
||||
|
|
@ -388,12 +388,9 @@ fn optimize_png(mut png: &mut png::PngData, file_original_size: usize, opts: &Op
|
|||
let filtered = filters.get(&trial.0).unwrap();
|
||||
let best = best.clone();
|
||||
scope.execute(move || {
|
||||
let new_idat = deflate::deflate::deflate(filtered,
|
||||
trial.1,
|
||||
trial.2,
|
||||
trial.3,
|
||||
opts.window)
|
||||
.unwrap();
|
||||
let new_idat =
|
||||
deflate::deflate::deflate(filtered, trial.1, trial.2, trial.3, opts.window)
|
||||
.unwrap();
|
||||
|
||||
if opts.verbosity == Some(1) {
|
||||
writeln!(&mut stderr(),
|
||||
|
|
|
|||
373
src/main.rs
373
src/main.rs
|
|
@ -10,186 +10,185 @@ use std::fs::DirBuilder;
|
|||
use std::io::{Write, stderr};
|
||||
use std::path::PathBuf;
|
||||
|
||||
const VERSION_STRING: &'static str = "0.8.1";
|
||||
const VERSION_STRING: &'static str = "0.8.2";
|
||||
|
||||
fn main() {
|
||||
let matches =
|
||||
App::new("oxipng")
|
||||
.version(VERSION_STRING)
|
||||
.author("Joshua Holmer <jholmer.in@gmail.com>")
|
||||
.about("Losslessly improves compression of PNG files")
|
||||
.arg(Arg::with_name("files")
|
||||
.help("File(s) to compress")
|
||||
.index(1)
|
||||
.multiple(true)
|
||||
.required(true))
|
||||
.arg(Arg::with_name("optimization")
|
||||
.help("Optimization level - Default: 2")
|
||||
.short("o")
|
||||
.long("opt")
|
||||
.takes_value(true)
|
||||
.possible_value("0")
|
||||
.possible_value("1")
|
||||
.possible_value("2")
|
||||
.possible_value("3")
|
||||
.possible_value("4")
|
||||
.possible_value("5")
|
||||
.possible_value("6"))
|
||||
.arg(Arg::with_name("backup")
|
||||
.help("Back up modified files")
|
||||
.short("b")
|
||||
.long("backup"))
|
||||
.arg(Arg::with_name("force")
|
||||
.help("Write output even if larger than the original")
|
||||
.short("F")
|
||||
.long("force"))
|
||||
.arg(Arg::with_name("recursive")
|
||||
.help("Recurse into subdirectories")
|
||||
.short("r")
|
||||
.long("recursive"))
|
||||
.arg(Arg::with_name("output_dir")
|
||||
.help("Write output file(s) to <directory>")
|
||||
.long("dir")
|
||||
.takes_value(true)
|
||||
.conflicts_with("output_file")
|
||||
.conflicts_with("stdout"))
|
||||
.arg(Arg::with_name("output_file")
|
||||
.help("Write output file to <file>")
|
||||
.long("out")
|
||||
.takes_value(true)
|
||||
.conflicts_with("output_dir")
|
||||
.conflicts_with("stdout"))
|
||||
.arg(Arg::with_name("stdout")
|
||||
.help("Write output to stdout")
|
||||
.long("stdout")
|
||||
.conflicts_with("output_dir")
|
||||
.conflicts_with("output_file"))
|
||||
.arg(Arg::with_name("fix")
|
||||
.help("Enable error recovery")
|
||||
.long("fix"))
|
||||
.arg(Arg::with_name("no-clobber")
|
||||
.help("Do not overwrite existing files")
|
||||
.long("no-clobber"))
|
||||
.arg(Arg::with_name("pretend")
|
||||
.help("Do not write any files, only calculate compression gains")
|
||||
.short("P")
|
||||
.long("pretend"))
|
||||
.arg(Arg::with_name("preserve")
|
||||
.help("Preserve file attributes if possible")
|
||||
.short("p")
|
||||
.long("preserve"))
|
||||
.arg(Arg::with_name("quiet")
|
||||
.help("Run in quiet mode")
|
||||
.short("q")
|
||||
.long("quiet")
|
||||
.conflicts_with("verbose"))
|
||||
.arg(Arg::with_name("verbose")
|
||||
.help("Run in verbose mode")
|
||||
.short("v")
|
||||
.long("verbose")
|
||||
.conflicts_with("quiet"))
|
||||
.arg(Arg::with_name("filters")
|
||||
.help("PNG delta filters (0-5) - Default: 0,5")
|
||||
.short("f")
|
||||
.long("filters")
|
||||
.takes_value(true)
|
||||
.validator(|x| {
|
||||
match parse_numeric_range_opts(&x, 0, 5) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err("Invalid option for filters".to_owned()),
|
||||
}
|
||||
}))
|
||||
.arg(Arg::with_name("interlace")
|
||||
.help("PNG interlace type")
|
||||
.short("i")
|
||||
.long("interlace")
|
||||
.takes_value(true)
|
||||
.possible_value("0")
|
||||
.possible_value("1"))
|
||||
.arg(Arg::with_name("compression")
|
||||
.help("zlib compression levels (1-9) - Default: 9")
|
||||
.long("zc")
|
||||
.takes_value(true)
|
||||
.validator(|x| {
|
||||
match parse_numeric_range_opts(&x, 1, 9) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err("Invalid option for compression".to_owned()),
|
||||
}
|
||||
}))
|
||||
.arg(Arg::with_name("memory")
|
||||
.help("zlib memory levels (1-9) - Default: 9")
|
||||
.long("zm")
|
||||
.takes_value(true)
|
||||
.validator(|x| {
|
||||
match parse_numeric_range_opts(&x, 1, 9) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err("Invalid option for memory".to_owned()),
|
||||
}
|
||||
}))
|
||||
.arg(Arg::with_name("strategies")
|
||||
.help("zlib compression strategies (0-3) - Default: 0-3")
|
||||
.long("zs")
|
||||
.takes_value(true)
|
||||
.validator(|x| {
|
||||
match parse_numeric_range_opts(&x, 0, 3) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err("Invalid option for strategies".to_owned()),
|
||||
}
|
||||
}))
|
||||
.arg(Arg::with_name("window")
|
||||
.help("zlib window size - Default: 32k")
|
||||
.long("zw")
|
||||
.takes_value(true)
|
||||
.possible_value("256")
|
||||
.possible_value("512")
|
||||
.possible_value("1k")
|
||||
.possible_value("2k")
|
||||
.possible_value("4k")
|
||||
.possible_value("8k")
|
||||
.possible_value("16k")
|
||||
.possible_value("32k"))
|
||||
.arg(Arg::with_name("no-bit-reduction")
|
||||
.help("No bit depth reduction")
|
||||
.long("nb"))
|
||||
.arg(Arg::with_name("no-color-reduction")
|
||||
.help("No color type reduction")
|
||||
.long("nc"))
|
||||
.arg(Arg::with_name("no-palette-reduction")
|
||||
.help("No palette reduction")
|
||||
.long("np"))
|
||||
.arg(Arg::with_name("no-reductions")
|
||||
.help("No reductions")
|
||||
.long("nx"))
|
||||
.arg(Arg::with_name("no-recoding")
|
||||
.help("No IDAT recoding unless necessary")
|
||||
.long("nz"))
|
||||
.arg(Arg::with_name("strip")
|
||||
.help("Strip metadata objects ['safe', 'all', or comma-separated list]")
|
||||
.long("strip")
|
||||
.takes_value(true)
|
||||
.conflicts_with("strip-safe"))
|
||||
.arg(Arg::with_name("strip-safe")
|
||||
.help("Strip safely-removable metadata objects")
|
||||
.short("s")
|
||||
.conflicts_with("strip"))
|
||||
.arg(Arg::with_name("threads")
|
||||
.help("Set number of threads to use - default 1.5x CPU cores")
|
||||
.long("threads")
|
||||
.short("t")
|
||||
.takes_value(true)
|
||||
.validator(|x| {
|
||||
match x.parse::<usize>() {
|
||||
Ok(val) => {
|
||||
if val > 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Thread count must be >= 1".to_owned())
|
||||
}
|
||||
}
|
||||
Err(_) => Err("Thread count must be >= 1".to_owned()),
|
||||
}
|
||||
}))
|
||||
.after_help("Optimization levels:
|
||||
let matches = App::new("oxipng")
|
||||
.version(VERSION_STRING)
|
||||
.author("Joshua Holmer <jholmer.in@gmail.com>")
|
||||
.about("Losslessly improves compression of PNG files")
|
||||
.arg(Arg::with_name("files")
|
||||
.help("File(s) to compress")
|
||||
.index(1)
|
||||
.multiple(true)
|
||||
.required(true))
|
||||
.arg(Arg::with_name("optimization")
|
||||
.help("Optimization level - Default: 2")
|
||||
.short("o")
|
||||
.long("opt")
|
||||
.takes_value(true)
|
||||
.possible_value("0")
|
||||
.possible_value("1")
|
||||
.possible_value("2")
|
||||
.possible_value("3")
|
||||
.possible_value("4")
|
||||
.possible_value("5")
|
||||
.possible_value("6"))
|
||||
.arg(Arg::with_name("backup")
|
||||
.help("Back up modified files")
|
||||
.short("b")
|
||||
.long("backup"))
|
||||
.arg(Arg::with_name("force")
|
||||
.help("Write output even if larger than the original")
|
||||
.short("F")
|
||||
.long("force"))
|
||||
.arg(Arg::with_name("recursive")
|
||||
.help("Recurse into subdirectories")
|
||||
.short("r")
|
||||
.long("recursive"))
|
||||
.arg(Arg::with_name("output_dir")
|
||||
.help("Write output file(s) to <directory>")
|
||||
.long("dir")
|
||||
.takes_value(true)
|
||||
.conflicts_with("output_file")
|
||||
.conflicts_with("stdout"))
|
||||
.arg(Arg::with_name("output_file")
|
||||
.help("Write output file to <file>")
|
||||
.long("out")
|
||||
.takes_value(true)
|
||||
.conflicts_with("output_dir")
|
||||
.conflicts_with("stdout"))
|
||||
.arg(Arg::with_name("stdout")
|
||||
.help("Write output to stdout")
|
||||
.long("stdout")
|
||||
.conflicts_with("output_dir")
|
||||
.conflicts_with("output_file"))
|
||||
.arg(Arg::with_name("fix")
|
||||
.help("Enable error recovery")
|
||||
.long("fix"))
|
||||
.arg(Arg::with_name("no-clobber")
|
||||
.help("Do not overwrite existing files")
|
||||
.long("no-clobber"))
|
||||
.arg(Arg::with_name("pretend")
|
||||
.help("Do not write any files, only calculate compression gains")
|
||||
.short("P")
|
||||
.long("pretend"))
|
||||
.arg(Arg::with_name("preserve")
|
||||
.help("Preserve file attributes if possible")
|
||||
.short("p")
|
||||
.long("preserve"))
|
||||
.arg(Arg::with_name("quiet")
|
||||
.help("Run in quiet mode")
|
||||
.short("q")
|
||||
.long("quiet")
|
||||
.conflicts_with("verbose"))
|
||||
.arg(Arg::with_name("verbose")
|
||||
.help("Run in verbose mode")
|
||||
.short("v")
|
||||
.long("verbose")
|
||||
.conflicts_with("quiet"))
|
||||
.arg(Arg::with_name("filters")
|
||||
.help("PNG delta filters (0-5) - Default: 0,5")
|
||||
.short("f")
|
||||
.long("filters")
|
||||
.takes_value(true)
|
||||
.validator(|x| {
|
||||
match parse_numeric_range_opts(&x, 0, 5) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err("Invalid option for filters".to_owned()),
|
||||
}
|
||||
}))
|
||||
.arg(Arg::with_name("interlace")
|
||||
.help("PNG interlace type")
|
||||
.short("i")
|
||||
.long("interlace")
|
||||
.takes_value(true)
|
||||
.possible_value("0")
|
||||
.possible_value("1"))
|
||||
.arg(Arg::with_name("compression")
|
||||
.help("zlib compression levels (1-9) - Default: 9")
|
||||
.long("zc")
|
||||
.takes_value(true)
|
||||
.validator(|x| {
|
||||
match parse_numeric_range_opts(&x, 1, 9) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err("Invalid option for compression".to_owned()),
|
||||
}
|
||||
}))
|
||||
.arg(Arg::with_name("memory")
|
||||
.help("zlib memory levels (1-9) - Default: 9")
|
||||
.long("zm")
|
||||
.takes_value(true)
|
||||
.validator(|x| {
|
||||
match parse_numeric_range_opts(&x, 1, 9) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err("Invalid option for memory".to_owned()),
|
||||
}
|
||||
}))
|
||||
.arg(Arg::with_name("strategies")
|
||||
.help("zlib compression strategies (0-3) - Default: 0-3")
|
||||
.long("zs")
|
||||
.takes_value(true)
|
||||
.validator(|x| {
|
||||
match parse_numeric_range_opts(&x, 0, 3) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err("Invalid option for strategies".to_owned()),
|
||||
}
|
||||
}))
|
||||
.arg(Arg::with_name("window")
|
||||
.help("zlib window size - Default: 32k")
|
||||
.long("zw")
|
||||
.takes_value(true)
|
||||
.possible_value("256")
|
||||
.possible_value("512")
|
||||
.possible_value("1k")
|
||||
.possible_value("2k")
|
||||
.possible_value("4k")
|
||||
.possible_value("8k")
|
||||
.possible_value("16k")
|
||||
.possible_value("32k"))
|
||||
.arg(Arg::with_name("no-bit-reduction")
|
||||
.help("No bit depth reduction")
|
||||
.long("nb"))
|
||||
.arg(Arg::with_name("no-color-reduction")
|
||||
.help("No color type reduction")
|
||||
.long("nc"))
|
||||
.arg(Arg::with_name("no-palette-reduction")
|
||||
.help("No palette reduction")
|
||||
.long("np"))
|
||||
.arg(Arg::with_name("no-reductions")
|
||||
.help("No reductions")
|
||||
.long("nx"))
|
||||
.arg(Arg::with_name("no-recoding")
|
||||
.help("No IDAT recoding unless necessary")
|
||||
.long("nz"))
|
||||
.arg(Arg::with_name("strip")
|
||||
.help("Strip metadata objects ['safe', 'all', or comma-separated list]")
|
||||
.long("strip")
|
||||
.takes_value(true)
|
||||
.conflicts_with("strip-safe"))
|
||||
.arg(Arg::with_name("strip-safe")
|
||||
.help("Strip safely-removable metadata objects")
|
||||
.short("s")
|
||||
.conflicts_with("strip"))
|
||||
.arg(Arg::with_name("threads")
|
||||
.help("Set number of threads to use - default 1.5x CPU cores")
|
||||
.long("threads")
|
||||
.short("t")
|
||||
.takes_value(true)
|
||||
.validator(|x| {
|
||||
match x.parse::<usize>() {
|
||||
Ok(val) => {
|
||||
if val > 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Thread count must be >= 1".to_owned())
|
||||
}
|
||||
}
|
||||
Err(_) => Err("Thread count must be >= 1".to_owned()),
|
||||
}
|
||||
}))
|
||||
.after_help("Optimization levels:
|
||||
-o 0 => --zc 3 --nz (0 or 1 trials)
|
||||
-o 1 => --zc 9 (1 trial, determined heuristically)
|
||||
-o 2 => --zc 9 --zs 0-3 --f 0,5 (8 trials)
|
||||
|
|
@ -204,7 +203,7 @@ fn main() {
|
|||
|
||||
Manually specifying a compression option (zc, zm, etc.) will override the optimization preset,
|
||||
regardless of the order you write the arguments.")
|
||||
.get_matches();
|
||||
.get_matches();
|
||||
|
||||
let mut opts = oxipng::Options::default();
|
||||
|
||||
|
|
@ -217,9 +216,9 @@ fn main() {
|
|||
}
|
||||
|
||||
handle_optimization(matches.values_of("files")
|
||||
.unwrap()
|
||||
.map(PathBuf::from)
|
||||
.collect(),
|
||||
.unwrap()
|
||||
.map(PathBuf::from)
|
||||
.collect(),
|
||||
opts);
|
||||
}
|
||||
|
||||
|
|
@ -390,8 +389,8 @@ fn parse_opts_into_struct(matches: &ArgMatches, opts: &mut oxipng::Options) -> R
|
|||
let path = PathBuf::from(x);
|
||||
if !path.exists() {
|
||||
match DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(&path) {
|
||||
.recursive(true)
|
||||
.create(&path) {
|
||||
Ok(_) => (),
|
||||
Err(x) => return Err(format!("Could not create output directory {}", x)),
|
||||
};
|
||||
|
|
@ -473,7 +472,7 @@ fn parse_opts_into_struct(matches: &ArgMatches, opts: &mut oxipng::Options) -> R
|
|||
if hdrs.contains(&"safe".to_owned()) || hdrs.contains(&"all".to_owned()) {
|
||||
if hdrs.len() > 1 {
|
||||
return Err("'safe' or 'all' presets for --strip should be used by themselves"
|
||||
.to_owned());
|
||||
.to_owned());
|
||||
}
|
||||
if hdrs[0] == "safe" {
|
||||
opts.strip = png::Headers::Safe;
|
||||
|
|
@ -512,8 +511,8 @@ fn parse_numeric_range_opts(input: &str,
|
|||
max_value,
|
||||
min_value,
|
||||
max_value)
|
||||
.as_ref())
|
||||
.unwrap();
|
||||
.as_ref())
|
||||
.unwrap();
|
||||
let mut items = HashSet::new();
|
||||
|
||||
if one_item.is_match(input) {
|
||||
|
|
|
|||
125
src/png.rs
125
src/png.rs
|
|
@ -416,9 +416,9 @@ impl PngData {
|
|||
ihdr_data.write_u8(self.ihdr_data.interlaced).ok();
|
||||
write_png_block(b"IHDR", &ihdr_data, &mut output);
|
||||
// Ancillary headers
|
||||
for (key, header) in self.aux_headers.iter().filter(|&(ref key, _)| {
|
||||
!(**key == "bKGD" || **key == "hIST" || **key == "tRNS")
|
||||
}) {
|
||||
for (key, header) in self.aux_headers
|
||||
.iter()
|
||||
.filter(|&(ref key, _)| !(**key == "bKGD" || **key == "hIST" || **key == "tRNS")) {
|
||||
write_png_block(key.as_bytes(), header, &mut output);
|
||||
}
|
||||
// Palette
|
||||
|
|
@ -433,9 +433,9 @@ impl PngData {
|
|||
write_png_block(b"tRNS", &transparency_pixel, &mut output);
|
||||
}
|
||||
// Special ancillary headers that need to come after PLTE but before IDAT
|
||||
for (key, header) in self.aux_headers.iter().filter(|&(ref key, _)| {
|
||||
**key == "bKGD" || **key == "hIST" || **key == "tRNS"
|
||||
}) {
|
||||
for (key, header) in self.aux_headers
|
||||
.iter()
|
||||
.filter(|&(ref key, _)| **key == "bKGD" || **key == "hIST" || **key == "tRNS") {
|
||||
write_png_block(key.as_bytes(), header, &mut output);
|
||||
}
|
||||
// IDAT data
|
||||
|
|
@ -459,7 +459,7 @@ impl PngData {
|
|||
let mut unfiltered = Vec::with_capacity(self.raw_data.len());
|
||||
let bpp = (((self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel()) as f32) /
|
||||
8f32)
|
||||
.ceil() as usize;
|
||||
.ceil() as usize;
|
||||
let mut last_line: Vec<u8> = Vec::new();
|
||||
for line in self.scan_lines() {
|
||||
let unfiltered_line = unfilter_line(line.filter, bpp, &line.data, &last_line);
|
||||
|
|
@ -480,7 +480,7 @@ impl PngData {
|
|||
let mut filtered = Vec::with_capacity(self.raw_data.len());
|
||||
let bpp = (((self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel()) as f32) /
|
||||
8f32)
|
||||
.ceil() as usize;
|
||||
.ceil() as usize;
|
||||
let mut last_line: Vec<u8> = Vec::new();
|
||||
let mut last_pass: Option<u8> = None;
|
||||
for line in self.scan_lines() {
|
||||
|
|
@ -512,13 +512,13 @@ impl PngData {
|
|||
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();
|
||||
.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);
|
||||
}
|
||||
|
|
@ -548,10 +548,10 @@ impl PngData {
|
|||
}
|
||||
|
||||
// Reduce from 16 to 8 bits per channel per pixel
|
||||
let mut reduced =
|
||||
Vec::with_capacity((self.ihdr_data.width * self.ihdr_data.height *
|
||||
self.channels_per_pixel() as u32 +
|
||||
self.ihdr_data.height) as usize);
|
||||
let mut reduced = Vec::with_capacity((self.ihdr_data.width * self.ihdr_data.height *
|
||||
self.channels_per_pixel() as u32 +
|
||||
self.ihdr_data
|
||||
.height) as usize);
|
||||
let mut high_byte = 0;
|
||||
|
||||
for line in self.scan_lines() {
|
||||
|
|
@ -888,23 +888,33 @@ fn interlace_image(png: &mut PngData) {
|
|||
let bits_per_pixel = png.ihdr_data.bit_depth.as_u8() * png.channels_per_pixel();
|
||||
for (index, line) in png.scan_lines().enumerate() {
|
||||
match index % 8 {
|
||||
// Add filter bytes to appropriate lines
|
||||
// Add filter bytes to passes that will be in the output image
|
||||
0 => {
|
||||
passes[0].extend(BitVec::from_elem(8, false));
|
||||
passes[3].extend(BitVec::from_elem(8, false));
|
||||
passes[5].extend(BitVec::from_elem(8, false));
|
||||
if png.ihdr_data.width > 4 {
|
||||
if png.ihdr_data.width >= 5 {
|
||||
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 => {
|
||||
passes[3].extend(BitVec::from_elem(8, false));
|
||||
passes[5].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 => {
|
||||
passes[4].extend(BitVec::from_elem(8, false));
|
||||
passes[5].extend(BitVec::from_elem(8, false));
|
||||
if png.ihdr_data.width >= 2 {
|
||||
passes[5].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 bits_in_line = ((png.ihdr_data.width - pass_constants.x_shift as u32) as f32 /
|
||||
pass_constants.x_step as f32)
|
||||
.ceil() as usize *
|
||||
bits_per_pixel as usize;
|
||||
.ceil() as usize * bits_per_pixel as usize;
|
||||
for (i, bit) in bit_vec.iter().enumerate() {
|
||||
// Avoid moving padded 0's into new image
|
||||
if i >= bits_in_line {
|
||||
|
|
@ -1093,19 +1102,19 @@ fn filter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> Vec<u8>
|
|||
1 => {
|
||||
filtered.extend_from_slice(&data[0..bpp]);
|
||||
filtered.extend_from_slice(&data.iter()
|
||||
.skip(bpp)
|
||||
.zip(data.iter())
|
||||
.map(|(cur, last)| cur.wrapping_sub(*last))
|
||||
.collect::<Vec<u8>>());
|
||||
.skip(bpp)
|
||||
.zip(data.iter())
|
||||
.map(|(cur, last)| cur.wrapping_sub(*last))
|
||||
.collect::<Vec<u8>>());
|
||||
}
|
||||
2 => {
|
||||
if last_line.is_empty() {
|
||||
filtered.extend_from_slice(data);
|
||||
} else {
|
||||
filtered.extend_from_slice(&data.iter()
|
||||
.zip(last_line.iter())
|
||||
.map(|(cur, last)| cur.wrapping_sub(*last))
|
||||
.collect::<Vec<u8>>());
|
||||
.zip(last_line.iter())
|
||||
.map(|(cur, last)| cur.wrapping_sub(*last))
|
||||
.collect::<Vec<u8>>());
|
||||
};
|
||||
}
|
||||
3 => {
|
||||
|
|
@ -1171,9 +1180,9 @@ fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> Vec<u
|
|||
unfiltered.extend_from_slice(data);
|
||||
} else {
|
||||
unfiltered.extend_from_slice(&data.iter()
|
||||
.zip(last_line.iter())
|
||||
.map(|(cur, last)| cur.wrapping_add(*last))
|
||||
.collect::<Vec<u8>>());
|
||||
.zip(last_line.iter())
|
||||
.map(|(cur, last)| cur.wrapping_add(*last))
|
||||
.collect::<Vec<u8>>());
|
||||
};
|
||||
}
|
||||
3 => {
|
||||
|
|
@ -1535,14 +1544,14 @@ fn reduce_rgb_to_grayscale(png: &PngData) -> Option<Vec<u8>> {
|
|||
reduced.push(cur_pixel[0]);
|
||||
} else {
|
||||
let mut pixel_zip = cur_pixel.iter()
|
||||
.enumerate()
|
||||
.filter(|&(i, _)| i % 2 == 0)
|
||||
.map(|(_, x)| *x)
|
||||
.zip(cur_pixel.iter()
|
||||
.enumerate()
|
||||
.filter(|&(i, _)| i % 2 == 1)
|
||||
.map(|(_, x)| *x))
|
||||
.collect::<Vec<(u8, u8)>>();
|
||||
.enumerate()
|
||||
.filter(|&(i, _)| i % 2 == 0)
|
||||
.map(|(_, x)| *x)
|
||||
.zip(cur_pixel.iter()
|
||||
.enumerate()
|
||||
.filter(|&(i, _)| i % 2 == 1)
|
||||
.map(|(_, x)| *x))
|
||||
.collect::<Vec<(u8, u8)>>();
|
||||
pixel_zip.sort();
|
||||
pixel_zip.dedup();
|
||||
if pixel_zip.len() > 1 {
|
||||
|
|
@ -1605,10 +1614,10 @@ fn parse_next_header(byte_data: &[u8],
|
|||
fix_errors: bool)
|
||||
-> Result<Option<(String, Vec<u8>)>, String> {
|
||||
let mut rdr = Cursor::new(byte_data.iter()
|
||||
.skip(*byte_offset)
|
||||
.take(4)
|
||||
.cloned()
|
||||
.collect::<Vec<u8>>());
|
||||
.skip(*byte_offset)
|
||||
.take(4)
|
||||
.cloned()
|
||||
.collect::<Vec<u8>>());
|
||||
let length: u32 = match rdr.read_u32::<BigEndian>() {
|
||||
Ok(x) => x,
|
||||
Err(_) => return Err("Invalid data found--unable to read PNG file".to_owned()),
|
||||
|
|
@ -1627,16 +1636,16 @@ fn parse_next_header(byte_data: &[u8],
|
|||
*byte_offset += 4;
|
||||
|
||||
let data: Vec<u8> = byte_data.iter()
|
||||
.skip(*byte_offset)
|
||||
.take(length as usize)
|
||||
.cloned()
|
||||
.collect();
|
||||
.skip(*byte_offset)
|
||||
.take(length as usize)
|
||||
.cloned()
|
||||
.collect();
|
||||
*byte_offset += length as usize;
|
||||
let mut rdr = Cursor::new(byte_data.iter()
|
||||
.skip(*byte_offset)
|
||||
.take(4)
|
||||
.cloned()
|
||||
.collect::<Vec<u8>>());
|
||||
.skip(*byte_offset)
|
||||
.take(4)
|
||||
.cloned()
|
||||
.collect::<Vec<u8>>());
|
||||
let crc: u32 = match rdr.read_u32::<BigEndian>() {
|
||||
Ok(x) => x,
|
||||
Err(_) => return Err("Invalid data found--unable to read PNG file".to_owned()),
|
||||
|
|
|
|||
BIN
tests/files/issue_42.png
Normal file
BIN
tests/files/issue_42.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 B |
|
|
@ -486,3 +486,44 @@ fn fix_errors() {
|
|||
// Cannot check if pixels are equal because image crate cannot read corrupt (input) PNGs
|
||||
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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue