Skip to content

Commit

Permalink
Ignore color reductions that would increase file size
Browse files Browse the repository at this point in the history
Closes #61
  • Loading branch information
Josh Holmer committed Mar 8, 2017
1 parent a7c2c9d commit d18850e
Show file tree
Hide file tree
Showing 50 changed files with 15 additions and 801 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
**Version 0.15.1 (unreleased)**
- Ignore color reductions that would increase file size ([#61](https://github.com/shssoichiro/oxipng/issues/61))

**Version 0.15.0**
- [SEMVER_MINOR] Check images for correctness before writing result ([#60](https://github.com/shssoichiro/oxipng/issues/60))
- Fix invalid output when reducing image to a different color type but file size does not improve ([#60](https://github.com/shssoichiro/oxipng/issues/60))
Expand Down
5 changes: 0 additions & 5 deletions src/png.rs
Original file line number Diff line number Diff line change
Expand Up @@ -717,11 +717,6 @@ impl PngData {
should_reduce_bit_depth = true;
}

if self.ihdr_data.color_type == ColorType::Grayscale && reduce_grayscale_to_palette(self) {
changed = true;
should_reduce_bit_depth = true;
}

if should_reduce_bit_depth {
// Some conversions will allow us to perform bit depth reduction that
// wasn't possible before
Expand Down
88 changes: 12 additions & 76 deletions src/reduction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ pub fn reduce_rgba_to_palette(png: &mut PngData) -> bool {
}
}

let headers_size = color_palette.len() + trans_palette.len() + 8;
if reduced.len() + headers_size > png.raw_data.len() * 4 {
// Reduction would result in a larger image
return false;
}

if let Some(bkgd_header) = png.aux_headers.get_mut(&"bKGD".to_string()) {
assert_eq!(bkgd_header.len(), 6);
let header_pixels = bkgd_header.iter().skip(1).step(2).cloned().collect::<Vec<u8>>();
Expand Down Expand Up @@ -217,6 +223,12 @@ pub fn reduce_rgb_to_palette(png: &mut PngData) -> bool {
color_palette.extend_from_slice(color);
}

let headers_size = color_palette.len() + 4;
if reduced.len() + headers_size > png.raw_data.len() * 3 {
// Reduction would result in a larger image
return false;
}

if let Some(bkgd_header) = png.aux_headers.get_mut(&"bKGD".to_string()) {
assert_eq!(bkgd_header.len(), 6);
let header_pixels = bkgd_header.iter().skip(1).step(2).cloned().collect::<Vec<u8>>();
Expand All @@ -237,82 +249,6 @@ pub fn reduce_rgb_to_palette(png: &mut PngData) -> bool {
true
}

pub fn reduce_grayscale_to_palette(png: &mut PngData) -> bool {
if png.ihdr_data.bit_depth == BitDepth::Sixteen {
return false;
}
let mut reduced = BitVec::with_capacity(png.raw_data.len() * 8);
// Only perform reduction if we can get to 4-bits or less
let mut palette = Vec::with_capacity(16);
let bpp: usize = png.ihdr_data.bit_depth.as_u8() as usize;
let bpp_inverse = 8 - bpp;
for line in png.scan_lines() {
reduced.extend(BitVec::from_bytes(&[line.filter]));
let bit_vec = BitVec::from_bytes(&line.data);
let mut cur_pixel = BitVec::with_capacity(bpp);
for (i, bit) in bit_vec.iter().enumerate() {
cur_pixel.push(bit);
if i % bpp == bpp - 1 {
let pix_value = cur_pixel.to_bytes()[0] >> bpp_inverse;
let pix_slice = vec![pix_value, pix_value, pix_value];
if palette.contains(&pix_slice) {
let index = palette.iter().enumerate().find(|&x| x.1 == &pix_slice).unwrap().0;
let idx = BitVec::from_bytes(&[(index as u8) << bpp_inverse]);
for b in idx.iter().take(bpp) {
reduced.push(b);
}
} else {
let len = palette.len();
if len == 16 {
return false;
}
palette.push(pix_slice);
let idx = BitVec::from_bytes(&[(len as u8) << bpp_inverse]);
for b in idx.iter().take(bpp) {
reduced.push(b);
}
}
cur_pixel = BitVec::with_capacity(bpp);
}
}
// Pad end of line to get 8 bits per byte
while reduced.len() % 8 != 0 {
reduced.push(false);
}
}

let mut color_palette = Vec::with_capacity(palette.len() * 3);
for color in &palette {
color_palette.extend_from_slice(color);
}

if let Some(bkgd_header) = png.aux_headers.get_mut(&"bKGD".to_string()) {
assert_eq!(bkgd_header.len(), 2);
let header_pixels = [bkgd_header[1], bkgd_header[1], bkgd_header[1]];
if let Some(entry) = color_palette.chunks(3).position(|x| *x == header_pixels) {
*bkgd_header = vec![entry as u8];
} else if color_palette.len() == 255 {
return false;
} else {
let entry = color_palette.len() / 3;
color_palette.extend_from_slice(&header_pixels);
*bkgd_header = vec![entry as u8];
}
}

if let Some(sbit_header) = png.aux_headers.get_mut(&"sBIT".to_string()) {
assert_eq!(sbit_header.len(), 1);
let byte = sbit_header[0];
sbit_header.push(byte);
sbit_header.push(byte);
}

png.raw_data = reduced.to_bytes();
png.palette = Some(color_palette);
png.ihdr_data.color_type = ColorType::Indexed;
true
}

pub fn reduce_rgb_to_grayscale(png: &mut PngData) -> bool {
let mut reduced = Vec::with_capacity(png.raw_data.len());
let byte_depth: u8 = png.ihdr_data.bit_depth.as_u8() >> 3;
Expand Down
Binary file removed tests/files/grayscale_16_should_be_palette_1.png
Binary file not shown.
Binary file removed tests/files/grayscale_16_should_be_palette_2.png
Binary file not shown.
Binary file removed tests/files/grayscale_16_should_be_palette_4.png
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Binary file removed tests/files/rgb_16_should_be_palette_1_grayscale.png
Diff not rendered.
Binary file removed tests/files/rgb_16_should_be_palette_2_grayscale.png
Diff not rendered.
Diff not rendered.
Binary file removed tests/files/rgb_8_should_be_palette_1_grayscale.png
Diff not rendered.
Binary file removed tests/files/rgb_8_should_be_palette_2_grayscale.png
Diff not rendered.
Binary file removed tests/files/rgb_8_should_be_palette_4_grayscale.png
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Binary file removed tests/files/rgba_8_should_be_palette_1_grayscale.png
Diff not rendered.
Diff not rendered.
Binary file removed tests/files/rgba_8_should_be_palette_4_grayscale.png
Diff not rendered.
Loading

0 comments on commit d18850e

Please sign in to comment.