Fix outputting IHDR twice

This commit is contained in:
Joshua Holmer 2016-01-16 14:30:05 -05:00
parent 0866eb01ea
commit e4c9aca4a5
2 changed files with 57 additions and 40 deletions

View file

@ -68,7 +68,7 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
palette.len() / 3); palette.len() / 3);
} else { } else {
println!(" {}x{} bits/pixel, {:?}", println!(" {}x{} bits/pixel, {:?}",
png.bits_per_pixel_raw(), png.channels_per_pixel(),
png.ihdr_data.bit_depth, png.ihdr_data.bit_depth,
png.ihdr_data.color_type); png.ihdr_data.color_type);
} }
@ -81,30 +81,37 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
if opts.color_type_reduction { if opts.color_type_reduction {
if png.reduce_color_type() { if png.reduce_color_type() {
something_changed = true; something_changed = true;
// TODO: Print message to terminal
}; };
} }
if opts.bit_depth_reduction { if opts.bit_depth_reduction {
if png.reduce_bit_depth() { if png.reduce_bit_depth() {
something_changed = true; something_changed = true;
// TODO: Print message to terminal
}; };
} }
if opts.palette_reduction { if opts.palette_reduction {
if png.reduce_palette() { if png.reduce_palette() {
something_changed = true; 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 let Some(interlacing) = opts.interlace {
if interlacing != png.ihdr_data.interlaced { if png.change_interlacing(interlacing) {
if png.change_interlacing(interlacing) { something_changed = true;
something_changed = true;
// TODO: Print message to terminal
}
} }
} }

View file

@ -9,7 +9,7 @@ use std::io::prelude::*;
use std::iter::Iterator; use std::iter::Iterator;
use std::path::Path; use std::path::Path;
#[derive(Debug,PartialEq)] #[derive(Debug,PartialEq,Clone)]
pub enum ColorType { pub enum ColorType {
Grayscale, Grayscale,
RGB, RGB,
@ -44,7 +44,7 @@ impl ColorType {
} }
} }
#[derive(Debug,PartialEq)] #[derive(Debug,PartialEq,Clone)]
pub enum BitDepth { pub enum BitDepth {
One, One,
Two, Two,
@ -104,7 +104,7 @@ impl<'a> Iterator for ScanLines<'a> {
} else { } else {
let bits_per_line = self.png.ihdr_data.width as usize * let bits_per_line = self.png.ihdr_data.width as usize *
self.png.ihdr_data.bit_depth.as_u8() 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 // This avoids casting to and from floats, which is expensive
let bytes_per_line = ((bits_per_line + bits_per_line % 8) >> 3) + 1; let bytes_per_line = ((bits_per_line + bits_per_line % 8) >> 3) + 1;
self.start = self.end; self.start = self.end;
@ -190,7 +190,7 @@ impl PngData {
if aux_headers.get("IHDR").is_none() { if aux_headers.get("IHDR").is_none() {
return Err("Image header data was missing, skipping".to_owned()); 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, Ok(x) => x,
Err(x) => return Err(x), Err(x) => return Err(x),
}; };
@ -229,7 +229,7 @@ impl PngData {
// Return the PngData // Return the PngData
Ok(png_data) Ok(png_data)
} }
pub fn bits_per_pixel_raw(&self) -> u8 { pub fn channels_per_pixel(&self) -> u8 {
match self.ihdr_data.color_type { match self.ihdr_data.color_type {
ColorType::Grayscale => 1, ColorType::Grayscale => 1,
ColorType::RGB => 3, ColorType::RGB => 3,
@ -249,8 +249,8 @@ impl PngData {
ihdr_data.write_u32::<BigEndian>(self.ihdr_data.height).ok(); ihdr_data.write_u32::<BigEndian>(self.ihdr_data.height).ok();
ihdr_data.write_u8(self.ihdr_data.bit_depth.as_u8()).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(self.ihdr_data.color_type.png_header_code()).ok();
ihdr_data.write_u8(0).ok(); ihdr_data.write_u8(0).ok(); // Compression -- deflate
ihdr_data.write_u8(self.ihdr_data.filter).ok(); ihdr_data.write_u8(0).ok(); // Filter method -- 5-way adaptive filtering
ihdr_data.write_u8(self.ihdr_data.interlaced).ok(); ihdr_data.write_u8(self.ihdr_data.interlaced).ok();
output.reserve(ihdr_data.len() + 8); output.reserve(ihdr_data.len() + 8);
output.write_u32::<BigEndian>(ihdr_data.len() as u32 - 4).ok(); output.write_u32::<BigEndian>(ihdr_data.len() as u32 - 4).ok();
@ -330,11 +330,11 @@ 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());
// This avoids casting to and from floats, which is expensive // 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 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(line.filter); unfiltered.push(0);
match line.filter { match line.filter {
0 => { 0 => {
let mut data = line.data.clone(); let mut data = line.data.clone();
@ -414,7 +414,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());
// This avoids casting to and from floats, which is expensive // 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 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
@ -426,8 +426,7 @@ impl PngData {
} }
match filter { match filter {
0 => { 0 => {
let mut data = line.data.clone(); filtered.extend(&line.data);
filtered.append(&mut data);
} }
1 => { 1 => {
for (i, byte) in line.data.iter().enumerate() { for (i, byte) in line.data.iter().enumerate() {
@ -564,8 +563,7 @@ impl PngData {
} }
filtered.push(best.0); filtered.push(best.0);
let mut data = best.1.to_owned(); filtered.extend(best.1);
filtered.append(&mut data);
} }
_ => panic!("Unreachable"), _ => panic!("Unreachable"),
} }
@ -575,7 +573,13 @@ impl PngData {
} }
pub fn reduce_bit_depth(&mut self) -> bool { pub fn reduce_bit_depth(&mut self) -> bool {
if self.ihdr_data.bit_depth != BitDepth::Sixteen { 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; return false;
} }
@ -672,6 +676,10 @@ impl PngData {
} }
pub fn change_interlacing(&mut self, interlace: u8) -> bool { pub fn change_interlacing(&mut self, interlace: u8) -> bool {
// TODO: Implement // TODO: Implement
if interlace != self.ihdr_data.interlaced {
return false;
}
false false
} }
} }
@ -770,7 +778,7 @@ fn reduce_rgba_to_grayscale_alpha(png: &PngData) -> Option<Vec<u8>> {
fn reduce_rgba_to_palette(png: &PngData) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)> { fn reduce_rgba_to_palette(png: &PngData) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)> {
let mut reduced = Vec::with_capacity(png.raw_data.len()); 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 byte_depth: usize = (png.ihdr_data.bit_depth.as_u8() >> 3) as usize;
let bpp: usize = 4 * byte_depth; let bpp: usize = 4 * byte_depth;
for line in png.scan_lines() { for line in png.scan_lines() {
@ -779,13 +787,14 @@ fn reduce_rgba_to_palette(png: &PngData) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)>
for (i, byte) in line.data.iter().enumerate() { for (i, byte) in line.data.iter().enumerate() {
cur_pixel.push(*byte); cur_pixel.push(*byte);
if i % bpp == bpp - 1 { if i % bpp == bpp - 1 {
if !palette.contains(&cur_pixel) { if !palette.contains_key(&cur_pixel) {
palette.push(cur_pixel.clone()); let len = palette.len();
if len >= 255 {
return None;
}
palette.insert(cur_pixel.clone(), len as u8);
} }
if palette.len() > 255 { reduced.push(palette.get(&cur_pixel).unwrap().clone());
return None;
}
reduced.push(palette.binary_search(&cur_pixel).unwrap() as u8);
cur_pixel.clear(); cur_pixel.clear();
} }
} }
@ -793,7 +802,7 @@ fn reduce_rgba_to_palette(png: &PngData) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)>
let mut color_palette = Vec::with_capacity(palette.len() * byte_depth * 3); let mut color_palette = Vec::with_capacity(palette.len() * byte_depth * 3);
let mut trans_palette = Vec::with_capacity(palette.len() * byte_depth); 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() { for (i, byte) in color.iter().enumerate() {
if i < byte_depth * 3 { if i < byte_depth * 3 {
color_palette.push(*byte); color_palette.push(*byte);
@ -808,7 +817,7 @@ fn reduce_rgba_to_palette(png: &PngData) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)>
fn reduce_rgb_to_palette(png: &PngData) -> Option<(Vec<u8>, Vec<u8>)> { fn reduce_rgb_to_palette(png: &PngData) -> Option<(Vec<u8>, Vec<u8>)> {
let mut reduced = Vec::with_capacity(png.raw_data.len()); 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 byte_depth: usize = (png.ihdr_data.bit_depth.as_u8() >> 3) as usize;
let bpp: usize = 3 * byte_depth; let bpp: usize = 3 * byte_depth;
for line in png.scan_lines() { for line in png.scan_lines() {
@ -817,20 +826,21 @@ fn reduce_rgb_to_palette(png: &PngData) -> Option<(Vec<u8>, Vec<u8>)> {
for (i, byte) in line.data.iter().enumerate() { for (i, byte) in line.data.iter().enumerate() {
cur_pixel.push(*byte); cur_pixel.push(*byte);
if i % bpp == bpp - 1 { if i % bpp == bpp - 1 {
if !palette.contains(&cur_pixel) { if !palette.contains_key(&cur_pixel) {
palette.push(cur_pixel.clone()); let len = palette.len();
if len >= 255 {
return None;
}
palette.insert(cur_pixel.clone(), len as u8);
} }
if palette.len() > 255 { reduced.push(palette.get(&cur_pixel).unwrap().clone());
return None;
}
reduced.push(palette.binary_search(&cur_pixel).unwrap() as u8);
cur_pixel.clear(); cur_pixel.clear();
} }
} }
} }
let mut color_palette = Vec::with_capacity(palette.len() * byte_depth * 3); let mut color_palette = Vec::with_capacity(palette.len() * byte_depth * 3);
for color in palette { for (color, _) in palette {
color_palette.extend(&color); color_palette.extend(&color);
} }