diff --git a/Cargo.toml b/Cargo.toml index ffe36b42..4e4d7b5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "1.0.0-alpha.1" authors = ["Joshua Holmer "] [dependencies] +bit-vec = "^0.4.2" byteorder = "^0.4.0" clap = "^1.5.4" crc = "^1.0.0" diff --git a/src/lib.rs b/src/lib.rs index 033bee80..22b8da1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +extern crate bit_vec; extern crate byteorder; extern crate crc; extern crate libc; diff --git a/src/png.rs b/src/png.rs index 54720de9..4d345b6b 100644 --- a/src/png.rs +++ b/src/png.rs @@ -1,10 +1,10 @@ -use std::io::Cursor; +use bit_vec::BitVec; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use crc::crc32; use std::collections::HashMap; -use std::collections::HashSet; use std::fmt; use std::fs::File; +use std::io::Cursor; use std::io::prelude::*; use std::iter::Iterator; use std::path::Path; @@ -77,6 +77,16 @@ impl BitDepth { BitDepth::Sixteen => 16, } } + fn from_u8(depth: u8) -> BitDepth { + match depth { + 1 => BitDepth::One, + 2 => BitDepth::Two, + 4 => BitDepth::Four, + 8 => BitDepth::Eight, + 16 => BitDepth::Sixteen, + _ => panic!("Unsupported bit depth") + } + } } struct ScanLines<'a> { @@ -598,6 +608,7 @@ impl PngData { } pub fn reduce_color_type(&mut self) -> bool { let mut changed = false; + let mut should_reduce_bit_depth = false; // Go down one step at a time // Maybe not the most efficient, but it's safe @@ -617,6 +628,7 @@ impl PngData { self.transparency_palette = Some(trans); self.ihdr_data.color_type = ColorType::Indexed; changed = true; + should_reduce_bit_depth = true; } } @@ -626,18 +638,15 @@ impl PngData { self.palette = Some(palette); self.ihdr_data.color_type = ColorType::Indexed; changed = true; + should_reduce_bit_depth = true; } } - if self.ihdr_data.color_type == ColorType::Indexed { - if let Some((data, alpha)) = reduce_palette_to_grayscale(self) { + if self.ihdr_data.color_type == ColorType::Indexed && self.transparency_palette.is_none() { + if let Some(data) = reduce_palette_to_grayscale(self) { self.raw_data = data; self.palette = None; - if alpha { - self.ihdr_data.color_type = ColorType::GrayscaleAlpha; - } else { - self.ihdr_data.color_type = ColorType::Grayscale; - } + self.ihdr_data.color_type = ColorType::Grayscale; changed = true; } } @@ -646,9 +655,18 @@ impl PngData { if let Some(data) = reduce_grayscale_alpha_to_grayscale(self) { self.raw_data = data; changed = true; + should_reduce_bit_depth = true; } } + if should_reduce_bit_depth { + if let Some((data, depth)) = reduce_bit_depth_8_or_less(self) { + self.raw_data = data; + self.ihdr_data.bit_depth = BitDepth::from_u8(depth); + } + + } + changed } pub fn change_interlacing(&mut self, interlace: u8) -> bool { @@ -657,6 +675,39 @@ impl PngData { } } +fn reduce_bit_depth_8_or_less(png: &PngData) -> Option<(Vec, u8)> { + 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; + for line in png.scan_lines() { + let bit_vec = BitVec::from_bytes(&line.data); + for (i, bit) in bit_vec.iter().enumerate() { + let bit_index = bit_depth - (i % bit_depth); + if bit && bit_index > allowed_bits { + allowed_bits = bit_index.next_power_of_two(); + if allowed_bits == bit_depth { + // Not reducable + return None; + } + } + } + } + + for line in png.scan_lines() { + // I hate having to iterate twice... + reduced.extend(BitVec::from_bytes(&[line.filter])); + let bit_vec = BitVec::from_bytes(&line.data); + for (i, bit) in bit_vec.iter().enumerate() { + let bit_index = bit_depth - (i % bit_depth); + if bit_index <= allowed_bits { + reduced.push(bit); + } + } + } + + Some((reduced.to_bytes(), allowed_bits as u8)) +} + fn reduce_rgba_to_rgb(png: &PngData) -> Option> { let mut reduced = Vec::with_capacity(png.raw_data.len()); let byte_depth = png.ihdr_data.bit_depth.as_u8() >> 3; @@ -755,31 +806,62 @@ fn reduce_rgba_to_palette(png: &PngData) -> Option<(Vec, Vec, Vec)> } fn reduce_rgb_to_palette(png: &PngData) -> Option<(Vec, Vec)> { - // TODO: Implement let mut reduced = Vec::with_capacity(png.raw_data.len()); - let byte_depth = png.ihdr_data.bit_depth.as_u8() >> 3; - let bpp: usize = 3 * byte_depth as usize; + let mut palette = Vec::with_capacity(256); + 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() { reduced.push(line.filter); + let mut cur_pixel = Vec::with_capacity(bpp); 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.len() > 255 { + return None; + } + reduced.push(palette.binary_search(&cur_pixel).unwrap() as u8); + cur_pixel.clear(); + } } } - None + let mut color_palette = Vec::with_capacity(palette.len() * byte_depth * 3); + for color in palette { + color_palette.extend(&color); + } + + Some((reduced, color_palette)) } -fn reduce_palette_to_grayscale(png: &PngData) -> Option<(Vec, bool)> { - // TODO: Implement - let mut reduced = Vec::with_capacity(png.raw_data.len()); - let bit_depth = png.ihdr_data.bit_depth.as_u8(); - for line in png.scan_lines() { - reduced.push(line.filter); - for (i, byte) in line.data.iter().enumerate() { - let mut cur_bit = 0; - while cur_bit < 8 { +fn reduce_palette_to_grayscale(png: &PngData) -> Option> { + let mut reduced = BitVec::with_capacity(png.raw_data.len() * 8); + let mut cur_pixel = Vec::with_capacity(3); + let palette = png.palette.clone().unwrap(); + for byte in &palette { + cur_pixel.push(*byte); + if cur_pixel.len() == 3 { + cur_pixel.sort(); + cur_pixel.dedup(); + if cur_pixel.len() > 1 { + return None; + } + cur_pixel.clear(); + } + } - cur_bit += bit_depth; + let bit_depth: usize = png.ihdr_data.bit_depth.as_u8() as usize; + 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(bit_depth); + for bit in bit_vec { + cur_pixel.push(bit); + if cur_pixel.len() == bit_depth { + let palette_idx: usize = ((cur_pixel.to_bytes()[0] - 1) * 3) as usize; + reduced.extend(BitVec::from_bytes(&[palette[palette_idx]])); } } }