diff --git a/.gitignore b/.gitignore index 344840d6..c03f402b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ target *.bk .DS_Store *.out.png -/.idea \ No newline at end of file +/.idea +/node_modules diff --git a/README.md b/README.md index 76b6d982..fdbb5abc 100644 --- a/README.md +++ b/README.md @@ -91,3 +91,34 @@ Other contributions (such as improving documentation or translations) are also w ## License Oxipng is open-source software, distributed under the MIT license. + +## Benchmarks + +Tested oxipng 0.18.3 (compiled on rustc 1.25.0-nightly (0f9c78475 2018-01-17)) against OptiPNG version 0.7.6 on Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz with 8 logical cores + + + +Benchmark #1: ./target/release/oxipng ./tests/files/rgb_16_should_be_grayscale_8.png + + Time (mean ± σ): 121.5 ms ± 7.9 ms + + Range (min … max): 113.4 ms … 145.0 ms + +Benchmark #2: optipng ./tests/files/rgb_16_should_be_grayscale_8.png + + Time (mean ± σ): 241.3 ms ± 5.0 ms + + Range (min … max): 234.4 ms … 249.4 ms + +Benchmark #1: ./target/release/oxipng -o4 ./tests/files/rgb_16_should_be_grayscale_8.png + + Time (mean ± σ): 289.7 ms ± 24.0 ms + + Range (min … max): 251.1 ms … 332.5 ms + +Benchmark #2: optipng -o 4 ./tests/files/rgb_16_should_be_grayscale_8.png + + Time (mean ± σ): 944.2 ms ± 127.5 ms + + Range (min … max): 861.5 ms … 1278.4 ms + diff --git a/README.template.md b/README.template.md new file mode 100644 index 00000000..c5b824cf --- /dev/null +++ b/README.template.md @@ -0,0 +1,96 @@ +# Oxipng + +[![Build Status](https://travis-ci.org/shssoichiro/oxipng.svg?branch=master)](https://travis-ci.org/shssoichiro/oxipng) +[![Version](https://img.shields.io/crates/v/oxipng.svg)](https://crates.io/crates/oxipng) +[![License](https://img.shields.io/crates/l/oxipng.svg)](https://github.com/shssoichiro/oxipng/blob/master/LICENSE) + +## Overview + +Oxipng is a multithreaded lossless PNG compression optimizer. It can be used via a command-line +interface or as a library in other Rust programs. + +## Installing + +Oxipng can be downloaded from the [Releases](https://github.com/shssoichiro/oxipng/releases) link on the GitHub page. + +Oxipng can also be installed from Cargo, via the following command: +``` +cargo install oxipng +``` + +Alternatively, oxipng can be built from source using the latest stable or nightly Rust: +``` +git clone https://github.com/shssoichiro/oxipng.git +cd oxipng +cargo build --release +cp target/release/oxipng /usr/local/bin +``` + +The current minimum supported Rust version is **1.20.0**. Oxipng may compile on earlier versions of Rust, +but there is no guarantee. + +Oxipng follows Semantic Versioning. + +## Usage + +Oxipng is a command-line utility. Basic usage looks similar to the following: + +``` +oxipng -o 4 -i 1 --strip safe *.png +``` + +The most commonly used options are as follows: +* Optimization: `-o 1` through `-o 6`, lower is faster, higher is better compression. +The default (`-o 2`) is sufficiently fast on a modern CPU and provides 30-50% compression +gains over an unoptimized PNG. `-o 4` is 6 times slower than `-o 2` but can provide 5-10% +extra compression over `-o 2`. Using any setting higher than `-o 4` is unlikely +to give any extra compression gains and is not recommended. +* Interlacing: `-i 1` will enable [Adam7](https://en.wikipedia.org/wiki/Adam7_algorithm) +PNG interlacing on any images that are processed. `-i 0` will remove interlacing from all +processed images. Not specifying either will keep the same interlacing state as the +input image. Note: Interlacing can add 25-50% to the size of an optimized image. Only use +it if you believe the benefits outweight the costs for your use case. +* Strip: Used to remove metadata info from processed images. Used via `--strip [safe,all]`. +Can save a few kilobytes if you don't need the metadata. "Safe" removes only metadata that +will never affect rendering of the image. "All" removes all metadata that is not critical +to the image. You can also pass a comma-separated list of specific metadata chunks to remove. +`-s` can be used as a shorthand for `--strip safe`. + +More advanced options can be found by running `oxipng -h`. + +## Library Usage + +Although originally intended to be used as an executable, oxipng can also be used as a library in +other Rust projects. To do so, simply add oxipng as a dependency in your Cargo.toml, +then `extern crate oxipng` in your project. You should then have access to all of the library +functions [documented here](https://docs.rs/oxipng). The simplest +method of usage involves creating an +[Options struct](https://docs.rs/oxipng/0.13.0/oxipng/struct.Options.html) and +passing it, along with an input filename, into the +[optimize function](https://docs.rs/oxipng/0.13.0/oxipng/fn.optimize.html). + +## History + +Oxipng began as a complete rewrite of the OptiPNG project, +which was assumed to be dead as no commit had been made to it since March 2014. +(OptiPNG has since released a new version, after Oxipng was first released.) +The name has been changed to avoid confusion and potential legal issues. + +The core goal of rewriting OptiPNG was to implement multithreading, +which would be very difficult to do within the existing C codebase of OptiPNG. +This also served as an opportunity to choose a more modern, safer language (Rust). + +## Contributing + +Any contributions are welcome and will be accepted via pull request on GitHub. Bug reports can be +filed via GitHub issues. Please include as many details as possible. If you have the capability +to submit a fix with the bug report, it is preferred that you do so via pull request, +however you do not need to be a Rust developer to contribute. +Other contributions (such as improving documentation or translations) are also welcome via GitHub. + +## License + +Oxipng is open-source software, distributed under the MIT license. + +## Benchmarks + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..2edd0046 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,386 @@ +{ + "name": "oxipng", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "2.1.1", + "map-obj": "1.0.1" + } + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "1.0.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "hosted-git-info": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", + "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "2.0.1" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "0.4.1", + "signal-exit": "3.0.2" + } + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "2.1.0", + "decamelize": "1.2.0", + "loud-rejection": "1.6.0", + "map-obj": "1.0.1", + "minimist": "1.2.0", + "normalize-package-data": "2.4.0", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "redent": "1.0.0", + "trim-newlines": "1.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "2.5.0", + "is-builtin-module": "1.0.0", + "semver": "5.5.0", + "validate-npm-package-license": "3.0.1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "2.1.0", + "strip-indent": "1.0.1" + } + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "1.0.2" + } + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dev": true, + "requires": { + "spdx-license-ids": "1.2.2" + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "dev": true + }, + "spdx-license-ids": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "strip-ansi-cli": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi-cli/-/strip-ansi-cli-2.0.0.tgz", + "integrity": "sha1-D91Cq86DJTm/cv+ROSpxqzVGIIw=", + "dev": true, + "requires": { + "meow": "3.7.0", + "strip-ansi": "4.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "4.0.1" + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..59339f1b --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "oxipng", + "version": "1.0.0", + "description": "[![Build Status](https://travis-ci.org/shssoichiro/oxipng.svg?branch=master)](https://travis-ci.org/shssoichiro/oxipng) [![Version](https://img.shields.io/crates/v/oxipng.svg)](https://crates.io/crates/oxipng) [![License](https://img.shields.io/crates/l/oxipng.svg)](https://github.com/shssoichiro/oxipng/blob/master/LICENSE)", + "main": "index.js", + "private": true, + "directories": { + "test": "tests" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/shssoichiro/oxipng.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/shssoichiro/oxipng/issues" + }, + "homepage": "https://github.com/shssoichiro/oxipng#readme", + "devDependencies": { + "strip-ansi-cli": "^2.0.0" + } +} diff --git a/scripts/compare.sh b/scripts/compare.sh new file mode 100755 index 00000000..0f493772 --- /dev/null +++ b/scripts/compare.sh @@ -0,0 +1,14 @@ +#!/bin/bash +cargo build --release +cp README.template.md README.md + +CORES=$(sysctl -n hw.ncpu 2>/dev/null || grep -c ^processor /proc/cpuinfo) +CPU=$(sysctl -n machdep.cpu.brand_string 2>/dev/null || grep '^model name' /proc/cpuinfo | sed 's/model name.\+: //g' | head -n 1) +OXIPNG_VERSION=$(oxipng -V) +OPTIPNG_VERSION=$(optipng -v | head -n 1) +RUST_VERSION=$(rustc -V) +echo "Tested $OXIPNG_VERSION (compiled on $RUST_VERSION) against $OPTIPNG_VERSION on $CPU with $CORES logical cores" >> README.md +echo -e '\n\n' >> README.md + +hyperfine --warmup 3 './target/release/oxipng ./tests/files/rgb_16_should_be_grayscale_8.png' 'optipng ./tests/files/rgb_16_should_be_grayscale_8.png' | ./node_modules/.bin/strip-ansi >> README.md +hyperfine --warmup 3 './target/release/oxipng -o4 ./tests/files/rgb_16_should_be_grayscale_8.png' 'optipng -o 4 ./tests/files/rgb_16_should_be_grayscale_8.png' | ./node_modules/.bin/strip-ansi >> README.md diff --git a/tests/files/rgb_16_should_be_grayscale_8.png b/tests/files/rgb_16_should_be_grayscale_8.png index eb5375a3..b43f7adf 100644 Binary files a/tests/files/rgb_16_should_be_grayscale_8.png and b/tests/files/rgb_16_should_be_grayscale_8.png differ