Merge branch 'master' of github.com:shssoichiro/oxipng

This commit is contained in:
Joshua Holmer 2016-04-04 16:25:58 -04:00
commit 0b7f40afd1
92 changed files with 1766 additions and 12 deletions

View file

@ -1,3 +1,8 @@
**Version 0.3.0 (unreleased)**
- Support interlaced images
- Allow converting between progressive and interlaced images
- Fix a bug that would cause oxipng to crash on very small images
**Version 0.2.2**
- Limit number of threads to 1.5x number of cores
- Significantly improve memory usage, especially with high optimization levels. ([#32](https://github.com/shssoichiro/oxipng/issues/32))

View file

@ -1,6 +1,6 @@
# Oxipng
[![Build Status](https://travis-ci.org/shssoichiro/oxipng.svg)](https://travis-ci.org/shssoichiro/oxipng)
[![Build Status](https://travis-ci.org/shssoichiro/oxipng.svg?branch=master)](https://travis-ci.org/shssoichiro/oxipng)
[![Version](https://img.shields.io/crates/v/oxipng.svg)](https://crates.io/crates/oxipng)
[![License](https://img.shields.io/crates/l/oxipng.svg)](https://github.com/shssoichiro/oxipng/blob/master/LICENSE)
@ -66,7 +66,7 @@ More advanced options can be found by running `oxipng -h`.
## History
Oxipng began as a completely rewrite of the OptiPNG project,
Oxipng began as a complete rewrite of the OptiPNG project,
which is assumed to be dead as no commit has been made to it since March 2014.
The name has been changed to avoid confusion and potential legal issues.

View file

@ -1,5 +1,6 @@
use libz_sys;
use libc::c_int;
use std::cmp::max;
/// Decompress a data stream using the DEFLATE algorithm
pub fn inflate(data: &[u8]) -> Result<Vec<u8>, String> {
@ -25,10 +26,12 @@ pub fn deflate(data: &[u8], zc: u8, zm: u8, zs: u8, zw: u8) -> Result<Vec<u8>, S
zw as c_int,
zm as c_int,
zs as c_int);
let mut output = Vec::with_capacity(data.len() / 20);
// Compressed input should be smaller than decompressed, so allocate less than data.len()
// However, it needs a minimum capacity in order to handle very small images
let mut output = Vec::with_capacity(max(1024, data.len() / 20));
loop {
match stream.compress_vec(input.as_mut(), output.as_mut()) {
libz_sys::Z_OK => output.reserve(data.len() / 20),
libz_sys::Z_OK => output.reserve(max(1024, data.len() / 20)),
libz_sys::Z_STREAM_END => break,
c => return Err(format!("Error code on compress: {}", c)),
}

View file

@ -186,10 +186,8 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
if let Some(interlacing) = opts.interlace {
if png.change_interlacing(interlacing) {
png.ihdr_data.interlaced = interlacing;
something_changed = true;
if opts.verbosity == Some(1) {
report_reduction(&png);
}
}
}

View file

@ -123,6 +123,8 @@ pub struct ScanLines<'a> {
pub png: &'a PngData,
start: usize,
end: usize,
/// Current pass number, and 0-indexed row within the pass
pass: Option<(u8, u32)>,
}
impl<'a> Iterator for ScanLines<'a> {
@ -130,12 +132,97 @@ impl<'a> Iterator for ScanLines<'a> {
fn next(&mut self) -> Option<Self::Item> {
if self.end == self.png.raw_data.len() {
None
} else if self.png.ihdr_data.interlaced == 1 {
// Scanlines for interlaced PNG files
if self.pass.is_none() {
self.pass = Some((1, 0));
}
// Handle edge cases for images smaller than 5 pixels in either direction
if self.png.ihdr_data.width < 5 && self.pass.unwrap().0 == 2 {
if let Some(pass) = self.pass.as_mut() {
pass.0 = 3;
pass.1 = 4;
}
}
// Intentionally keep these separate so that they can be applied one after another
if self.png.ihdr_data.height < 5 && self.pass.unwrap().0 == 3 {
if let Some(pass) = self.pass.as_mut() {
pass.0 = 4;
pass.1 = 0;
}
}
let bits_per_pixel = self.png.ihdr_data.bit_depth.as_u8() as usize *
self.png.channels_per_pixel() as usize;
let mut bits_per_line = self.png.ihdr_data.width as usize * bits_per_pixel;
let y_steps;
match self.pass {
Some((1, _)) | Some((2, _)) => {
bits_per_line = (bits_per_line as f32 / 8f32).ceil() as usize;
y_steps = 8;
}
Some((3, _)) => {
bits_per_line = (bits_per_line as f32 / 4f32).ceil() as usize;
y_steps = 8;
}
Some((4, _)) => {
bits_per_line = (bits_per_line as f32 / 4f32).ceil() as usize;
y_steps = 4;
}
Some((5, _)) => {
bits_per_line = (bits_per_line as f32 / 2f32).ceil() as usize;
y_steps = 4;
}
Some((6, _)) => {
bits_per_line = (bits_per_line as f32 / 2f32).ceil() as usize;
y_steps = 2;
}
Some((7, _)) => {
y_steps = 2;
}
_ => unreachable!(),
}
// Determine whether to trim the last (overflow) pixel for rows on this pass
let gap = bits_per_line % bits_per_pixel;
if gap != 0 {
let x_start = bits_per_pixel *
match self.pass.unwrap().0 {
2 => 4,
4 => 2,
6 => 1,
_ => 0,
};
if gap >= x_start {
bits_per_line += bits_per_pixel - gap;
} else {
bits_per_line -= gap;
}
}
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;
if let Some(pass) = self.pass.as_mut() {
if pass.1 + y_steps >= self.png.ihdr_data.height {
pass.0 += 1;
pass.1 = match pass.0 {
3 => 4,
5 => 2,
7 => 1,
_ => 0,
};
} else {
pass.1 += y_steps;
}
}
Some(ScanLine {
filter: self.png.raw_data[self.start],
data: self.png.raw_data[(self.start + 1)..self.end].to_owned(),
})
} else {
// Standard, non-interlaced PNG scanlines
let bits_per_line = self.png.ihdr_data.width as usize *
self.png.ihdr_data.bit_depth.as_u8() as usize *
self.png.channels_per_pixel() as usize;
// Round up without converting to float
let bytes_per_line = (bits_per_line + bits_per_line % 8) >> 3;
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;
Some(ScanLine {
@ -338,6 +425,7 @@ impl PngData {
png: &self,
start: 0,
end: 0,
pass: None,
}
}
/// Reverse all filters applied on the image, returning an unfiltered IDAT bytestream
@ -542,13 +630,225 @@ impl PngData {
}
/// Convert the image to the specified interlacing type
/// Returns true if the interlacing was changed, false otherwise
/// The `interlace` parameter specifies the *new* interlacing mode
/// Assumes that the data has already been de-filtered
pub fn change_interlacing(&mut self, interlace: u8) -> bool {
// TODO: Implement
if interlace != self.ihdr_data.interlaced {
if interlace == self.ihdr_data.interlaced {
return false;
}
false
if interlace == 1 {
// Convert progressive to interlaced data
interlace_image(self);
} else {
// Convert interlaced to progressive data
deinterlace_image(self);
}
true
}
}
fn interlace_image(png: &mut PngData) {
let mut passes: Vec<BitVec> = Vec::with_capacity(7);
for _ in 0..7 {
passes.push(BitVec::new());
}
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
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 {
passes[1].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));
}
2 | 6 => {
passes[4].extend(BitVec::from_elem(8, false));
passes[5].extend(BitVec::from_elem(8, false));
}
_ => {
passes[6].extend(BitVec::from_elem(8, false));
}
}
let bit_vec = BitVec::from_bytes(&line.data);
for (i, bit) in bit_vec.iter().enumerate() {
// Avoid moving padded 0's into new image
if i >= (png.ihdr_data.width * bits_per_pixel as u32) as usize {
break;
}
// Copy pixels into interlaced passes
let pix_modulo = (((i / bits_per_pixel as usize) as f32).floor() as usize) % 8;
match index % 8 {
0 => {
match pix_modulo {
0 => passes[0].push(bit),
4 => passes[1].push(bit),
2 | 6 => passes[3].push(bit),
_ => passes[5].push(bit),
}
}
4 => {
match pix_modulo {
0 | 4 => passes[2].push(bit),
2 | 6 => passes[3].push(bit),
_ => passes[5].push(bit),
}
}
2 | 6 => {
match pix_modulo % 2 {
0 => passes[4].push(bit),
_ => passes[5].push(bit),
}
}
_ => {
passes[6].push(bit);
}
}
}
// Pad end of line on each pass to get 8 bits per byte
for pass in &mut passes {
while pass.len() % 8 != 0 {
pass.push(false);
}
}
}
let mut output = Vec::new();
for pass in &passes {
output.extend(pass.to_bytes());
}
png.raw_data = output;
}
fn deinterlace_image(png: &mut PngData) {
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);
for _ in 0..png.ihdr_data.height {
// Initialize each output line with a starting filter byte of 0
// as well as some blank data
lines.push(BitVec::from_elem(8 + bits_per_pixel as usize * png.ihdr_data.width as usize,
false));
}
let mut current_pass = 1;
let mut pass_constants = interlaced_constants(current_pass);
let mut current_y: usize = pass_constants.y_shift as usize;
for line in png.scan_lines() {
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;
for (i, bit) in bit_vec.iter().enumerate() {
// Avoid moving padded 0's into new image
if i >= bits_in_line {
break;
}
let current_x: usize = pass_constants.x_shift as usize +
(i / bits_per_pixel as usize) * pass_constants.x_step as usize;
// Copy this bit into the output line, offset by 8 because of filter byte
let index = 8 + (i % bits_per_pixel as usize) + current_x * bits_per_pixel as usize;
lines[current_y].set(index, bit);
}
// Calculate the next line and move to next pass if necessary
current_y += pass_constants.y_step as usize;
if current_y >= png.ihdr_data.height as usize {
if current_pass == 7 {
break;
}
current_pass += 1;
if current_pass == 2 && png.ihdr_data.width <= 4 {
current_pass += 1;
}
if current_pass == 3 && png.ihdr_data.height <= 4 {
current_pass += 1;
}
pass_constants = interlaced_constants(current_pass);
current_y = pass_constants.y_shift as usize;
}
}
let mut output = Vec::new();
for line in &mut lines {
while line.len() % 8 != 0 {
line.push(false);
}
output.extend(line.to_bytes());
}
png.raw_data = output;
}
struct InterlacedConstants {
x_shift: u8,
y_shift: u8,
x_step: u8,
y_step: u8,
}
fn interlaced_constants(pass: u8) -> InterlacedConstants {
match pass {
1 => {
InterlacedConstants {
x_shift: 0,
y_shift: 0,
x_step: 8,
y_step: 8,
}
}
2 => {
InterlacedConstants {
x_shift: 4,
y_shift: 0,
x_step: 8,
y_step: 8,
}
}
3 => {
InterlacedConstants {
x_shift: 0,
y_shift: 4,
x_step: 4,
y_step: 8,
}
}
4 => {
InterlacedConstants {
x_shift: 2,
y_shift: 0,
x_step: 4,
y_step: 4,
}
}
5 => {
InterlacedConstants {
x_shift: 0,
y_shift: 2,
x_step: 2,
y_step: 4,
}
}
6 => {
InterlacedConstants {
x_shift: 1,
y_shift: 0,
x_step: 2,
y_step: 2,
}
}
7 => {
InterlacedConstants {
x_shift: 0,
y_shift: 1,
x_step: 1,
y_step: 2,
}
}
_ => unreachable!(),
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
tests/files/small_files.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -267,3 +267,159 @@ fn strip_headers_none() {
remove_file(output).ok();
}
#[test]
fn interlacing_0_to_1() {
let input = PathBuf::from("tests/files/interlacing_0_to_1.png");
let mut opts = get_opts(&input);
opts.interlace = Some(1);
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();
}
#[test]
fn interlacing_1_to_0() {
let input = PathBuf::from("tests/files/interlacing_1_to_0.png");
let mut opts = get_opts(&input);
opts.interlace = Some(0);
let output = opts.out_file.clone();
let png = png::PngData::new(&input).unwrap();
assert!(png.ihdr_data.interlaced == 1);
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 == 0);
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();
}
#[test]
fn interlacing_0_to_1_small_files() {
let input = PathBuf::from("tests/files/interlacing_0_to_1_small_files.png");
let mut opts = get_opts(&input);
opts.interlace = Some(1);
let output = opts.out_file.clone();
let png = png::PngData::new(&input).unwrap();
assert!(png.ihdr_data.interlaced == 0);
assert!(png.ihdr_data.color_type == png::ColorType::Indexed);
assert!(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) {
Ok(x) => x,
Err(x) => {
remove_file(output).ok();
panic!(x.to_owned())
}
};
assert!(png.ihdr_data.interlaced == 1);
assert!(png.ihdr_data.color_type == png::ColorType::Indexed);
assert!(png.ihdr_data.bit_depth == png::BitDepth::One);
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();
}
#[test]
fn interlacing_1_to_0_small_files() {
let input = PathBuf::from("tests/files/interlacing_1_to_0_small_files.png");
let mut opts = get_opts(&input);
opts.interlace = Some(0);
let output = opts.out_file.clone();
let png = png::PngData::new(&input).unwrap();
assert!(png.ihdr_data.interlaced == 1);
assert!(png.ihdr_data.color_type == png::ColorType::Indexed);
assert!(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) {
Ok(x) => x,
Err(x) => {
remove_file(output).ok();
panic!(x.to_owned())
}
};
assert!(png.ihdr_data.interlaced == 0);
assert!(png.ihdr_data.color_type == png::ColorType::Indexed);
assert!(png.ihdr_data.bit_depth == png::BitDepth::One);
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();
}

1276
tests/interlaced.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -60,6 +60,7 @@ fn test_it_converts(input: &Path,
assert!(png.ihdr_data.color_type == color_type_in);
assert!(png.ihdr_data.bit_depth == bit_depth_in);
assert!(png.ihdr_data.interlaced == 0);
match oxipng::optimize(input, opts) {
Ok(_) => (),
@ -1258,3 +1259,18 @@ fn grayscale_8_should_be_palette_1() {
png::ColorType::Indexed,
png::BitDepth::One);
}
#[test]
fn small_files() {
let input = PathBuf::from("tests/files/small_files.png");
let opts = get_opts(&input);
let output = opts.out_file.clone();
test_it_converts(&input,
&output,
&opts,
png::ColorType::Indexed,
png::BitDepth::Eight,
png::ColorType::Indexed,
png::BitDepth::One);
}