diff --git a/src/lib.rs b/src/lib.rs index ee7f03dd..cb97ab6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -740,10 +740,13 @@ fn perform_reductions(png: &mut PngData, opts: &Options, deadline: &Deadline) -> return reduction_occurred; } - if opts.bit_depth_reduction && png.reduce_bit_depth() { - reduction_occurred = true; - if opts.verbosity == Some(1) { - report_reduction(png); + if opts.bit_depth_reduction { + if let Some(reduced) = png.reduce_bit_depth() { + png.apply_reduction(reduced); + reduction_occurred = true; + if opts.verbosity == Some(1) { + report_reduction(png); + } } } diff --git a/src/png/mod.rs b/src/png/mod.rs index 02da016c..980b1c59 100644 --- a/src/png/mod.rs +++ b/src/png/mod.rs @@ -328,14 +328,15 @@ impl PngData { /// Attempt to reduce the bit depth of the image /// Returns true if the bit depth was reduced, false otherwise - pub fn reduce_bit_depth(&mut self) -> bool { + #[must_use] + pub fn reduce_bit_depth(&self) -> Option { if self.ihdr_data.bit_depth != BitDepth::Sixteen { if self.ihdr_data.color_type == ColorType::Indexed || self.ihdr_data.color_type == ColorType::Grayscale { return reduce_bit_depth_8_or_less(self); } - return false; + return None; } // Reduce from 16 to 8 bits per channel per pixel @@ -355,16 +356,21 @@ impl PngData { // Low byte if high_byte != byte { // Can't reduce, exit early - return false; + return None; } reduced.push(byte); } } } - self.ihdr_data.bit_depth = BitDepth::Eight; - self.raw_data = reduced; - true + Some(ReducedPng { + color_type: self.ihdr_data.color_type, + bit_depth: BitDepth::Eight, + raw_data: reduced, + palette: self.palette.clone(), + transparency_pixel: self.transparency_pixel.clone(), + aux_headers: Default::default(), + }) } /// Attempt to reduce the color type of the image @@ -376,9 +382,7 @@ impl PngData { // Go down one step at a time // Maybe not the most efficient, but it's safe if self.ihdr_data.color_type == ColorType::RGBA { - if reduce_rgba_to_grayscale_alpha(self) { - changed = true; - } else if let Some(reduced) = reduced_alpha_channel(self) { + if let Some(reduced) = reduce_rgba_to_grayscale_alpha(self).or_else(|| reduced_alpha_channel(self)) { self.apply_reduction(reduced); changed = true; } else if let Some(reduced) = reduced_color_to_palette(self) { @@ -397,10 +401,7 @@ impl PngData { } if self.ihdr_data.color_type == ColorType::RGB { - if reduce_rgb_to_grayscale(self) { - changed = true; - should_reduce_bit_depth = true; - } else if let Some(reduced) = reduced_color_to_palette(self) { + if let Some(reduced) = reduce_rgb_to_grayscale(self).or_else(|| reduced_color_to_palette(self)) { self.apply_reduction(reduced); changed = true; should_reduce_bit_depth = true; @@ -410,14 +411,17 @@ impl PngData { if should_reduce_bit_depth { // Some conversions will allow us to perform bit depth reduction that // wasn't possible before - reduce_bit_depth_8_or_less(self); + if let Some(reduced) = reduce_bit_depth_8_or_less(self) { + self.apply_reduction(reduced); + } } changed } - pub(crate) fn apply_reduction(&mut self, ReducedPng {color_type, raw_data, palette, transparency_pixel, aux_headers}: ReducedPng) { + pub(crate) fn apply_reduction(&mut self, ReducedPng {color_type, bit_depth, raw_data, palette, transparency_pixel, aux_headers}: ReducedPng) { self.ihdr_data.color_type = color_type; + self.ihdr_data.bit_depth = bit_depth; self.raw_data = raw_data; if palette.is_some() { self.transparency_pixel = None; diff --git a/src/reduction/alpha.rs b/src/reduction/alpha.rs index 68cb207f..caad1a48 100644 --- a/src/reduction/alpha.rs +++ b/src/reduction/alpha.rs @@ -3,6 +3,7 @@ use png::PngData; use colors::ColorType; use std::collections::HashMap; +#[must_use] pub fn reduced_alpha_channel(png: &PngData) -> Option { let target_color_type = match png.ihdr_data.color_type { ColorType::GrayscaleAlpha => ColorType::Grayscale, @@ -45,6 +46,7 @@ pub fn reduced_alpha_channel(png: &PngData) -> Option { Some(ReducedPng { raw_data, + bit_depth: png.ihdr_data.bit_depth, color_type: target_color_type, aux_headers, transparency_pixel: None, diff --git a/src/reduction/bit_depth.rs b/src/reduction/bit_depth.rs index ee3f0665..a6bab853 100644 --- a/src/reduction/bit_depth.rs +++ b/src/reduction/bit_depth.rs @@ -1,3 +1,4 @@ +use reduction::ReducedPng; use bit_vec::BitVec; use colors::{BitDepth, ColorType}; use png::PngData; @@ -24,7 +25,8 @@ const FOUR_BIT_PERMUTATIONS: [u8; 11] = [ 0b1111_1111, ]; -pub fn reduce_bit_depth_8_or_less(png: &mut PngData) -> bool { +#[must_use] +pub fn reduce_bit_depth_8_or_less(png: &PngData) -> Option { let mut reduced = BitVec::with_capacity(png.raw_data.len() * 8); let bit_depth: usize = png.ihdr_data.bit_depth.as_u8() as usize; let mut allowed_bits = 1; @@ -37,7 +39,7 @@ pub fn reduce_bit_depth_8_or_less(png: &mut PngData) -> bool { allowed_bits = bit_index.next_power_of_two(); if allowed_bits == bit_depth { // Not reducable - return false; + return None; } } } @@ -51,7 +53,7 @@ pub fn reduce_bit_depth_8_or_less(png: &mut PngData) -> bool { } else if allowed_bits == 4 { &FOUR_BIT_PERMUTATIONS } else { - return false; + return None; }; if permutations.iter().any(|perm| *perm == byte) { break; @@ -78,7 +80,12 @@ pub fn reduce_bit_depth_8_or_less(png: &mut PngData) -> bool { } } - png.raw_data = reduced.to_bytes(); - png.ihdr_data.bit_depth = BitDepth::from_u8(allowed_bits as u8); - true + Some(ReducedPng { + color_type: png.ihdr_data.color_type, + raw_data: reduced.to_bytes(), + bit_depth: BitDepth::from_u8(allowed_bits as u8), + aux_headers: Default::default(), + palette: png.palette.clone(), + transparency_pixel: png.transparency_pixel.clone(), + }) } diff --git a/src/reduction/color.rs b/src/reduction/color.rs index 3654df6f..2a2552ca 100644 --- a/src/reduction/color.rs +++ b/src/reduction/color.rs @@ -6,7 +6,8 @@ use rgb::{FromSlice, RGB8, RGBA8}; use std::collections::HashMap; use std::hash::Hash; -pub fn reduce_rgba_to_grayscale_alpha(png: &mut PngData) -> bool { +#[must_use] +pub fn reduce_rgba_to_grayscale_alpha(png: &PngData) -> Option { let mut reduced = Vec::with_capacity(png.raw_data.len()); let byte_depth = png.ihdr_data.bit_depth.as_u8() >> 3; let bpp = 4 * byte_depth; @@ -31,11 +32,11 @@ pub fn reduce_rgba_to_grayscale_alpha(png: &mut PngData) -> bool { if (i as u8 & bpp_mask) == bpp - 1 { if low_bytes.iter().unique().count() > 1 { - return false; + return None; } if byte_depth == 2 { if high_bytes.iter().unique().count() > 1 { - return false; + return None; } reduced.push(high_bytes[0]); high_bytes.clear(); @@ -48,19 +49,23 @@ pub fn reduce_rgba_to_grayscale_alpha(png: &mut PngData) -> bool { } } - if let Some(sbit_header) = png.aux_headers.get_mut(b"sBIT") { - assert!(sbit_header.len() >= 3); - sbit_header.remove(1); - sbit_header.remove(1); - } - if let Some(bkgd_header) = png.aux_headers.get_mut(b"bKGD") { - assert_eq!(bkgd_header.len(), 6); - bkgd_header.truncate(2); + let mut aux_headers = HashMap::new(); + if let Some(sbit_header) = png.aux_headers.get(b"sBIT") { + aux_headers.insert(*b"sBIT", sbit_header.get(0).map(|&s| vec![s])); } - png.raw_data = reduced; - png.ihdr_data.color_type = ColorType::GrayscaleAlpha; - true + if let Some(bkgd_header) = png.aux_headers.get(b"bKGD") { + aux_headers.insert(*b"bKGD", bkgd_header.get(0..2).map(|b| b.to_owned())); + } + + Some(ReducedPng { + raw_data: reduced, + bit_depth: png.ihdr_data.bit_depth, + color_type: ColorType::GrayscaleAlpha, + palette: None, + transparency_pixel: None, + aux_headers, + }) } fn reduce_scanline_to_palette( @@ -88,7 +93,8 @@ where true } -pub fn reduced_color_to_palette(png: &mut PngData) -> Option { +#[must_use] +pub fn reduced_color_to_palette(png: &PngData) -> Option { if png.ihdr_data.bit_depth != BitDepth::Eight { return None; } @@ -171,6 +177,7 @@ pub fn reduced_color_to_palette(png: &mut PngData) -> Option { Some(ReducedPng { color_type: ColorType::Indexed, + bit_depth: png.ihdr_data.bit_depth, aux_headers, raw_data, transparency_pixel: None, @@ -178,7 +185,8 @@ pub fn reduced_color_to_palette(png: &mut PngData) -> Option { }) } -pub fn reduce_rgb_to_grayscale(png: &mut PngData) -> bool { +#[must_use] +pub fn reduce_rgb_to_grayscale(png: &PngData) -> Option { let mut reduced = Vec::with_capacity(png.raw_data.len()); let byte_depth: u8 = png.ihdr_data.bit_depth.as_u8() >> 3; let bpp: usize = 3 * byte_depth as usize; @@ -190,7 +198,7 @@ pub fn reduce_rgb_to_grayscale(png: &mut PngData) -> bool { if i % bpp == bpp - 1 { if bpp == 3 { if cur_pixel.iter().unique().count() > 1 { - return false; + return None; } reduced.push(cur_pixel[0]); } else { @@ -202,7 +210,7 @@ pub fn reduce_rgb_to_grayscale(png: &mut PngData) -> bool { .unique() .collect::>(); if pixel_bytes.len() > 1 { - return false; + return None; } reduced.push(pixel_bytes[0].0); reduced.push(pixel_bytes[0].1); @@ -211,23 +219,31 @@ pub fn reduce_rgb_to_grayscale(png: &mut PngData) -> bool { } } } - if let Some(ref mut trns) = png.transparency_pixel { - assert_eq!(trns.len(), 6); - if trns[0..2] != trns[2..4] || trns[2..4] != trns[4..6] { - return false; + + let transparency_pixel = if let Some(ref trns) = png.transparency_pixel { + if trns.len() != 6 || trns[0..2] != trns[2..4] || trns[2..4] != trns[4..6] { + None + } else { + Some(trns[0..2].to_owned()) } - *trns = trns[0..2].to_owned(); + } else { + png.transparency_pixel.clone() + }; + + let mut aux_headers = HashMap::new(); + if let Some(sbit_header) = png.aux_headers.get(b"sBIT") { + aux_headers.insert(*b"sBIT", sbit_header.get(0).map(|&byte| vec![byte])); } - if let Some(sbit_header) = png.aux_headers.get_mut(b"sBIT") { - assert_eq!(sbit_header.len(), 3); - sbit_header.truncate(1); - } - if let Some(bkgd_header) = png.aux_headers.get_mut(b"bKGD") { - assert_eq!(bkgd_header.len(), 6); - bkgd_header.truncate(2); + if let Some(bkgd_header) = png.aux_headers.get(b"bKGD") { + aux_headers.insert(*b"bKGD", bkgd_header.get(0..2).map(|b| b.to_owned())); } - png.raw_data = reduced; - png.ihdr_data.color_type = ColorType::Grayscale; - true + Some(ReducedPng { + raw_data: reduced, + color_type: ColorType::Grayscale, + bit_depth: png.ihdr_data.bit_depth, + palette: None, + transparency_pixel, + aux_headers, + }) } diff --git a/src/reduction/mod.rs b/src/reduction/mod.rs index 508588c4..2d326976 100644 --- a/src/reduction/mod.rs +++ b/src/reduction/mod.rs @@ -12,6 +12,7 @@ pub mod color; pub struct ReducedPng { pub color_type: ColorType, pub raw_data: Vec, + pub bit_depth: BitDepth, /// replace if Some pub palette: Option>, /// replace if Some @@ -22,6 +23,7 @@ pub struct ReducedPng { /// Attempt to reduce the number of colors in the palette /// Returns `None` if palette hasn't changed +#[must_use] pub fn reduced_palette(png: &PngData) -> Option { if png.ihdr_data.color_type != ColorType::Indexed { // Can't reduce if there is no palette @@ -84,6 +86,7 @@ pub fn reduced_palette(png: &PngData) -> Option { do_palette_reduction(png, &palette_map) } +#[must_use] fn do_palette_reduction(png: &PngData, palette_map: &[Option; 256]) -> Option { let byte_map = palette_map_to_byte_map(png, palette_map)?; let mut raw_data = Vec::with_capacity(png.raw_data.len()); @@ -105,6 +108,7 @@ fn do_palette_reduction(png: &PngData, palette_map: &[Option; 256]) -> Optio Some(ReducedPng { color_type: ColorType::Indexed, + bit_depth: png.ihdr_data.bit_depth, raw_data, transparency_pixel: None, palette: Some(reordered_palette(png.palette.as_ref()?, palette_map)),