commit
f8de1c8e44
5 changed files with 2569 additions and 3028 deletions
30
src/png.rs
30
src/png.rs
|
|
@ -343,8 +343,7 @@ impl PngData {
|
||||||
pub fn unfilter_image(&self) -> Vec<u8> {
|
pub fn unfilter_image(&self) -> Vec<u8> {
|
||||||
let mut unfiltered = Vec::with_capacity(self.raw_data.len());
|
let mut unfiltered = Vec::with_capacity(self.raw_data.len());
|
||||||
let tmp = self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel();
|
let tmp = self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel();
|
||||||
// Round up without converting to float
|
let bpp = ((tmp as f32) / 8f32).ceil() as usize;
|
||||||
let bpp = (tmp + tmp % 8) >> 3;
|
|
||||||
let mut last_line: Vec<u8> = vec![];
|
let mut last_line: Vec<u8> = vec![];
|
||||||
for line in self.scan_lines() {
|
for line in self.scan_lines() {
|
||||||
unfiltered.push(0);
|
unfiltered.push(0);
|
||||||
|
|
@ -355,7 +354,7 @@ impl PngData {
|
||||||
1 => {
|
1 => {
|
||||||
let mut data = Vec::with_capacity(line.data.len());
|
let mut data = Vec::with_capacity(line.data.len());
|
||||||
for (i, byte) in line.data.iter().enumerate() {
|
for (i, byte) in line.data.iter().enumerate() {
|
||||||
match i.checked_sub(bpp as usize) {
|
match i.checked_sub(bpp) {
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
let b = data[x];
|
let b = data[x];
|
||||||
data.push(byte.wrapping_add(b))
|
data.push(byte.wrapping_add(b))
|
||||||
|
|
@ -382,7 +381,7 @@ impl PngData {
|
||||||
let mut data = Vec::with_capacity(line.data.len());
|
let mut data = Vec::with_capacity(line.data.len());
|
||||||
for (i, byte) in line.data.iter().enumerate() {
|
for (i, byte) in line.data.iter().enumerate() {
|
||||||
if last_line.is_empty() {
|
if last_line.is_empty() {
|
||||||
match i.checked_sub(bpp as usize) {
|
match i.checked_sub(bpp) {
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
let b = data[x];
|
let b = data[x];
|
||||||
data.push(byte.wrapping_add(b >> 1))
|
data.push(byte.wrapping_add(b >> 1))
|
||||||
|
|
@ -390,7 +389,7 @@ impl PngData {
|
||||||
None => data.push(*byte),
|
None => data.push(*byte),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
match i.checked_sub(bpp as usize) {
|
match i.checked_sub(bpp) {
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
let b = data[x];
|
let b = data[x];
|
||||||
data.push(byte.wrapping_add(
|
data.push(byte.wrapping_add(
|
||||||
|
|
@ -408,7 +407,7 @@ impl PngData {
|
||||||
let mut data = Vec::with_capacity(line.data.len());
|
let mut data = Vec::with_capacity(line.data.len());
|
||||||
for (i, byte) in line.data.iter().enumerate() {
|
for (i, byte) in line.data.iter().enumerate() {
|
||||||
if last_line.is_empty() {
|
if last_line.is_empty() {
|
||||||
match i.checked_sub(bpp as usize) {
|
match i.checked_sub(bpp) {
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
let b = data[x];
|
let b = data[x];
|
||||||
data.push(byte.wrapping_add(b))
|
data.push(byte.wrapping_add(b))
|
||||||
|
|
@ -416,7 +415,7 @@ impl PngData {
|
||||||
None => data.push(*byte),
|
None => data.push(*byte),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
match i.checked_sub(bpp as usize) {
|
match i.checked_sub(bpp) {
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
let b = data[x];
|
let b = data[x];
|
||||||
data.push(byte.wrapping_add(paeth_predictor(b,
|
data.push(byte.wrapping_add(paeth_predictor(b,
|
||||||
|
|
@ -438,8 +437,7 @@ impl PngData {
|
||||||
pub fn filter_image(&self, filter: u8) -> Vec<u8> {
|
pub fn filter_image(&self, filter: u8) -> Vec<u8> {
|
||||||
let mut filtered = Vec::with_capacity(self.raw_data.len());
|
let mut filtered = Vec::with_capacity(self.raw_data.len());
|
||||||
let tmp = self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel();
|
let tmp = self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel();
|
||||||
// Round up without converting to float
|
let bpp = ((tmp as f32) / 8f32).ceil() as usize;
|
||||||
let bpp = (tmp + tmp % 8) >> 3;
|
|
||||||
let mut last_line: Vec<u8> = vec![];
|
let mut last_line: Vec<u8> = vec![];
|
||||||
// We could try a different filter method for each line
|
// We could try a different filter method for each line
|
||||||
// But that would be prohibitively slow and probably not provide much benefit
|
// But that would be prohibitively slow and probably not provide much benefit
|
||||||
|
|
@ -454,7 +452,7 @@ impl PngData {
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
for (i, byte) in line.data.iter().enumerate() {
|
for (i, byte) in line.data.iter().enumerate() {
|
||||||
filtered.push(match i.checked_sub(bpp as usize) {
|
filtered.push(match i.checked_sub(bpp) {
|
||||||
Some(x) => byte.wrapping_sub(line.data[x]),
|
Some(x) => byte.wrapping_sub(line.data[x]),
|
||||||
None => *byte,
|
None => *byte,
|
||||||
});
|
});
|
||||||
|
|
@ -472,12 +470,12 @@ impl PngData {
|
||||||
3 => {
|
3 => {
|
||||||
for (i, byte) in line.data.iter().enumerate() {
|
for (i, byte) in line.data.iter().enumerate() {
|
||||||
if last_line.is_empty() {
|
if last_line.is_empty() {
|
||||||
filtered.push(match i.checked_sub(bpp as usize) {
|
filtered.push(match i.checked_sub(bpp) {
|
||||||
Some(x) => byte.wrapping_sub(line.data[x] >> 1),
|
Some(x) => byte.wrapping_sub(line.data[x] >> 1),
|
||||||
None => *byte,
|
None => *byte,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
filtered.push(match i.checked_sub(bpp as usize) {
|
filtered.push(match i.checked_sub(bpp) {
|
||||||
Some(x) => byte.wrapping_sub(
|
Some(x) => byte.wrapping_sub(
|
||||||
((line.data[x] as u16 + last_line[i] as u16) >> 1) as u8
|
((line.data[x] as u16 + last_line[i] as u16) >> 1) as u8
|
||||||
),
|
),
|
||||||
|
|
@ -489,12 +487,12 @@ impl PngData {
|
||||||
4 => {
|
4 => {
|
||||||
for (i, byte) in line.data.iter().enumerate() {
|
for (i, byte) in line.data.iter().enumerate() {
|
||||||
if last_line.is_empty() {
|
if last_line.is_empty() {
|
||||||
filtered.push(match i.checked_sub(bpp as usize) {
|
filtered.push(match i.checked_sub(bpp) {
|
||||||
Some(x) => byte.wrapping_sub(line.data[x]),
|
Some(x) => byte.wrapping_sub(line.data[x]),
|
||||||
None => *byte,
|
None => *byte,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
filtered.push(match i.checked_sub(bpp as usize) {
|
filtered.push(match i.checked_sub(bpp) {
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
byte.wrapping_sub(paeth_predictor(line.data[x],
|
byte.wrapping_sub(paeth_predictor(line.data[x],
|
||||||
last_line[i],
|
last_line[i],
|
||||||
|
|
@ -520,7 +518,7 @@ impl PngData {
|
||||||
let mut line_4 = Vec::with_capacity(line.data.len());
|
let mut line_4 = Vec::with_capacity(line.data.len());
|
||||||
for (i, byte) in line.data.iter().enumerate() {
|
for (i, byte) in line.data.iter().enumerate() {
|
||||||
if last_line.is_empty() {
|
if last_line.is_empty() {
|
||||||
match i.checked_sub(bpp as usize) {
|
match i.checked_sub(bpp) {
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
line_1.push(byte.wrapping_sub(line.data[x]));
|
line_1.push(byte.wrapping_sub(line.data[x]));
|
||||||
line_2.push(*byte);
|
line_2.push(*byte);
|
||||||
|
|
@ -535,7 +533,7 @@ impl PngData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match i.checked_sub(bpp as usize) {
|
match i.checked_sub(bpp) {
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
line_1.push(byte.wrapping_sub(line.data[x]));
|
line_1.push(byte.wrapping_sub(line.data[x]));
|
||||||
line_2.push(byte.wrapping_sub(last_line[i]));
|
line_2.push(byte.wrapping_sub(last_line[i]));
|
||||||
|
|
|
||||||
1210
tests/filters.rs
Normal file
1210
tests/filters.rs
Normal file
File diff suppressed because it is too large
Load diff
86
tests/flags.rs
Normal file
86
tests/flags.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
extern crate image;
|
||||||
|
extern crate oxipng;
|
||||||
|
|
||||||
|
use image::GenericImage;
|
||||||
|
use image::Pixel;
|
||||||
|
use oxipng::png;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::fs::remove_file;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn get_opts(input: &Path) -> oxipng::Options {
|
||||||
|
let mut filter = HashSet::new();
|
||||||
|
filter.insert(0);
|
||||||
|
let mut compression = HashSet::new();
|
||||||
|
compression.insert(9);
|
||||||
|
let mut memory = HashSet::new();
|
||||||
|
memory.insert(9);
|
||||||
|
let mut strategies = HashSet::new();
|
||||||
|
for i in 0..4 {
|
||||||
|
strategies.insert(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
oxipng::Options {
|
||||||
|
backup: false,
|
||||||
|
out_file: input.with_extension("out.png").to_owned(),
|
||||||
|
out_dir: None,
|
||||||
|
stdout: false,
|
||||||
|
pretend: false,
|
||||||
|
recursive: false,
|
||||||
|
fix_errors: false,
|
||||||
|
force: true,
|
||||||
|
clobber: true,
|
||||||
|
create: true,
|
||||||
|
preserve_attrs: false,
|
||||||
|
verbosity: Some(0),
|
||||||
|
filter: filter,
|
||||||
|
interlace: None,
|
||||||
|
compression: compression,
|
||||||
|
memory: memory,
|
||||||
|
strategies: strategies,
|
||||||
|
window: 15,
|
||||||
|
bit_depth_reduction: true,
|
||||||
|
color_type_reduction: true,
|
||||||
|
palette_reduction: true,
|
||||||
|
idat_recoding: true,
|
||||||
|
strip: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strip_headers() {
|
||||||
|
let input = PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png");
|
||||||
|
let mut opts = get_opts(&input);
|
||||||
|
opts.strip = true;
|
||||||
|
let output = opts.out_file.clone();
|
||||||
|
|
||||||
|
let png = png::PngData::new(&input).unwrap();
|
||||||
|
|
||||||
|
assert!(png.aux_headers.contains_key("tEXt"));
|
||||||
|
|
||||||
|
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.aux_headers.contains_key("tEXt"));
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
3012
tests/oxipng.rs
3012
tests/oxipng.rs
File diff suppressed because it is too large
Load diff
1259
tests/reduction.rs
Normal file
1259
tests/reduction.rs
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue