diff --git a/src/lib.rs b/src/lib.rs index 86512f51..86a9e685 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,7 +68,7 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { palette.len() / 3); } else { println!(" {}x{} bits/pixel, {:?}", - png.bits_per_pixel_raw(), + png.channels_per_pixel(), png.ihdr_data.bit_depth, png.ihdr_data.color_type); } @@ -81,30 +81,37 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { if opts.color_type_reduction { if png.reduce_color_type() { something_changed = true; - // TODO: Print message to terminal }; } if opts.bit_depth_reduction { if png.reduce_bit_depth() { something_changed = true; - // TODO: Print message to terminal }; } if opts.palette_reduction { if png.reduce_palette() { something_changed = true; - // TODO: Print message to terminal }; } + if something_changed { + if let Some(palette) = png.palette.clone() { + println!("Reducing image to {} bits/pixel, {} colors in palette", + png.ihdr_data.bit_depth, + palette.len() / 3); + } else { + println!("Reducing image to {}x{} bits/pixel, {:?}", + png.channels_per_pixel(), + png.ihdr_data.bit_depth, + png.ihdr_data.color_type); + } + } + if let Some(interlacing) = opts.interlace { - if interlacing != png.ihdr_data.interlaced { - if png.change_interlacing(interlacing) { - something_changed = true; - // TODO: Print message to terminal - } + if png.change_interlacing(interlacing) { + something_changed = true; } } diff --git a/src/png.rs b/src/png.rs index 37c4de97..2a99f6fe 100644 --- a/src/png.rs +++ b/src/png.rs @@ -9,7 +9,7 @@ use std::io::prelude::*; use std::iter::Iterator; use std::path::Path; -#[derive(Debug,PartialEq)] +#[derive(Debug,PartialEq,Clone)] pub enum ColorType { Grayscale, RGB, @@ -44,7 +44,7 @@ impl ColorType { } } -#[derive(Debug,PartialEq)] +#[derive(Debug,PartialEq,Clone)] pub enum BitDepth { One, Two, @@ -104,7 +104,7 @@ impl<'a> Iterator for ScanLines<'a> { } else { let bits_per_line = self.png.ihdr_data.width as usize * self.png.ihdr_data.bit_depth.as_u8() as usize * - self.png.bits_per_pixel_raw() as usize; + self.png.channels_per_pixel() as usize; // This avoids casting to and from floats, which is expensive let bytes_per_line = ((bits_per_line + bits_per_line % 8) >> 3) + 1; self.start = self.end; @@ -190,7 +190,7 @@ impl PngData { if aux_headers.get("IHDR").is_none() { return Err("Image header data was missing, skipping".to_owned()); } - let ihdr_header = match parse_ihdr_header(aux_headers.get("IHDR").unwrap().as_ref()) { + let ihdr_header = match parse_ihdr_header(aux_headers.remove("IHDR").unwrap().as_ref()) { Ok(x) => x, Err(x) => return Err(x), }; @@ -229,7 +229,7 @@ impl PngData { // Return the PngData Ok(png_data) } - pub fn bits_per_pixel_raw(&self) -> u8 { + pub fn channels_per_pixel(&self) -> u8 { match self.ihdr_data.color_type { ColorType::Grayscale => 1, ColorType::RGB => 3, @@ -249,8 +249,8 @@ impl PngData { ihdr_data.write_u32::(self.ihdr_data.height).ok(); ihdr_data.write_u8(self.ihdr_data.bit_depth.as_u8()).ok(); ihdr_data.write_u8(self.ihdr_data.color_type.png_header_code()).ok(); - ihdr_data.write_u8(0).ok(); - ihdr_data.write_u8(self.ihdr_data.filter).ok(); + ihdr_data.write_u8(0).ok(); // Compression -- deflate + ihdr_data.write_u8(0).ok(); // Filter method -- 5-way adaptive filtering ihdr_data.write_u8(self.ihdr_data.interlaced).ok(); output.reserve(ihdr_data.len() + 8); output.write_u32::(ihdr_data.len() as u32 - 4).ok(); @@ -330,11 +330,11 @@ impl PngData { pub fn unfilter_image(&self) -> Vec { let mut unfiltered = Vec::with_capacity(self.raw_data.len()); // This avoids casting to and from floats, which is expensive - let tmp = self.ihdr_data.bit_depth.as_u8() * self.bits_per_pixel_raw(); + let tmp = self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel(); let bpp = (tmp + tmp % 8) >> 3; let mut last_line: Vec = vec![]; for line in self.scan_lines() { - unfiltered.push(line.filter); + unfiltered.push(0); match line.filter { 0 => { let mut data = line.data.clone(); @@ -414,7 +414,7 @@ impl PngData { pub fn filter_image(&self, filter: u8) -> Vec { let mut filtered = Vec::with_capacity(self.raw_data.len()); // This avoids casting to and from floats, which is expensive - let tmp = self.ihdr_data.bit_depth.as_u8() * self.bits_per_pixel_raw(); + let tmp = self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel(); let bpp = (tmp + tmp % 8) >> 3; let mut last_line: Vec = vec![]; // We could try a different filter method for each line @@ -426,8 +426,7 @@ impl PngData { } match filter { 0 => { - let mut data = line.data.clone(); - filtered.append(&mut data); + filtered.extend(&line.data); } 1 => { for (i, byte) in line.data.iter().enumerate() { @@ -564,8 +563,7 @@ impl PngData { } filtered.push(best.0); - let mut data = best.1.to_owned(); - filtered.append(&mut data); + filtered.extend(best.1); } _ => panic!("Unreachable"), } @@ -575,7 +573,13 @@ impl PngData { } pub fn reduce_bit_depth(&mut self) -> bool { if self.ihdr_data.bit_depth != BitDepth::Sixteen { - // TODO: Handle 8-bit to lower reduction + if self.ihdr_data.color_type == ColorType::Indexed || + self.ihdr_data.color_type == ColorType::Grayscale { + return match reduce_bit_depth_8_or_less(self) { + Some(_) => true, + None => false + }; + } return false; } @@ -672,6 +676,10 @@ impl PngData { } pub fn change_interlacing(&mut self, interlace: u8) -> bool { // TODO: Implement + if interlace != self.ihdr_data.interlaced { + return false; + } + false } } @@ -770,7 +778,7 @@ fn reduce_rgba_to_grayscale_alpha(png: &PngData) -> Option> { fn reduce_rgba_to_palette(png: &PngData) -> Option<(Vec, Vec, Vec)> { let mut reduced = Vec::with_capacity(png.raw_data.len()); - let mut palette = Vec::with_capacity(256); + let mut palette = HashMap::with_capacity(255); let byte_depth: usize = (png.ihdr_data.bit_depth.as_u8() >> 3) as usize; let bpp: usize = 4 * byte_depth; for line in png.scan_lines() { @@ -779,13 +787,14 @@ fn reduce_rgba_to_palette(png: &PngData) -> Option<(Vec, Vec, Vec)> for (i, byte) in line.data.iter().enumerate() { cur_pixel.push(*byte); if i % bpp == bpp - 1 { - if !palette.contains(&cur_pixel) { - palette.push(cur_pixel.clone()); + if !palette.contains_key(&cur_pixel) { + let len = palette.len(); + if len >= 255 { + return None; + } + palette.insert(cur_pixel.clone(), len as u8); } - if palette.len() > 255 { - return None; - } - reduced.push(palette.binary_search(&cur_pixel).unwrap() as u8); + reduced.push(palette.get(&cur_pixel).unwrap().clone()); cur_pixel.clear(); } } @@ -793,7 +802,7 @@ fn reduce_rgba_to_palette(png: &PngData) -> Option<(Vec, Vec, Vec)> let mut color_palette = Vec::with_capacity(palette.len() * byte_depth * 3); let mut trans_palette = Vec::with_capacity(palette.len() * byte_depth); - for color in palette { + for (color, _) in palette { for (i, byte) in color.iter().enumerate() { if i < byte_depth * 3 { color_palette.push(*byte); @@ -808,7 +817,7 @@ fn reduce_rgba_to_palette(png: &PngData) -> Option<(Vec, Vec, Vec)> fn reduce_rgb_to_palette(png: &PngData) -> Option<(Vec, Vec)> { let mut reduced = Vec::with_capacity(png.raw_data.len()); - let mut palette = Vec::with_capacity(256); + let mut palette = HashMap::with_capacity(255); let byte_depth: usize = (png.ihdr_data.bit_depth.as_u8() >> 3) as usize; let bpp: usize = 3 * byte_depth; for line in png.scan_lines() { @@ -817,20 +826,21 @@ fn reduce_rgb_to_palette(png: &PngData) -> Option<(Vec, Vec)> { for (i, byte) in line.data.iter().enumerate() { cur_pixel.push(*byte); if i % bpp == bpp - 1 { - if !palette.contains(&cur_pixel) { - palette.push(cur_pixel.clone()); + if !palette.contains_key(&cur_pixel) { + let len = palette.len(); + if len >= 255 { + return None; + } + palette.insert(cur_pixel.clone(), len as u8); } - if palette.len() > 255 { - return None; - } - reduced.push(palette.binary_search(&cur_pixel).unwrap() as u8); + reduced.push(palette.get(&cur_pixel).unwrap().clone()); cur_pixel.clear(); } } } let mut color_palette = Vec::with_capacity(palette.len() * byte_depth * 3); - for color in palette { + for (color, _) in palette { color_palette.extend(&color); }