From ece85e6d157b3ca4690782846ce334a90dd9c396 Mon Sep 17 00:00:00 2001 From: Josh Holmer Date: Tue, 30 Jan 2018 09:35:03 -0500 Subject: [PATCH] Refactoring of internal code into smaller chunks. There is still more to be done. --- CHANGELOG.md | 5 +- Cargo.lock | 84 ++++----- src/filters.rs | 1 - src/lib.rs | 219 ++++++++--------------- src/{png.rs => png/mod.rs} | 143 +-------------- src/png/scan_lines.rs | 141 +++++++++++++++ src/reduction/alpha.rs | 26 +++ src/reduction/bit_depth.rs | 45 +++++ src/{reduction.rs => reduction/color.rs} | 68 +------ src/reduction/mod.rs | 3 + 10 files changed, 337 insertions(+), 398 deletions(-) rename src/{png.rs => png/mod.rs} (85%) create mode 100644 src/png/scan_lines.rs create mode 100644 src/reduction/alpha.rs create mode 100644 src/reduction/bit_depth.rs rename src/{reduction.rs => reduction/color.rs} (80%) create mode 100644 src/reduction/mod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8418f5b9..8a4ac20e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +### Version 0.19.1 (unreleased) + - Refactor of internal code. + ### Version 0.19.0 - [SEMVER_MAJOR] Default to overwriting the input file if `out_file` is not set. This does not affect the CLI, but with the library, it was easy to forget to set the `out_file`, @@ -5,7 +8,7 @@ - Bump dependencies, reduces binary size by a considerable amount - Hide all modules from documentation, and only export the specific structures that should be public. Previously there were too many implementation details made public. The modules are still public for the purposes of our integration tests, - but we strongly advice against using undocumented modules. These may become private in the future. + but we strongly advise against using undocumented modules. These may become private in the future. - Internal refactoring and code cleanup - Fix an error message that was displaying the wrong file path - Fix an issue where the output file would not be written if the input was already optimized, diff --git a/Cargo.lock b/Cargo.lock index cb42f98c..a12a5c35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,9 +26,9 @@ name = "atty" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -78,7 +78,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "clap" -version = "2.29.1" +version = "2.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -92,17 +92,17 @@ dependencies = [ [[package]] name = "clippy" -version = "0.0.179" +version = "0.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cargo_metadata 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "clippy_lints 0.0.179 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy_lints 0.0.183 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "clippy_lints" -version = "0.0.179" +version = "0.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "if_chain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -161,7 +161,7 @@ name = "enum_primitive" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -180,7 +180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "getopts" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -211,8 +211,8 @@ dependencies = [ "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", - "num-rational 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "png 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -257,7 +257,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -270,7 +270,7 @@ name = "memchr" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -279,7 +279,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -287,7 +287,7 @@ name = "num-integer" version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -296,21 +296,21 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-rational" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" -version = "0.1.41" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -318,7 +318,7 @@ name = "num_cpus" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -327,13 +327,13 @@ version = "0.19.0" dependencies = [ "bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.29.1 (registry+https://github.com/rust-lang/crates.io-index)", - "clippy 0.0.179 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.29.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.183 (registry+https://github.com/rust-lang/crates.io-index)", "crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "miniz-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -363,7 +363,7 @@ version = "0.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "getopts 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -382,7 +382,7 @@ version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -401,7 +401,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -486,7 +486,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -523,7 +523,7 @@ name = "termion" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -616,21 +616,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "winapi-i686-pc-windows-gnu" -version = "0.3.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-x86_64-pc-windows-gnu" -version = "0.3.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -658,9 +658,9 @@ dependencies = [ "checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" "checksum cargo_metadata 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "be1057b8462184f634c3a208ee35b0f935cfd94b694b26deadccd98732088d7b" "checksum cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "deaf9ec656256bb25b404c51ef50097207b9cbb29c933d31f92cae5a8a0ffee0" -"checksum clap 2.29.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8f4a2b3bb7ef3c672d7c13d15613211d5a6976b6892c598b0fcb5d40765f19c2" -"checksum clippy 0.0.179 (registry+https://github.com/rust-lang/crates.io-index)" = "f3865be451fd0a4f53376171c2e86e52ef6dfa2d51e8d137229851b332f5ab12" -"checksum clippy_lints 0.0.179 (registry+https://github.com/rust-lang/crates.io-index)" = "78002b9b809798e082008d1a90d63b1053243b0b03ded66bb4d661a6248be896" +"checksum clap 2.29.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4151c5790817c7d21bbdc6c3530811f798172915f93258244948b93ba19604a6" +"checksum clippy 0.0.183 (registry+https://github.com/rust-lang/crates.io-index)" = "a5e81310a5a578c454fb713d381b332d67be11d0e108e5846eaadd7044003026" +"checksum clippy_lints 0.0.183 (registry+https://github.com/rust-lang/crates.io-index)" = "0f7b3f5649a03ab72703d07bc53f0d6935e3a9f24f31c84003f801752e4d2692" "checksum coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c06169f5beb7e31c7c67ebf5540b8b472d23e3eade3b2ec7d1f5b504a85f91bd" "checksum crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd5d02c0aac6bd68393ed69e00bbc2457f3e89075c6349db7189618dc4ddc1d7" "checksum deflate 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)" = "4dddda59aaab719767ab11d3efd9a714e95b610c4445d4435765021e9d52dfb1" @@ -669,7 +669,7 @@ dependencies = [ "checksum enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum getopts 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "65922871abd2f101a2eb0eaebadc66668e54a87ad9c3dd82520b5f86ede5eff9" +"checksum getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "b900c08c1939860ce8b54dc6a89e26e00c04c380fd0e09796799bd7f12861e05" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" "checksum if_chain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "61bb90bdd39e3af69b0172dfc6130f6cd6332bf040fbb9bdd4401d37adbd48b8" @@ -680,14 +680,14 @@ dependencies = [ "checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" -"checksum libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)" = "96264e9b293e95d25bfcbbf8a88ffd1aedc85b754eba8b7d78012f638ba220eb" +"checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121" "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" "checksum miniz-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "609ce024854aeb19a0ef7567d348aaa5a746b32fb72e336df7fcc16869d7e2b4" "checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba" "checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01" -"checksum num-rational 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "0c7cb72a95250d8a370105c828f388932373e0e94414919891a0f945222310fe" -"checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070" +"checksum num-rational 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "0b950f75e042fdd710460084d19c8efdcd72d65183ead8ecd04b90483f5a55d2" +"checksum num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "9936036cc70fe4a8b2d338ab665900323290efb03983c86cbe235ae800ad8017" "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum png 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f0b0cabbbd20c2d7f06dbf015e06aad59b6ca3d9ed14848783e98af9aaf19925" @@ -726,7 +726,7 @@ dependencies = [ "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b09fb3b6f248ea4cd42c9a65113a847d612e17505d6ebd1f7357ad68a8bf8693" -"checksum winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ec6667f60c23eca65c561e63a13d81b44234c2e38a6b6c959025ee907ec614cc" -"checksum winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98f12c52b2630cd05d2c3ffd8e008f7f48252c042b4871c72aed9dc733b96668" +"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum zopfli 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ea92ee4d5ceb063a154706472afcf453c69c9d72485946c80e8f439fffbd0043" diff --git a/src/filters.rs b/src/filters.rs index dbea26ea..c92cb1b6 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -144,7 +144,6 @@ pub fn unfilter_line(filter: u8, bpp: usize, data: &[u8], last_line: &[u8]) -> V unfiltered } -#[inline] fn paeth_predictor(a: u8, b: u8, c: u8) -> u8 { let p = i32::from(a) + i32::from(b) - i32::from(c); let pa = (p - i32::from(a)).abs(); diff --git a/src/lib.rs b/src/lib.rs index b02f40ad..defa7767 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ extern crate num_cpus; extern crate rayon; extern crate zopfli; -use image::{GenericImage, ImageFormat, Pixel}; +use image::{DynamicImage, GenericImage, ImageFormat, Pixel}; use png::PngData; use rayon::prelude::*; use std::collections::{HashMap, HashSet}; @@ -173,19 +173,18 @@ impl Options { } } + // The following methods make assumptions that they are operating + // on an `Options` struct generated by the `default` method. fn apply_preset_0(mut self) -> Self { self.idat_recoding = false; - let mut compression = HashSet::new(); - compression.insert(3); - self.compression = compression; + self.compression.clear(); + self.compression.insert(3); self } fn apply_preset_1(mut self) -> Self { - let filter = HashSet::new(); - self.filter = filter; - let strategies = HashSet::new(); - self.strategies = strategies; + self.filter.clear(); + self.strategies.clear(); self.use_heuristics = true; self } @@ -195,94 +194,30 @@ impl Options { } fn apply_preset_3(mut self) -> Self { - let mut filter = HashSet::new(); - filter.insert(0); - filter.insert(5); - self.filter = filter; - let mut compression = HashSet::new(); - compression.insert(9); - self.compression = compression; - let mut memory = HashSet::new(); - for i in 8..10 { - memory.insert(i); - } - self.memory = memory; - let mut strategies = HashSet::new(); - for i in 0..4 { - strategies.insert(i); - } - self.strategies = strategies; + self.memory.insert(8); self } fn apply_preset_4(mut self) -> Self { - let mut filter = HashSet::new(); - for i in 0..6 { - filter.insert(i); + for i in 1..5 { + self.filter.insert(i); } - self.filter = filter; - let mut compression = HashSet::new(); - compression.insert(9); - self.compression = compression; - let mut memory = HashSet::new(); - for i in 8..10 { - memory.insert(i); - } - self.memory = memory; - let mut strategies = HashSet::new(); - for i in 0..4 { - strategies.insert(i); - } - self.strategies = strategies; - self + self.apply_preset_3() } fn apply_preset_5(mut self) -> Self { - let mut filter = HashSet::new(); - for i in 0..6 { - filter.insert(i); + for i in 3..9 { + self.compression.insert(i); } - self.filter = filter; - let mut compression = HashSet::new(); - for i in 3..10 { - compression.insert(i); - } - self.compression = compression; - let mut memory = HashSet::new(); - for i in 8..10 { - memory.insert(i); - } - self.memory = memory; - let mut strategies = HashSet::new(); - for i in 0..4 { - strategies.insert(i); - } - self.strategies = strategies; - self + self.apply_preset_4() } fn apply_preset_6(mut self) -> Self { - let mut filter = HashSet::new(); - for i in 0..6 { - filter.insert(i); + for i in 1..3 { + self.compression.insert(i); } - self.filter = filter; - let mut compression = HashSet::new(); - for i in 1..10 { - compression.insert(i); - } - self.compression = compression; - let mut memory = HashSet::new(); - for i in 7..10 { - memory.insert(i); - } - self.memory = memory; - let mut strategies = HashSet::new(); - for i in 0..4 { - strategies.insert(i); - } - self.strategies = strategies; - self + self.memory.insert(7); + self.apply_preset_5() } } @@ -321,11 +256,11 @@ impl Default for Options { force: false, preserve_attrs: false, verbosity: Some(0), - filter: filter, + filter, interlace: None, - compression: compression, - memory: memory, - strategies: strategies, + compression, + memory, + strategies, window: 15, alphas, bit_depth_reduction: true, @@ -375,19 +310,7 @@ pub fn optimize(input_path: &Path, opts: &Options) -> Result<(), PngError> { } } else { if opts.backup { - let backup_file = input_path.with_extension(format!( - "bak.{}", - input_path.extension().unwrap().to_str().unwrap() - )); - match copy(input_path, &backup_file) { - Ok(x) => x, - Err(_) => { - return Err(PngError::new(&format!( - "Unable to write to backup file at {}", - backup_file.display() - ))) - } - }; + perform_backup(input_path)?; } if opts.stdout { @@ -408,34 +331,7 @@ pub fn optimize(input_path: &Path, opts: &Options) -> Result<(), PngError> { }; if opts.preserve_attrs { - match File::open(input_path) { - Ok(f) => { - match f.metadata() { - Ok(metadata) => { - // TODO: Implement full permission changing on Unix - // Not available in stable, requires block cfg statements - // See https://github.com/rust-lang/rust/issues/15701 - { - match out_file.metadata() { - Ok(out_meta) => { - let readonly = metadata.permissions().readonly(); - out_meta.permissions().set_readonly(readonly); - } - Err(_) => if opts.verbosity.is_some() { - eprintln!("Failed to set permissions on output file"); - }, - } - } - } - Err(_) => if opts.verbosity.is_some() { - eprintln!("Failed to read permissions on input file"); - }, - } - } - Err(_) => if opts.verbosity.is_some() { - eprintln!("Failed to read permissions on input file"); - }, - }; + copy_permissions(input_path, &out_file, opts.verbosity); } let mut buffer = BufWriter::new(out_file); @@ -480,6 +376,15 @@ pub fn optimize_from_memory(data: &[u8], opts: &Options) -> Result, PngE } } +#[derive(Debug, PartialEq, Clone, Copy)] +/// Defines options to be used for a single compression trial +struct TrialOptions { + pub filter: u8, + pub compression: u8, + pub memory: u8, + pub strategy: u8, +} + /// Perform optimization on the input PNG object using the options provided fn optimize_png( png: &mut PngData, @@ -688,17 +593,7 @@ fn optimize_png( if let Ok(new_png) = new_png { if let Ok(old_png) = old_png { - if old_png - .pixels() - .map(|x| x.2.channels().to_owned()) - .filter(|p| !(p.len() == 4 && p[3] == 0)) - .collect::>>() - == new_png - .pixels() - .map(|x| x.2.channels().to_owned()) - .filter(|p| !(p.len() == 4 && p[3] == 0)) - .collect::>>() - { + if image_to_pixel_array(&old_png) == image_to_pixel_array(&new_png) { return Ok(output); } } else { @@ -758,7 +653,6 @@ fn perform_reductions(png: &mut png::PngData, opts: &Options) -> bool { } /// Display the status of the image data after a reduction has taken place -#[inline] fn report_reduction(png: &png::PngData) { if let Some(ref palette) = png.palette { eprintln!( @@ -802,16 +696,45 @@ fn perform_strip(png: &mut png::PngData, opts: &Options) { } /// Check if an image was already optimized prior to oxipng's operations -#[inline] fn is_fully_optimized(original_size: usize, optimized_size: usize, opts: &Options) -> bool { original_size <= optimized_size && !opts.force && opts.interlace.is_none() } -#[derive(Debug, PartialEq, Clone, Copy)] -/// Defines options to be used for a single compression trial -struct TrialOptions { - pub filter: u8, - pub compression: u8, - pub memory: u8, - pub strategy: u8, +fn perform_backup(input_path: &Path) -> Result<(), PngError> { + let backup_file = input_path.with_extension(format!( + "bak.{}", + input_path.extension().unwrap().to_str().unwrap() + )); + copy(input_path, &backup_file).map(|_| ()).map_err(|_| { + PngError::new(&format!( + "Unable to write to backup file at {}", + backup_file.display() + )) + }) +} + +fn copy_permissions(input_path: &Path, out_file: &File, verbosity: Option) { + if let Ok(f) = File::open(input_path) { + if let Ok(metadata) = f.metadata() { + // TODO: Implement full permission changing on Unix + // Not available in stable, requires block cfg statements + // See https://github.com/rust-lang/rust/issues/15701 + if let Ok(out_meta) = out_file.metadata() { + let readonly = metadata.permissions().readonly(); + out_meta.permissions().set_readonly(readonly); + return; + } + } + }; + if verbosity.is_some() { + eprintln!("Failed to set permissions on output file"); + } +} + +fn image_to_pixel_array(image: &DynamicImage) -> Vec> { + image + .pixels() + .map(|x| x.2.channels().to_owned()) + .filter(|p| !(p.len() == 4 && p[3] == 0)) + .collect() } diff --git a/src/png.rs b/src/png/mod.rs similarity index 85% rename from src/png.rs rename to src/png/mod.rs index 759a7dd2..16698665 100644 --- a/src/png.rs +++ b/src/png/mod.rs @@ -8,7 +8,8 @@ use filters::*; use headers::*; use interlace::{deinterlace_image, interlace_image}; use itertools::Itertools; -use reduction::*; +use reduction::bit_depth::*; +use reduction::color::*; use std::collections::{HashMap, HashSet}; use std::fs::File; use std::io::{Read, Seek, SeekFrom}; @@ -21,145 +22,9 @@ const STD_STRATEGY: u8 = 2; // Huffman only const STD_WINDOW: u8 = 15; const STD_FILTERS: [u8; 2] = [0, 5]; -#[derive(Debug, Clone)] -/// An iterator over the scan lines of a PNG image -pub struct ScanLines<'a> { - /// A reference to the PNG image being iterated upon - pub png: &'a PngData, - start: usize, - end: usize, - /// Current pass number, and 0-indexed row within the pass - pass: Option<(u8, u32)>, -} +mod scan_lines; -impl<'a> Iterator for ScanLines<'a> { - type Item = ScanLine; - fn next(&mut self) -> Option { - if self.end == self.png.raw_data.len() { - None - } else if self.png.ihdr_data.interlaced == 1 { - // Scanlines for interlaced PNG files - if self.pass.is_none() { - self.pass = Some((1, 0)); - } - // Handle edge cases for images smaller than 5 pixels in either direction - if self.png.ihdr_data.width < 5 && self.pass.unwrap().0 == 2 { - if let Some(pass) = self.pass.as_mut() { - pass.0 = 3; - pass.1 = 4; - } - } - // Intentionally keep these separate so that they can be applied one after another - if self.png.ihdr_data.height < 5 && self.pass.unwrap().0 == 3 { - if let Some(pass) = self.pass.as_mut() { - pass.0 = 4; - pass.1 = 0; - } - } - let bits_per_pixel = u32::from(self.png.ihdr_data.bit_depth.as_u8()) - * u32::from(self.png.channels_per_pixel()); - let y_steps; - let pixels_factor; - match self.pass { - Some((1, _)) | Some((2, _)) => { - pixels_factor = 8; - y_steps = 8; - } - Some((3, _)) => { - pixels_factor = 4; - y_steps = 8; - } - Some((4, _)) => { - pixels_factor = 4; - y_steps = 4; - } - Some((5, _)) => { - pixels_factor = 2; - y_steps = 4; - } - Some((6, _)) => { - pixels_factor = 2; - y_steps = 2; - } - Some((7, _)) => { - pixels_factor = 1; - y_steps = 2; - } - _ => unreachable!(), - } - let mut pixels_per_line = self.png.ihdr_data.width / pixels_factor as u32; - // Determine whether to add pixels if there is a final, incomplete 8x8 block - let gap = self.png.ihdr_data.width % pixels_factor; - if gap > 0 { - match self.pass.unwrap().0 { - 1 | 3 | 5 => { - pixels_per_line += 1; - } - 2 if gap >= 5 => { - pixels_per_line += 1; - } - 4 if gap >= 3 => { - pixels_per_line += 1; - } - 6 if gap >= 2 => { - pixels_per_line += 1; - } - _ => (), - }; - } - let current_pass = if let Some(pass) = self.pass { - Some(pass.0) - } else { - None - }; - let bytes_per_line = ((pixels_per_line * bits_per_pixel + 7) / 8) as usize; - self.start = self.end; - self.end = self.start + bytes_per_line + 1; - if let Some(pass) = self.pass.as_mut() { - if pass.1 + y_steps >= self.png.ihdr_data.height { - pass.0 += 1; - pass.1 = match pass.0 { - 3 => 4, - 5 => 2, - 7 => 1, - _ => 0, - }; - } else { - pass.1 += y_steps; - } - } - Some(ScanLine { - filter: self.png.raw_data[self.start], - data: self.png.raw_data[(self.start + 1)..self.end].to_owned(), - pass: current_pass, - }) - } else { - // Standard, non-interlaced PNG scanlines - let bits_per_line = self.png.ihdr_data.width as usize - * self.png.ihdr_data.bit_depth.as_u8() as usize - * self.png.channels_per_pixel() as usize; - let bytes_per_line = (bits_per_line + 7) / 8 as usize; - self.start = self.end; - self.end = self.start + bytes_per_line + 1; - Some(ScanLine { - filter: self.png.raw_data[self.start], - data: self.png.raw_data[(self.start + 1)..self.end].to_owned(), - pass: None, - }) - } - } -} - -#[derive(Debug, Clone)] -/// A scan line in a PNG image -pub struct ScanLine { - /// The filter type used to encode the current scan line (0-4) - pub filter: u8, - /// The byte data for the current scan line, encoded with the filter specified in the `filter` field - pub data: Vec, - /// The current pass if the image is interlaced - pub pass: Option, -} +use self::scan_lines::{ScanLine, ScanLines}; #[derive(Debug, Clone)] /// Contains all data relevant to a PNG image diff --git a/src/png/scan_lines.rs b/src/png/scan_lines.rs new file mode 100644 index 00000000..a451e14a --- /dev/null +++ b/src/png/scan_lines.rs @@ -0,0 +1,141 @@ +use super::PngData; + +#[derive(Debug, Clone)] +/// An iterator over the scan lines of a PNG image +pub struct ScanLines<'a> { + /// A reference to the PNG image being iterated upon + pub png: &'a PngData, + pub start: usize, + pub end: usize, + /// Current pass number, and 0-indexed row within the pass + pub pass: Option<(u8, u32)>, +} + +impl<'a> Iterator for ScanLines<'a> { + type Item = ScanLine; + fn next(&mut self) -> Option { + if self.end == self.png.raw_data.len() { + None + } else if self.png.ihdr_data.interlaced == 1 { + // Scanlines for interlaced PNG files + if self.pass.is_none() { + self.pass = Some((1, 0)); + } + // Handle edge cases for images smaller than 5 pixels in either direction + if self.png.ihdr_data.width < 5 && self.pass.unwrap().0 == 2 { + if let Some(pass) = self.pass.as_mut() { + pass.0 = 3; + pass.1 = 4; + } + } + // Intentionally keep these separate so that they can be applied one after another + if self.png.ihdr_data.height < 5 && self.pass.unwrap().0 == 3 { + if let Some(pass) = self.pass.as_mut() { + pass.0 = 4; + pass.1 = 0; + } + } + let bits_per_pixel = u32::from(self.png.ihdr_data.bit_depth.as_u8()) + * u32::from(self.png.channels_per_pixel()); + let y_steps; + let pixels_factor; + match self.pass { + Some((1, _)) | Some((2, _)) => { + pixels_factor = 8; + y_steps = 8; + } + Some((3, _)) => { + pixels_factor = 4; + y_steps = 8; + } + Some((4, _)) => { + pixels_factor = 4; + y_steps = 4; + } + Some((5, _)) => { + pixels_factor = 2; + y_steps = 4; + } + Some((6, _)) => { + pixels_factor = 2; + y_steps = 2; + } + Some((7, _)) => { + pixels_factor = 1; + y_steps = 2; + } + _ => unreachable!(), + } + let mut pixels_per_line = self.png.ihdr_data.width / pixels_factor as u32; + // Determine whether to add pixels if there is a final, incomplete 8x8 block + let gap = self.png.ihdr_data.width % pixels_factor; + if gap > 0 { + match self.pass.unwrap().0 { + 1 | 3 | 5 => { + pixels_per_line += 1; + } + 2 if gap >= 5 => { + pixels_per_line += 1; + } + 4 if gap >= 3 => { + pixels_per_line += 1; + } + 6 if gap >= 2 => { + pixels_per_line += 1; + } + _ => (), + }; + } + let current_pass = if let Some(pass) = self.pass { + Some(pass.0) + } else { + None + }; + let bytes_per_line = ((pixels_per_line * bits_per_pixel + 7) / 8) as usize; + self.start = self.end; + self.end = self.start + bytes_per_line + 1; + if let Some(pass) = self.pass.as_mut() { + if pass.1 + y_steps >= self.png.ihdr_data.height { + pass.0 += 1; + pass.1 = match pass.0 { + 3 => 4, + 5 => 2, + 7 => 1, + _ => 0, + }; + } else { + pass.1 += y_steps; + } + } + Some(ScanLine { + filter: self.png.raw_data[self.start], + data: self.png.raw_data[(self.start + 1)..self.end].to_owned(), + pass: current_pass, + }) + } else { + // Standard, non-interlaced PNG scanlines + let bits_per_line = self.png.ihdr_data.width as usize + * self.png.ihdr_data.bit_depth.as_u8() as usize + * self.png.channels_per_pixel() as usize; + let bytes_per_line = (bits_per_line + 7) / 8 as usize; + self.start = self.end; + self.end = self.start + bytes_per_line + 1; + Some(ScanLine { + filter: self.png.raw_data[self.start], + data: self.png.raw_data[(self.start + 1)..self.end].to_owned(), + pass: None, + }) + } + } +} + +#[derive(Debug, Clone)] +/// A scan line in a PNG image +pub struct ScanLine { + /// The filter type used to encode the current scan line (0-4) + pub filter: u8, + /// The byte data for the current scan line, encoded with the filter specified in the `filter` field + pub data: Vec, + /// The current pass if the image is interlaced + pub pass: Option, +} diff --git a/src/reduction/alpha.rs b/src/reduction/alpha.rs new file mode 100644 index 00000000..8be26072 --- /dev/null +++ b/src/reduction/alpha.rs @@ -0,0 +1,26 @@ +use png::PngData; + +pub fn reduce_alpha_channel(png: &mut PngData, bpp_factor: usize) -> Option> { + let mut reduced = Vec::with_capacity(png.raw_data.len()); + let byte_depth: u8 = png.ihdr_data.bit_depth.as_u8() >> 3; + let bpp: usize = bpp_factor * byte_depth as usize; + let colored_bytes = bpp - byte_depth as usize; + for line in png.scan_lines() { + reduced.push(line.filter); + for (i, byte) in line.data.iter().enumerate() { + if i % bpp >= colored_bytes { + if *byte != 255 { + return None; + } + } else { + reduced.push(*byte); + } + } + } + if let Some(sbit_header) = png.aux_headers.get_mut(&"sBIT".to_string()) { + assert_eq!(sbit_header.len(), bpp_factor); + sbit_header.pop(); + } + + Some(reduced) +} diff --git a/src/reduction/bit_depth.rs b/src/reduction/bit_depth.rs new file mode 100644 index 00000000..df99973c --- /dev/null +++ b/src/reduction/bit_depth.rs @@ -0,0 +1,45 @@ +use bit_vec::BitVec; +use colors::{BitDepth, ColorType}; +use png::PngData; + +pub fn reduce_bit_depth_8_or_less(png: &mut PngData) -> bool { + 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 = if png.ihdr_data.color_type == ColorType::Indexed { + bit_depth - (i % bit_depth) + } else { + 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 false; + } + } + } + } + + for line in png.scan_lines() { + 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); + } + } + // Pad end of line to get 8 bits per byte + while reduced.len() % 8 != 0 { + reduced.push(false); + } + } + + png.raw_data = reduced.to_bytes(); + png.ihdr_data.bit_depth = BitDepth::from_u8(allowed_bits as u8); + true +} diff --git a/src/reduction.rs b/src/reduction/color.rs similarity index 80% rename from src/reduction.rs rename to src/reduction/color.rs index a4344f35..a61de657 100644 --- a/src/reduction.rs +++ b/src/reduction/color.rs @@ -1,49 +1,8 @@ -use bit_vec::BitVec; use colors::{BitDepth, ColorType}; use itertools::Itertools; use png::PngData; -pub fn reduce_bit_depth_8_or_less(png: &mut PngData) -> bool { - 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 = if png.ihdr_data.color_type == ColorType::Indexed { - bit_depth - (i % bit_depth) - } else { - 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 false; - } - } - } - } - - for line in png.scan_lines() { - 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); - } - } - // Pad end of line to get 8 bits per byte - while reduced.len() % 8 != 0 { - reduced.push(false); - } - } - - png.raw_data = reduced.to_bytes(); - png.ihdr_data.bit_depth = BitDepth::from_u8(allowed_bits as u8); - true -} +use super::alpha::reduce_alpha_channel; pub fn reduce_rgba_to_rgb(png: &mut PngData) -> bool { if let Some(reduced) = reduce_alpha_channel(png, 4) { @@ -325,28 +284,3 @@ pub fn reduce_grayscale_alpha_to_grayscale(png: &mut PngData) -> bool { false } } - -fn reduce_alpha_channel(png: &mut PngData, bpp_factor: usize) -> Option> { - let mut reduced = Vec::with_capacity(png.raw_data.len()); - let byte_depth: u8 = png.ihdr_data.bit_depth.as_u8() >> 3; - let bpp: usize = bpp_factor * byte_depth as usize; - let colored_bytes = bpp - byte_depth as usize; - for line in png.scan_lines() { - reduced.push(line.filter); - for (i, byte) in line.data.iter().enumerate() { - if i % bpp >= colored_bytes { - if *byte != 255 { - return None; - } - } else { - reduced.push(*byte); - } - } - } - if let Some(sbit_header) = png.aux_headers.get_mut(&"sBIT".to_string()) { - assert_eq!(sbit_header.len(), bpp_factor); - sbit_header.pop(); - } - - Some(reduced) -} diff --git a/src/reduction/mod.rs b/src/reduction/mod.rs new file mode 100644 index 00000000..eb6ce754 --- /dev/null +++ b/src/reduction/mod.rs @@ -0,0 +1,3 @@ +mod alpha; +pub mod bit_depth; +pub mod color;