Get zlib compression to work
This commit is contained in:
parent
59609983a9
commit
052b047222
7 changed files with 304 additions and 82 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
target
|
target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
*.bk
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,7 @@ version = "2.0.0"
|
||||||
authors = ["Joshua Holmer <jholmer.in@gmail.com>"]
|
authors = ["Joshua Holmer <jholmer.in@gmail.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "0.4."
|
byteorder = "~0.4.0"
|
||||||
crc = "^1.0.0"
|
crc = "^1.0.0"
|
||||||
|
libz-sys = "^1.0.0"
|
||||||
|
libc = "~0.2.4"
|
||||||
|
|
|
||||||
37
src/deflate/deflate.rs
Normal file
37
src/deflate/deflate.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
use libz_sys;
|
||||||
|
use libc;
|
||||||
|
|
||||||
|
pub fn inflate(data: &[u8]) -> Result<Vec<u8>, String> {
|
||||||
|
let mut input = data.to_owned();
|
||||||
|
let mut stream = super::stream::Stream::new_decompress();
|
||||||
|
let mut output = Vec::with_capacity(data.len());
|
||||||
|
loop {
|
||||||
|
match stream.decompress_vec(input.as_mut(), output.as_mut()) {
|
||||||
|
libz_sys::Z_OK => output.reserve(data.len()),
|
||||||
|
libz_sys::Z_STREAM_END => break,
|
||||||
|
c => return Err(format!("Error code on decompress: {}", c)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output.shrink_to_fit();
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deflate(data: &[u8], zc: u8, zm: u8, zs: u8, zw: u16) -> Result<Vec<u8>, String> {
|
||||||
|
let mut input = data.to_owned();
|
||||||
|
let mut stream = super::stream::Stream::new_compress(zc as libc::c_int,
|
||||||
|
zw as libc::c_int,
|
||||||
|
zm as libc::c_int,
|
||||||
|
zs as libc::c_int);
|
||||||
|
let mut output = Vec::with_capacity(data.len() / 20);
|
||||||
|
loop {
|
||||||
|
match stream.compress_vec(input.as_mut(), output.as_mut()) {
|
||||||
|
libz_sys::Z_OK => output.reserve(data.len() / 20),
|
||||||
|
libz_sys::Z_STREAM_END => break,
|
||||||
|
c => return Err(format!("Error code on compress: {}", c)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output.shrink_to_fit();
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
130
src/deflate/stream.rs
Normal file
130
src/deflate/stream.rs
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
// Raw un-exported bindings to libz for encoding/decoding
|
||||||
|
// Copyright (c) 2014 Alex Crichton, MIT & Apache licenses
|
||||||
|
// Originally from flate2 crate for miniz
|
||||||
|
// Modified for use in Optipng
|
||||||
|
|
||||||
|
use std::marker;
|
||||||
|
use std::mem;
|
||||||
|
use libc::{c_int, c_uint};
|
||||||
|
use libz_sys;
|
||||||
|
|
||||||
|
pub struct Stream<D: Direction> {
|
||||||
|
raw: libz_sys::z_stream,
|
||||||
|
_marker: marker::PhantomData<D>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Compress {}
|
||||||
|
pub enum Decompress {}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub trait Direction {
|
||||||
|
unsafe fn destroy(stream: *mut libz_sys::z_stream) -> c_int;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream<Compress> {
|
||||||
|
pub fn new_compress(lvl: c_int,
|
||||||
|
window_bits: c_int,
|
||||||
|
mem_size: c_int,
|
||||||
|
strategy: c_int)
|
||||||
|
-> Stream<Compress> {
|
||||||
|
unsafe {
|
||||||
|
let mut state: libz_sys::z_stream = mem::zeroed();
|
||||||
|
let ret = libz_sys::deflateInit2_(&mut state,
|
||||||
|
lvl,
|
||||||
|
libz_sys::Z_DEFLATED,
|
||||||
|
window_bits,
|
||||||
|
mem_size,
|
||||||
|
strategy,
|
||||||
|
libz_sys::zlibVersion(),
|
||||||
|
mem::size_of::<libz_sys::z_stream>() as i32);
|
||||||
|
debug_assert_eq!(ret, 0);
|
||||||
|
Stream {
|
||||||
|
raw: state,
|
||||||
|
_marker: marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_decompress() -> Stream<Decompress> {
|
||||||
|
unsafe {
|
||||||
|
let mut state: libz_sys::z_stream = mem::zeroed();
|
||||||
|
let ret = libz_sys::inflateInit2_(&mut state,
|
||||||
|
15,
|
||||||
|
libz_sys::zlibVersion(),
|
||||||
|
mem::size_of::<libz_sys::z_stream>() as i32);
|
||||||
|
debug_assert_eq!(ret, 0);
|
||||||
|
Stream {
|
||||||
|
raw: state,
|
||||||
|
_marker: marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Direction> Stream<T> {
|
||||||
|
pub fn total_in(&self) -> u64 {
|
||||||
|
self.raw.total_in as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn total_out(&self) -> u64 {
|
||||||
|
self.raw.total_out as u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream<Decompress> {
|
||||||
|
pub fn decompress_vec(&mut self, input: &mut [u8], output: &mut Vec<u8>) -> c_int {
|
||||||
|
self.raw.avail_in = (input.len() - self.total_in() as usize) as c_uint;
|
||||||
|
self.raw.avail_out = (output.capacity() - self.total_out() as usize) as c_uint;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
self.raw.next_in = input.as_mut_ptr().offset(self.total_in() as isize);
|
||||||
|
self.raw.next_out = output.as_mut_ptr().offset(self.total_out() as isize);
|
||||||
|
let rc = libz_sys::inflate(&mut self.raw, libz_sys::Z_NO_FLUSH);
|
||||||
|
output.set_len(self.total_out() as usize);
|
||||||
|
rc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream<Compress> {
|
||||||
|
pub fn compress_vec(&mut self, input: &mut [u8], output: &mut Vec<u8>) -> c_int {
|
||||||
|
self.raw.avail_in = (input.len() - self.total_in() as usize) as c_uint;
|
||||||
|
self.raw.avail_out = (output.capacity() - self.total_out() as usize) as c_uint;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
self.raw.next_in = input.as_mut_ptr().offset(self.total_in() as isize);
|
||||||
|
self.raw.next_out = output.as_mut_ptr().offset(self.total_out() as isize);
|
||||||
|
let rc = libz_sys::deflate(&mut self.raw,
|
||||||
|
if self.raw.avail_in > 0 {
|
||||||
|
libz_sys::Z_NO_FLUSH
|
||||||
|
} else {
|
||||||
|
libz_sys::Z_FINISH
|
||||||
|
});
|
||||||
|
output.set_len(self.total_out() as usize);
|
||||||
|
rc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) -> c_int {
|
||||||
|
unsafe { libz_sys::deflateReset(&mut self.raw) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Direction for Compress {
|
||||||
|
unsafe fn destroy(stream: *mut libz_sys::z_stream) -> c_int {
|
||||||
|
libz_sys::deflateEnd(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Direction for Decompress {
|
||||||
|
unsafe fn destroy(stream: *mut libz_sys::z_stream) -> c_int {
|
||||||
|
libz_sys::inflateEnd(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Direction> Drop for Stream<D> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
let _ = <D as Direction>::destroy(&mut self.raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
110
src/lib.rs
110
src/lib.rs
|
|
@ -1,10 +1,16 @@
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate crc;
|
extern crate crc;
|
||||||
|
extern crate libz_sys;
|
||||||
|
extern crate libc;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
mod png;
|
pub mod png;
|
||||||
|
pub mod deflate {
|
||||||
|
pub mod deflate;
|
||||||
|
pub mod stream;
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Options<'a> {
|
pub struct Options<'a> {
|
||||||
pub backup: bool,
|
pub backup: bool,
|
||||||
|
|
@ -20,7 +26,7 @@ pub struct Options<'a> {
|
||||||
pub compression: HashSet<u8>,
|
pub compression: HashSet<u8>,
|
||||||
pub zm: HashSet<u8>,
|
pub zm: HashSet<u8>,
|
||||||
pub zs: HashSet<u8>,
|
pub zs: HashSet<u8>,
|
||||||
pub zw: u32,
|
pub zw: u16,
|
||||||
pub bit_depth_reduction: bool,
|
pub bit_depth_reduction: bool,
|
||||||
pub color_type_reduction: bool,
|
pub color_type_reduction: bool,
|
||||||
pub palette_reduction: bool,
|
pub palette_reduction: bool,
|
||||||
|
|
@ -28,61 +34,89 @@ pub struct Options<'a> {
|
||||||
pub idat_paranoia: bool,
|
pub idat_paranoia: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Processing: /Users/holmerj/Downloads/renpy-6.99.7-sdk/doc/_images/frame_example.png
|
|
||||||
// 579x354 pixels, PNG format
|
|
||||||
// 3x8 bits/pixel, RGB
|
|
||||||
// IDAT size = 45711 bytes
|
|
||||||
// file size = 45881 bytes
|
|
||||||
// Trying: 8 combinations
|
|
||||||
// Output:
|
|
||||||
// IDAT size = 45711 bytes (no change)
|
|
||||||
// file size = 45821 bytes (60 bytes = 0.13% decrease)
|
|
||||||
pub fn optimize(filepath: &Path, opts: Options) -> Result<(), String> {
|
pub fn optimize(filepath: &Path, opts: Options) -> Result<(), String> {
|
||||||
// Decode PNG from file
|
// Decode PNG from file
|
||||||
println!("Processing: {}", filepath.to_str().unwrap());
|
println!("Processing: {}", filepath.to_str().unwrap());
|
||||||
let in_file = Path::new(filepath);
|
let in_file = Path::new(filepath);
|
||||||
let png = match png::PngData::new(&in_file) {
|
let mut png = match png::PngData::new(&in_file) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => return Err(x)
|
Err(x) => return Err(x),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Print png info
|
// Print png info
|
||||||
let idat_current_size = png.idat_data.len();
|
let idat_current_size = png.idat_data.len();
|
||||||
let file_current_size = filepath.metadata().unwrap().len();
|
let file_current_size = filepath.metadata().unwrap().len();
|
||||||
if opts.verbosity.is_some() {
|
if opts.verbosity.is_some() {
|
||||||
println!(" {}x{} pixels, PNG format", png.ihdr_data.width, png.ihdr_data.height);
|
println!(" {}x{} pixels, PNG format",
|
||||||
|
png.ihdr_data.width,
|
||||||
|
png.ihdr_data.height);
|
||||||
if let Some(palette) = png.palette.clone() {
|
if let Some(palette) = png.palette.clone() {
|
||||||
println!(" {} bits/pixel, {} colors in palette", png.ihdr_data.bit_depth, palette.len() / 3);
|
println!(" {} bits/pixel, {} colors in palette",
|
||||||
|
png.ihdr_data.bit_depth,
|
||||||
|
palette.len() / 3);
|
||||||
} else {
|
} else {
|
||||||
println!(" {}x{} bits/pixel, {:?}", png.bits_per_pixel(), png.ihdr_data.bit_depth, png.ihdr_data.color_type);
|
println!(" {}x{} bits/pixel, {:?}",
|
||||||
|
png.bits_per_pixel(),
|
||||||
|
png.ihdr_data.bit_depth,
|
||||||
|
png.ihdr_data.color_type);
|
||||||
}
|
}
|
||||||
println!(" IDAT size = {} bytes", idat_current_size);
|
println!(" IDAT size = {} bytes", idat_current_size);
|
||||||
println!(" File size = {} bytes", file_current_size);
|
println!(" File size = {} bytes", file_current_size);
|
||||||
}
|
}
|
||||||
|
// TODO: Bit depth/palette reduction
|
||||||
//
|
//
|
||||||
// // TODO: Bit depth/palette reduction
|
// TODO: Apply interlacing changes
|
||||||
|
// TODO: Force reencoding if interlacing was changed
|
||||||
//
|
//
|
||||||
// // Go through selected permutations and determine the best
|
// Go through selected permutations and determine the best
|
||||||
// let mut best: (Option<(u8, u8, u8, u8)>, usize) = (None, idat_current_size.clone());
|
let mut best: Option<(u8, u8, u8, u8)> = None;
|
||||||
// if opts.idat_recoding {
|
if opts.idat_recoding {
|
||||||
// let combinations = opts.f.len() * opts.zc.len() * opts.zm.len() * opts.zs.len();
|
let combinations = opts.filter.len() * opts.compression.len() * opts.zm.len() *
|
||||||
// println!("Trying: {} combinations", combinations);
|
opts.zs.len();
|
||||||
// // TODO: Multithreading
|
println!("Trying: {} combinations", combinations);
|
||||||
// for f in &opts.f {
|
// TODO: Multithreading
|
||||||
// for zc in &opts.zc {
|
for f in &opts.filter {
|
||||||
// for zm in &opts.zm {
|
for zc in &opts.compression {
|
||||||
// for zs in &opts.zs {
|
for zm in &opts.zm {
|
||||||
// // TODO: Test compressions
|
for zs in &opts.zs {
|
||||||
// let new_idat = compress::compress(img_buf.clone(), *f, opts.i, *zc, *zm, *zs, opts.zw);
|
let new_idat = match deflate::deflate::deflate(png.raw_data.as_ref(),
|
||||||
// // TODO: Force reencoding if interlacing was changed
|
*zc,
|
||||||
// if new_idat.len() < best.1 {
|
*zm,
|
||||||
// best = (Some((*f, *zc, *zm, *zs)), new_idat.len());
|
*zs,
|
||||||
// }
|
opts.zw) {
|
||||||
// }
|
Ok(x) => x,
|
||||||
// }
|
Err(x) => return Err(x),
|
||||||
// }
|
};
|
||||||
// }
|
// TODO: Apply filtering
|
||||||
// }
|
if new_idat.len() < png.idat_data.len() {
|
||||||
|
best = Some((*f, *zc, *zm, *zs));
|
||||||
|
png.idat_data = new_idat.clone();
|
||||||
|
}
|
||||||
|
if opts.verbosity == Some(1) {
|
||||||
|
println!(" zc = {} zm = {} zs = {} f = {} {} bytes",
|
||||||
|
*zc,
|
||||||
|
*zm,
|
||||||
|
*zs,
|
||||||
|
*f,
|
||||||
|
new_idat.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(better) = best {
|
||||||
|
println!("Found better combination:");
|
||||||
|
println!(" zc = {} zm = {} zs = {} f = {} {} bytes",
|
||||||
|
better.1,
|
||||||
|
better.2,
|
||||||
|
better.3,
|
||||||
|
better.0,
|
||||||
|
png.idat_data.len());
|
||||||
|
} else {
|
||||||
|
println!("IDAT already optimized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Backup before writing?
|
// TODO: Backup before writing?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ fn main() {
|
||||||
compression: compression,
|
compression: compression,
|
||||||
zm: zm,
|
zm: zm,
|
||||||
zs: zs,
|
zs: zs,
|
||||||
zw: 4096,
|
zw: 15,
|
||||||
bit_depth_reduction: true,
|
bit_depth_reduction: true,
|
||||||
color_type_reduction: true,
|
color_type_reduction: true,
|
||||||
palette_reduction: true,
|
palette_reduction: true,
|
||||||
|
|
@ -43,5 +43,8 @@ fn main() {
|
||||||
};
|
};
|
||||||
// TODO: Handle command line args
|
// TODO: Handle command line args
|
||||||
// TODO: Handle optimization presets
|
// TODO: Handle optimization presets
|
||||||
optipng::optimize(infile, default_opts);
|
match optipng::optimize(infile, default_opts) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(x) => panic!(x),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
97
src/png.rs
97
src/png.rs
|
|
@ -18,13 +18,15 @@ pub enum ColorType {
|
||||||
|
|
||||||
impl fmt::Display for ColorType {
|
impl fmt::Display for ColorType {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}", match *self {
|
write!(f,
|
||||||
ColorType::Grayscale => "Grayscale",
|
"{}",
|
||||||
ColorType::RGB => "RGB",
|
match *self {
|
||||||
ColorType::Indexed => "Indexed",
|
ColorType::Grayscale => "Grayscale",
|
||||||
ColorType::GrayscaleAlpha => "Grayscale + Alpha",
|
ColorType::RGB => "RGB",
|
||||||
ColorType::RGBA => "RGB + Alpha",
|
ColorType::Indexed => "Indexed",
|
||||||
})
|
ColorType::GrayscaleAlpha => "Grayscale + Alpha",
|
||||||
|
ColorType::RGBA => "RGB + Alpha",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,13 +41,15 @@ pub enum BitDepth {
|
||||||
|
|
||||||
impl fmt::Display for BitDepth {
|
impl fmt::Display for BitDepth {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}", match *self {
|
write!(f,
|
||||||
BitDepth::One => "1",
|
"{}",
|
||||||
BitDepth::Two => "2",
|
match *self {
|
||||||
BitDepth::Four => "4",
|
BitDepth::One => "1",
|
||||||
BitDepth::Eight => "8",
|
BitDepth::Two => "2",
|
||||||
BitDepth::Sixteen => "16",
|
BitDepth::Four => "4",
|
||||||
})
|
BitDepth::Eight => "8",
|
||||||
|
BitDepth::Sixteen => "16",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,6 +57,7 @@ impl fmt::Display for BitDepth {
|
||||||
pub struct PngData {
|
pub struct PngData {
|
||||||
pub idat_data: Vec<u8>,
|
pub idat_data: Vec<u8>,
|
||||||
pub ihdr_data: IhdrData,
|
pub ihdr_data: IhdrData,
|
||||||
|
pub raw_data: Vec<u8>,
|
||||||
pub palette: Option<Vec<u8>>,
|
pub palette: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,13 +76,13 @@ impl PngData {
|
||||||
pub fn new(filepath: &Path) -> Result<PngData, String> {
|
pub fn new(filepath: &Path) -> Result<PngData, String> {
|
||||||
let mut file = match File::open(filepath) {
|
let mut file = match File::open(filepath) {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(_) => return Err("Failed to open file for reading".to_owned())
|
Err(_) => return Err("Failed to open file for reading".to_owned()),
|
||||||
};
|
};
|
||||||
let mut byte_data: Vec<u8> = Vec::new();
|
let mut byte_data: Vec<u8> = Vec::new();
|
||||||
// Read raw png data into memory
|
// Read raw png data into memory
|
||||||
match file.read_to_end(&mut byte_data) {
|
match file.read_to_end(&mut byte_data) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(_) => return Err("Failed to read from file".to_owned())
|
Err(_) => return Err("Failed to read from file".to_owned()),
|
||||||
}
|
}
|
||||||
let mut byte_offset: usize = 0;
|
let mut byte_offset: usize = 0;
|
||||||
// Test that png header is valid
|
// Test that png header is valid
|
||||||
|
|
@ -93,11 +98,11 @@ impl PngData {
|
||||||
let header = parse_next_header(byte_data.as_ref(), &mut byte_offset);
|
let header = parse_next_header(byte_data.as_ref(), &mut byte_offset);
|
||||||
let header = match header {
|
let header = match header {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => return Err(x)
|
Err(x) => return Err(x),
|
||||||
};
|
};
|
||||||
let header = match header {
|
let header = match header {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => break
|
None => break,
|
||||||
};
|
};
|
||||||
if header.0 == "IDAT" {
|
if header.0 == "IDAT" {
|
||||||
idat_headers.extend(header.1);
|
idat_headers.extend(header.1);
|
||||||
|
|
@ -106,33 +111,29 @@ impl PngData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Parse the headers into our PngData
|
// Parse the headers into our PngData
|
||||||
if idat_headers.len() == 0 {
|
if idat_headers.is_empty() {
|
||||||
return Err("Image data was empty, skipping".to_owned());
|
return Err("Image data was empty, skipping".to_owned());
|
||||||
}
|
}
|
||||||
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 = parse_ihdr_header(aux_headers.get("IHDR").unwrap().as_ref());
|
let ihdr_header = parse_ihdr_header(aux_headers.get("IHDR").unwrap().as_ref());
|
||||||
|
let raw_data = match super::deflate::deflate::inflate(idat_headers.as_ref()) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(x) => return Err(x),
|
||||||
|
};
|
||||||
|
// TODO: Reverse filtering?
|
||||||
// Return the PngData
|
// Return the PngData
|
||||||
Ok(PngData {
|
Ok(PngData {
|
||||||
idat_data: idat_headers,
|
idat_data: idat_headers.clone(),
|
||||||
ihdr_data: match ihdr_header {
|
ihdr_data: match ihdr_header {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => return Err(x)
|
Err(x) => return Err(x),
|
||||||
},
|
},
|
||||||
|
raw_data: raw_data,
|
||||||
palette: aux_headers.get("PLTE").cloned(),
|
palette: aux_headers.get("PLTE").cloned(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn buffer_size(&self) -> usize {
|
|
||||||
// Return the size needed to hold a decoded frame
|
|
||||||
self.ihdr_data.width * self.ihdr_data.height * match self.ihdr_data.bit_depth {
|
|
||||||
BitDepth::One => 1,
|
|
||||||
BitDepth::Two => 2,
|
|
||||||
BitDepth::Four => 4,
|
|
||||||
BitDepth::Eight => 8,
|
|
||||||
BitDepth::Sixteen => 16
|
|
||||||
} as usize
|
|
||||||
}
|
|
||||||
pub fn bits_per_pixel(&self) -> u8 {
|
pub fn bits_per_pixel(&self) -> u8 {
|
||||||
match self.ihdr_data.color_type {
|
match self.ihdr_data.color_type {
|
||||||
ColorType::Grayscale => 1,
|
ColorType::Grayscale => 1,
|
||||||
|
|
@ -150,31 +151,45 @@ fn file_header_is_valid(bytes: &[u8]) -> bool {
|
||||||
bytes.iter().zip(expected_header.iter()).all(|x| x.0 == x.1)
|
bytes.iter().zip(expected_header.iter()).all(|x| x.0 == x.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_next_header(byte_data: &[u8], byte_offset: &mut usize) -> Result<Option<(String, Vec<u8>)>, String> {
|
fn parse_next_header(byte_data: &[u8],
|
||||||
let mut rdr = Cursor::new(byte_data.iter().skip(*byte_offset).take(4).cloned().collect::<Vec<u8>>());
|
byte_offset: &mut usize)
|
||||||
|
-> Result<Option<(String, Vec<u8>)>, String> {
|
||||||
|
let mut rdr = Cursor::new(byte_data.iter()
|
||||||
|
.skip(*byte_offset)
|
||||||
|
.take(4)
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<u8>>());
|
||||||
let length: u32 = match rdr.read_u32::<BigEndian>() {
|
let length: u32 = match rdr.read_u32::<BigEndian>() {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(_) => return Err("Invalid data found--unable to read PNG file".to_owned())
|
Err(_) => return Err("Invalid data found--unable to read PNG file".to_owned()),
|
||||||
};
|
};
|
||||||
*byte_offset += 4;
|
*byte_offset += 4;
|
||||||
|
|
||||||
let mut header_bytes: Vec<u8> = byte_data.iter().skip(*byte_offset).take(4).cloned().collect();
|
let mut header_bytes: Vec<u8> = byte_data.iter().skip(*byte_offset).take(4).cloned().collect();
|
||||||
let header = match String::from_utf8(header_bytes.clone()) {
|
let header = match String::from_utf8(header_bytes.clone()) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(_) => return Err("Invalid data found--unable to read PNG file".to_owned())
|
Err(_) => return Err("Invalid data found--unable to read PNG file".to_owned()),
|
||||||
};
|
};
|
||||||
if header == "IEND".to_owned() {
|
if header == "IEND" {
|
||||||
// End of data
|
// End of data
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
*byte_offset += 4;
|
*byte_offset += 4;
|
||||||
|
|
||||||
let data: Vec<u8> = byte_data.iter().skip(*byte_offset).take(length as usize).cloned().collect();
|
let data: Vec<u8> = byte_data.iter()
|
||||||
|
.skip(*byte_offset)
|
||||||
|
.take(length as usize)
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
*byte_offset += length as usize;
|
*byte_offset += length as usize;
|
||||||
let mut rdr = Cursor::new(byte_data.iter().skip(*byte_offset).take(4).cloned().collect::<Vec<u8>>());
|
let mut rdr = Cursor::new(byte_data.iter()
|
||||||
|
.skip(*byte_offset)
|
||||||
|
.take(4)
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<u8>>());
|
||||||
let crc: u32 = match rdr.read_u32::<BigEndian>() {
|
let crc: u32 = match rdr.read_u32::<BigEndian>() {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(_) => return Err("Invalid data found--unable to read PNG file".to_owned())
|
Err(_) => return Err("Invalid data found--unable to read PNG file".to_owned()),
|
||||||
};
|
};
|
||||||
*byte_offset += 4;
|
*byte_offset += 4;
|
||||||
header_bytes.extend(data.clone());
|
header_bytes.extend(data.clone());
|
||||||
|
|
@ -194,7 +209,7 @@ fn parse_ihdr_header(byte_data: &[u8]) -> Result<IhdrData, String> {
|
||||||
3 => ColorType::Indexed,
|
3 => ColorType::Indexed,
|
||||||
4 => ColorType::GrayscaleAlpha,
|
4 => ColorType::GrayscaleAlpha,
|
||||||
6 => ColorType::RGBA,
|
6 => ColorType::RGBA,
|
||||||
_ => return Err("Unexpected color type in header".to_owned())
|
_ => return Err("Unexpected color type in header".to_owned()),
|
||||||
},
|
},
|
||||||
bit_depth: match byte_data[8] {
|
bit_depth: match byte_data[8] {
|
||||||
1 => BitDepth::One,
|
1 => BitDepth::One,
|
||||||
|
|
@ -202,7 +217,7 @@ fn parse_ihdr_header(byte_data: &[u8]) -> Result<IhdrData, String> {
|
||||||
4 => BitDepth::Four,
|
4 => BitDepth::Four,
|
||||||
8 => BitDepth::Eight,
|
8 => BitDepth::Eight,
|
||||||
16 => BitDepth::Sixteen,
|
16 => BitDepth::Sixteen,
|
||||||
_ => return Err("Unexpected bit depth in header".to_owned())
|
_ => return Err("Unexpected bit depth in header".to_owned()),
|
||||||
},
|
},
|
||||||
width: rdr.read_u32::<BigEndian>().unwrap(),
|
width: rdr.read_u32::<BigEndian>().unwrap(),
|
||||||
height: rdr.read_u32::<BigEndian>().unwrap(),
|
height: rdr.read_u32::<BigEndian>().unwrap(),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue