parent
911f51faa9
commit
eba8062ba5
4 changed files with 99 additions and 0 deletions
|
|
@ -1,3 +1,6 @@
|
||||||
|
**Version 0.2.1**
|
||||||
|
- Add rustdoc for public methods and structs
|
||||||
|
|
||||||
**Version 0.2.0**
|
**Version 0.2.0**
|
||||||
- Fix program version that is displayed when running `oxipng -V`
|
- Fix program version that is displayed when running `oxipng -V`
|
||||||
- Ensure `--quiet` mode is actually quiet (@SethDusek [#20](https://github.com/shssoichiro/oxipng/pull/20))
|
- Ensure `--quiet` mode is actually quiet (@SethDusek [#20](https://github.com/shssoichiro/oxipng/pull/20))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use libz_sys;
|
use libz_sys;
|
||||||
use libc::c_int;
|
use libc::c_int;
|
||||||
|
|
||||||
|
/// Decompress a data stream using the DEFLATE algorithm
|
||||||
pub fn inflate(data: &[u8]) -> Result<Vec<u8>, String> {
|
pub fn inflate(data: &[u8]) -> Result<Vec<u8>, String> {
|
||||||
let mut input = data.to_owned();
|
let mut input = data.to_owned();
|
||||||
let mut stream = super::stream::Stream::new_decompress();
|
let mut stream = super::stream::Stream::new_decompress();
|
||||||
|
|
@ -17,6 +18,7 @@ pub fn inflate(data: &[u8]) -> Result<Vec<u8>, String> {
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compress a data stream using the zlib implementation of the DEFLATE algorithm
|
||||||
pub fn deflate(data: &[u8], zc: u8, zm: u8, zs: u8, zw: u8) -> Result<Vec<u8>, String> {
|
pub fn deflate(data: &[u8], zc: u8, zm: u8, zs: u8, zw: u8) -> Result<Vec<u8>, String> {
|
||||||
let mut input = data.to_owned();
|
let mut input = data.to_owned();
|
||||||
let mut stream = super::stream::Stream::new_compress(zc as c_int,
|
let mut stream = super::stream::Stream::new_compress(zc as c_int,
|
||||||
|
|
|
||||||
33
src/lib.rs
33
src/lib.rs
|
|
@ -17,33 +17,66 @@ pub mod deflate {
|
||||||
pub mod png;
|
pub mod png;
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
|
/// Options controlling the output of the `optimize` function
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
|
/// Whether the input file should be backed up before writing the output
|
||||||
pub backup: bool,
|
pub backup: bool,
|
||||||
|
/// Path to write the output file to
|
||||||
pub out_file: PathBuf,
|
pub out_file: PathBuf,
|
||||||
|
/// Used only in CLI interface
|
||||||
pub out_dir: Option<PathBuf>,
|
pub out_dir: Option<PathBuf>,
|
||||||
|
/// Write to stdout instead of a file
|
||||||
pub stdout: bool,
|
pub stdout: bool,
|
||||||
|
/// Attempt to fix errors when decoding the input file
|
||||||
pub fix_errors: bool,
|
pub fix_errors: bool,
|
||||||
|
/// Don't actually write any output, just calculate the best results
|
||||||
pub pretend: bool,
|
pub pretend: bool,
|
||||||
|
/// Used only in CLI interface
|
||||||
pub recursive: bool,
|
pub recursive: bool,
|
||||||
|
/// Overwrite existing output files
|
||||||
pub clobber: bool,
|
pub clobber: bool,
|
||||||
|
/// Create new output files if they don't exist
|
||||||
pub create: bool,
|
pub create: bool,
|
||||||
|
/// Write to output even if there was no improvement in compression
|
||||||
pub force: bool,
|
pub force: bool,
|
||||||
|
/// Ensure the output file has the same permissions as the input file
|
||||||
pub preserve_attrs: bool,
|
pub preserve_attrs: bool,
|
||||||
|
/// How verbose the console logging should be (`None` for quiet, `Some(0)` for normal, `Some(1)` for verbose)
|
||||||
pub verbosity: Option<u8>,
|
pub verbosity: Option<u8>,
|
||||||
|
/// Which filters to try on the file (0-5)
|
||||||
pub filter: HashSet<u8>,
|
pub filter: HashSet<u8>,
|
||||||
|
/// Whether to change the interlacing type of the file
|
||||||
|
/// `None` will not change the current interlacing type
|
||||||
|
/// `Some(x)` will change the file to interlacing mode `x`
|
||||||
pub interlace: Option<u8>,
|
pub interlace: Option<u8>,
|
||||||
|
/// Which zlib compression levels to try on the file (1-9)
|
||||||
pub compression: HashSet<u8>,
|
pub compression: HashSet<u8>,
|
||||||
|
/// Which zlib memory levels to try on the file (1-9)
|
||||||
pub memory: HashSet<u8>,
|
pub memory: HashSet<u8>,
|
||||||
|
/// Which zlib compression strategies to try on the file (0-3)
|
||||||
pub strategies: HashSet<u8>,
|
pub strategies: HashSet<u8>,
|
||||||
|
/// Window size to use when compressing the file, as `2^window` bytes
|
||||||
|
/// Doesn't affect compression but may affect speed and memory usage
|
||||||
|
/// 15 is recommended default, 8-15 are valid values
|
||||||
pub window: u8,
|
pub window: u8,
|
||||||
|
/// Whether to attempt bit depth reduction
|
||||||
pub bit_depth_reduction: bool,
|
pub bit_depth_reduction: bool,
|
||||||
|
/// Whether to attempt color type reduction
|
||||||
pub color_type_reduction: bool,
|
pub color_type_reduction: bool,
|
||||||
|
/// Whether to attempt palette reduction
|
||||||
pub palette_reduction: bool,
|
pub palette_reduction: bool,
|
||||||
|
/// Whether to perform IDAT recoding
|
||||||
|
/// If any type of reduction is performed, IDAT recoding will be performed
|
||||||
|
/// regardless of this setting
|
||||||
pub idat_recoding: bool,
|
pub idat_recoding: bool,
|
||||||
|
/// Which headers to strip from the PNG file, if any
|
||||||
pub strip: png::Headers,
|
pub strip: png::Headers,
|
||||||
|
/// Whether to use heuristics to pick the best filter and compression
|
||||||
|
/// Intended for use with `-o 1` from the CLI interface
|
||||||
pub use_heuristics: bool,
|
pub use_heuristics: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Perform optimization on the input file using the options provided
|
||||||
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
|
||||||
if opts.verbosity.is_some() {
|
if opts.verbosity.is_some() {
|
||||||
|
|
|
||||||
61
src/png.rs
61
src/png.rs
|
|
@ -10,11 +10,17 @@ use std::iter::Iterator;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[derive(Debug,PartialEq,Clone,Copy)]
|
#[derive(Debug,PartialEq,Clone,Copy)]
|
||||||
|
/// The color type used to represent this image
|
||||||
pub enum ColorType {
|
pub enum ColorType {
|
||||||
|
/// Grayscale, with one color channel
|
||||||
Grayscale,
|
Grayscale,
|
||||||
|
/// RGB, with three color channels
|
||||||
RGB,
|
RGB,
|
||||||
|
/// Indexed, with one byte per pixel representing one of up to 256 colors in the image
|
||||||
Indexed,
|
Indexed,
|
||||||
|
/// Grayscale + Alpha, with two color channels
|
||||||
GrayscaleAlpha,
|
GrayscaleAlpha,
|
||||||
|
/// RGBA, with four color channels
|
||||||
RGBA,
|
RGBA,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,11 +51,17 @@ impl ColorType {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,PartialEq,Clone,Copy)]
|
#[derive(Debug,PartialEq,Clone,Copy)]
|
||||||
|
/// The number of bits to be used per channel per pixel
|
||||||
pub enum BitDepth {
|
pub enum BitDepth {
|
||||||
|
/// One bit per channel per pixel
|
||||||
One,
|
One,
|
||||||
|
/// Two bits per channel per pixel
|
||||||
Two,
|
Two,
|
||||||
|
/// Four bits per channel per pixel
|
||||||
Four,
|
Four,
|
||||||
|
/// Eight bits per channel per pixel
|
||||||
Eight,
|
Eight,
|
||||||
|
/// Sixteen bits per channel per pixel
|
||||||
Sixteen,
|
Sixteen,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,6 +80,7 @@ impl fmt::Display for BitDepth {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BitDepth {
|
impl BitDepth {
|
||||||
|
/// Retrieve the number of bits per channel per pixel as a `u8`
|
||||||
pub fn as_u8(&self) -> u8 {
|
pub fn as_u8(&self) -> u8 {
|
||||||
match *self {
|
match *self {
|
||||||
BitDepth::One => 1,
|
BitDepth::One => 1,
|
||||||
|
|
@ -77,6 +90,7 @@ impl BitDepth {
|
||||||
BitDepth::Sixteen => 16,
|
BitDepth::Sixteen => 16,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Parse a number of bits per channel per pixel into a `BitDepth`
|
||||||
pub fn from_u8(depth: u8) -> BitDepth {
|
pub fn from_u8(depth: u8) -> BitDepth {
|
||||||
match depth {
|
match depth {
|
||||||
1 => BitDepth::One,
|
1 => BitDepth::One,
|
||||||
|
|
@ -90,15 +104,22 @@ impl BitDepth {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,PartialEq,Clone)]
|
#[derive(Debug,PartialEq,Clone)]
|
||||||
|
/// Options to use for performing operations on headers (such as stripping)
|
||||||
pub enum Headers {
|
pub enum Headers {
|
||||||
|
/// None
|
||||||
None,
|
None,
|
||||||
|
/// Some, with a list of 4-character chunk codes
|
||||||
Some(Vec<String>),
|
Some(Vec<String>),
|
||||||
|
/// Headers that won't affect rendering (all but cHRM, gAMA, iCCP, sBIT, sRGB, bKGD, hIST, pHYs, sPLT)
|
||||||
Safe,
|
Safe,
|
||||||
|
/// All non-critical headers
|
||||||
All,
|
All,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug,Clone)]
|
||||||
|
/// An iterator over the scan lines of a PNG image
|
||||||
pub struct ScanLines<'a> {
|
pub struct ScanLines<'a> {
|
||||||
|
/// A reference to the PNG image being iterated upon
|
||||||
pub png: &'a PngData,
|
pub png: &'a PngData,
|
||||||
start: usize,
|
start: usize,
|
||||||
end: usize,
|
end: usize,
|
||||||
|
|
@ -126,34 +147,55 @@ impl<'a> Iterator for ScanLines<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug,Clone)]
|
||||||
|
/// A scan line in a PNG image
|
||||||
pub struct ScanLine {
|
pub struct ScanLine {
|
||||||
|
/// The filter type used to encode the current scan line (0-4)
|
||||||
pub filter: u8,
|
pub filter: u8,
|
||||||
|
/// The byte data for the current scan line, encoded with the filter specified in the `filter` field
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug,Clone)]
|
||||||
|
/// Contains all data relevant to a PNG image
|
||||||
pub struct PngData {
|
pub struct PngData {
|
||||||
|
/// The filtered and compressed data of the IDAT chunk
|
||||||
pub idat_data: Vec<u8>,
|
pub idat_data: Vec<u8>,
|
||||||
|
/// The headers stored in the IHDR chunk
|
||||||
pub ihdr_data: IhdrData,
|
pub ihdr_data: IhdrData,
|
||||||
|
/// The uncompressed, optionally filtered data from the IDAT chunk
|
||||||
pub raw_data: Vec<u8>,
|
pub raw_data: Vec<u8>,
|
||||||
|
/// The palette containing colors used in an Indexed image
|
||||||
|
/// Contains 3 bytes per color (R+G+B), up to 768
|
||||||
pub palette: Option<Vec<u8>>,
|
pub palette: Option<Vec<u8>>,
|
||||||
|
/// The pixel value that should be rendered as transparent
|
||||||
pub transparency_pixel: Option<Vec<u8>>,
|
pub transparency_pixel: Option<Vec<u8>>,
|
||||||
|
/// A map of how transparent each color in the palette should be
|
||||||
pub transparency_palette: Option<Vec<u8>>,
|
pub transparency_palette: Option<Vec<u8>>,
|
||||||
|
/// All non-critical headers from the PNG are stored here
|
||||||
pub aux_headers: HashMap<String, Vec<u8>>,
|
pub aux_headers: HashMap<String, Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone,Copy)]
|
#[derive(Debug,Clone,Copy)]
|
||||||
|
/// Headers from the IHDR chunk of the image
|
||||||
pub struct IhdrData {
|
pub struct IhdrData {
|
||||||
|
/// The width of the image in pixels
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
|
/// The height of the image in pixels
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
|
/// The color type of the image
|
||||||
pub color_type: ColorType,
|
pub color_type: ColorType,
|
||||||
|
/// The bit depth of the image
|
||||||
pub bit_depth: BitDepth,
|
pub bit_depth: BitDepth,
|
||||||
|
/// The compression method used for this image (0 for DEFLATE)
|
||||||
pub compression: u8,
|
pub compression: u8,
|
||||||
|
/// The filter mode used for this image (currently only 0 is valid)
|
||||||
pub filter: u8,
|
pub filter: u8,
|
||||||
|
/// The interlacing mode of the image (0 = None, 1 = Adam7)
|
||||||
pub interlaced: u8,
|
pub interlaced: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PngData {
|
impl PngData {
|
||||||
|
/// Create a new `PngData` struct by opening a file
|
||||||
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,
|
||||||
|
|
@ -237,6 +279,7 @@ impl PngData {
|
||||||
// Return the PngData
|
// Return the PngData
|
||||||
Ok(png_data)
|
Ok(png_data)
|
||||||
}
|
}
|
||||||
|
/// Return the number of channels in the image, based on color type
|
||||||
pub fn channels_per_pixel(&self) -> u8 {
|
pub fn channels_per_pixel(&self) -> u8 {
|
||||||
match self.ihdr_data.color_type {
|
match self.ihdr_data.color_type {
|
||||||
ColorType::Grayscale | ColorType::Indexed => 1,
|
ColorType::Grayscale | ColorType::Indexed => 1,
|
||||||
|
|
@ -245,6 +288,7 @@ impl PngData {
|
||||||
ColorType::RGBA => 4,
|
ColorType::RGBA => 4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Format the `PngData` struct into a valid PNG bytestream
|
||||||
pub fn output(&self) -> Vec<u8> {
|
pub fn output(&self) -> Vec<u8> {
|
||||||
// FIXME: This code can all be refactored
|
// FIXME: This code can all be refactored
|
||||||
// PNG header
|
// PNG header
|
||||||
|
|
@ -341,6 +385,7 @@ impl PngData {
|
||||||
|
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
/// Return an iterator over the scanlines of the image
|
||||||
pub fn scan_lines(&self) -> ScanLines {
|
pub fn scan_lines(&self) -> ScanLines {
|
||||||
ScanLines {
|
ScanLines {
|
||||||
png: &self,
|
png: &self,
|
||||||
|
|
@ -348,6 +393,7 @@ impl PngData {
|
||||||
end: 0,
|
end: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Reverse all filters applied on the image, returning an unfiltered IDAT bytestream
|
||||||
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());
|
||||||
let tmp = self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel();
|
let tmp = self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel();
|
||||||
|
|
@ -442,6 +488,13 @@ impl PngData {
|
||||||
}
|
}
|
||||||
unfiltered
|
unfiltered
|
||||||
}
|
}
|
||||||
|
/// Apply the specified filter type to all rows in the image
|
||||||
|
/// 0: None
|
||||||
|
/// 1: Sub
|
||||||
|
/// 2: Up
|
||||||
|
/// 3: Average
|
||||||
|
/// 4: Paeth
|
||||||
|
/// 5: All (heuristically pick the best filter for each line)
|
||||||
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());
|
||||||
let tmp = self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel();
|
let tmp = self.ihdr_data.bit_depth.as_u8() * self.channels_per_pixel();
|
||||||
|
|
@ -601,6 +654,8 @@ impl PngData {
|
||||||
}
|
}
|
||||||
filtered
|
filtered
|
||||||
}
|
}
|
||||||
|
/// 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 {
|
pub fn reduce_bit_depth(&mut self) -> bool {
|
||||||
if self.ihdr_data.bit_depth != BitDepth::Sixteen {
|
if self.ihdr_data.bit_depth != BitDepth::Sixteen {
|
||||||
if self.ihdr_data.color_type == ColorType::Indexed ||
|
if self.ihdr_data.color_type == ColorType::Indexed ||
|
||||||
|
|
@ -645,10 +700,14 @@ impl PngData {
|
||||||
self.raw_data = reduced;
|
self.raw_data = reduced;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
/// Attempt to reduce the number of colors in the palette
|
||||||
|
/// Returns true if the palette was reduced, false otherwise
|
||||||
pub fn reduce_palette(&mut self) -> bool {
|
pub fn reduce_palette(&mut self) -> bool {
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
/// Attempt to reduce the color type of the image
|
||||||
|
/// Returns true if the color type was reduced, false otherwise
|
||||||
pub fn reduce_color_type(&mut self) -> bool {
|
pub fn reduce_color_type(&mut self) -> bool {
|
||||||
let mut changed = false;
|
let mut changed = false;
|
||||||
let mut should_reduce_bit_depth = false;
|
let mut should_reduce_bit_depth = false;
|
||||||
|
|
@ -732,6 +791,8 @@ impl PngData {
|
||||||
|
|
||||||
changed
|
changed
|
||||||
}
|
}
|
||||||
|
/// Convert the image to the specified interlacing type
|
||||||
|
/// Returns true if the interlacing was changed, false otherwise
|
||||||
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 {
|
if interlace != self.ihdr_data.interlaced {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue