Merge branch 'master' into patch-1
|
|
@ -1,10 +1,11 @@
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
|
charset = utf-8
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
|
indent_style = space
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.rs]
|
[*.rs]
|
||||||
charset = utf-8
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
|
||||||
2
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Enforce Unix newlines
|
||||||
|
* text=auto eol=lf
|
||||||
6
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
||||||
32
.github/workflows/deploy.yml
vendored
|
|
@ -6,8 +6,9 @@ on:
|
||||||
- 'v*.*.*'
|
- 'v*.*.*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
create-windows-binaries:
|
create-windows-binaries:
|
||||||
|
runs-on: windows-latest
|
||||||
|
if: github.repository == 'shssoichiro/oxipng'
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
|
@ -16,10 +17,10 @@ jobs:
|
||||||
# and I don't have a Windows machine set up to experiment with fixing it.
|
# and I don't have a Windows machine set up to experiment with fixing it.
|
||||||
# conf: [x86_64, i686]
|
# conf: [x86_64, i686]
|
||||||
|
|
||||||
runs-on: windows-latest
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install stable
|
- name: Install stable
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
|
|
@ -55,12 +56,14 @@ jobs:
|
||||||
echo "::set-output name=name::${ARCHIVE_NAME}.zip"
|
echo "::set-output name=name::${ARCHIVE_NAME}.zip"
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.package.outputs.name }}
|
name: ${{ steps.package.outputs.name }}
|
||||||
path: ${{ steps.package.outputs.file }}
|
path: ${{ steps.package.outputs.file }}
|
||||||
|
|
||||||
create-unix-binaries:
|
create-unix-binaries:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
if: github.repository == 'shssoichiro/oxipng'
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
|
@ -71,10 +74,10 @@ jobs:
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
target: x86_64-apple-darwin
|
target: x86_64-apple-darwin
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
|
|
@ -118,20 +121,21 @@ jobs:
|
||||||
echo ::set-output "name=name::${ARCHIVE_NAME}.tar.gz"
|
echo ::set-output "name=name::${ARCHIVE_NAME}.tar.gz"
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.package.outputs.name }}
|
name: ${{ steps.package.outputs.name }}
|
||||||
path: ${{ steps.package.outputs.file }}
|
path: ${{ steps.package.outputs.file }}
|
||||||
|
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository == 'shssoichiro/oxipng'
|
||||||
needs: [create-windows-binaries, create-unix-binaries]
|
needs: [create-windows-binaries, create-unix-binaries]
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Get version and release description
|
- name: Get version and release description
|
||||||
id: tagName
|
id: tagName
|
||||||
|
|
@ -141,7 +145,7 @@ jobs:
|
||||||
echo "::set-output name=tag::$VERSION"
|
echo "::set-output name=tag::$VERSION"
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
path: ./binaries
|
path: ./binaries
|
||||||
|
|
||||||
|
|
|
||||||
29
.github/workflows/oxipng.yml
vendored
|
|
@ -7,10 +7,13 @@ on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
target:
|
target:
|
||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
|
|
@ -45,12 +48,25 @@ jobs:
|
||||||
toolchain: beta
|
toolchain: beta
|
||||||
- target: x86_64-unknown-linux-musl
|
- target: x86_64-unknown-linux-musl
|
||||||
toolchain: nightly
|
toolchain: nightly
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
- name: Install musl tools
|
- name: Install musl tools
|
||||||
run: sudo apt-get install musl-tools
|
run: sudo apt-get install musl-tools
|
||||||
if: "contains(matrix.target, 'musl')"
|
if: "contains(matrix.target, 'musl')"
|
||||||
|
- name: Cache cargo registry
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/bin/
|
||||||
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
~/.cargo/git/db/
|
||||||
|
key: ${{ runner.os }}-${{ matrix.toolchain }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-${{ matrix.toolchain }}-cargo-registry-
|
||||||
- name: Install ${{ matrix.toolchain }}
|
- name: Install ${{ matrix.toolchain }}
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
|
|
@ -58,13 +74,6 @@ jobs:
|
||||||
toolchain: ${{ matrix.toolchain }}
|
toolchain: ${{ matrix.toolchain }}
|
||||||
override: true
|
override: true
|
||||||
components: clippy, rustfmt
|
components: clippy, rustfmt
|
||||||
- name: Cache cargo registry
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: ~/.cargo/registry/cache
|
|
||||||
key: ${{ matrix.target }}-${{ matrix.toolchain }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ matrix.target }}-${{ matrix.toolchain }}-cargo-registry-
|
|
||||||
- name: Run rustfmt
|
- name: Run rustfmt
|
||||||
if: matrix.toolchain == 'stable'
|
if: matrix.toolchain == 'stable'
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
|
|
@ -78,7 +87,7 @@ jobs:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
args: -- -D warnings
|
args: -- -D warnings
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test
|
run: cargo test --features sanity-checks
|
||||||
- name: Build benchmarks
|
- name: Build benchmarks
|
||||||
if: matrix.toolchain == 'nightly'
|
if: matrix.toolchain == 'nightly'
|
||||||
run: cargo bench --no-run
|
run: cargo bench --no-run
|
||||||
|
|
|
||||||
160
Cargo.lock
generated
|
|
@ -45,9 +45,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.12.3"
|
version = "1.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f"
|
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
|
|
@ -57,9 +57,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.78"
|
version = "1.0.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
|
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
|
|
@ -69,9 +69,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "3.2.23"
|
version = "3.2.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
|
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
|
@ -108,9 +108,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.6"
|
version = "0.5.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
|
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
|
|
@ -118,9 +118,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-deque"
|
name = "crossbeam-deque"
|
||||||
version = "0.8.2"
|
version = "0.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
|
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"crossbeam-epoch",
|
"crossbeam-epoch",
|
||||||
|
|
@ -129,9 +129,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-epoch"
|
name = "crossbeam-epoch"
|
||||||
version = "0.9.13"
|
version = "0.9.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
|
checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
|
@ -142,24 +142,33 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.14"
|
version = "0.8.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
|
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.8.0"
|
version = "1.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fdeflate"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filetime"
|
name = "filetime"
|
||||||
version = "0.2.19"
|
version = "0.2.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9"
|
checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -169,9 +178,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.25"
|
version = "1.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
|
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
|
|
@ -185,9 +194,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
|
|
@ -215,9 +224,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.24.5"
|
version = "0.24.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945"
|
checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
|
@ -229,9 +238,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.2"
|
version = "1.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
|
@ -240,9 +249,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.139"
|
version = "0.2.144"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libdeflate-sys"
|
name = "libdeflate-sys"
|
||||||
|
|
@ -273,20 +282,21 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memoffset"
|
name = "memoffset"
|
||||||
version = "0.7.1"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.6.2"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
|
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -331,15 +341,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.16.0"
|
version = "1.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "os_str_bytes"
|
name = "os_str_bytes"
|
||||||
version = "6.4.1"
|
version = "6.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
|
checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxipng"
|
name = "oxipng"
|
||||||
|
|
@ -364,12 +374,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "png"
|
name = "png"
|
||||||
version = "0.17.7"
|
version = "0.17.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638"
|
checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
"flate2",
|
"flate2",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
@ -382,9 +393,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayon"
|
name = "rayon"
|
||||||
version = "1.6.1"
|
version = "1.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
|
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
"rayon-core",
|
"rayon-core",
|
||||||
|
|
@ -392,9 +403,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayon-core"
|
name = "rayon-core"
|
||||||
version = "1.10.1"
|
version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3"
|
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"crossbeam-deque",
|
"crossbeam-deque",
|
||||||
|
|
@ -413,9 +424,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rgb"
|
name = "rgb"
|
||||||
version = "0.8.34"
|
version = "0.8.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3603b7d71ca82644f79b5a06d1220e9a58ede60bd32255f698cb1af8838b8db3"
|
checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
|
|
@ -443,9 +454,15 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.16"
|
version = "1.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
|
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-adler32"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd-adler32"
|
name = "simd-adler32"
|
||||||
|
|
@ -494,10 +511,11 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.4"
|
version = "1.1.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
|
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -549,9 +567,18 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.42.0"
|
version = "0.48.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm",
|
"windows_aarch64_gnullvm",
|
||||||
"windows_aarch64_msvc",
|
"windows_aarch64_msvc",
|
||||||
|
|
@ -564,45 +591,45 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.42.0"
|
version = "0.48.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
|
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.42.0"
|
version = "0.48.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
|
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.42.0"
|
version = "0.48.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
|
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.42.0"
|
version = "0.48.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
|
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.42.0"
|
version = "0.48.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
|
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.42.0"
|
version = "0.48.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
|
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.42.0"
|
version = "0.48.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wyz"
|
name = "wyz"
|
||||||
|
|
@ -615,11 +642,10 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zopfli"
|
name = "zopfli"
|
||||||
version = "0.7.2"
|
version = "0.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a5b2bed49d3f0af28729a2338ee8c3a48eba2133a78ebc560779be161ebaaad8"
|
checksum = "4e0650ae6a051326d798eb099b632f1afb0d323d25ee4ec82ffb0779512084d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"log",
|
"log",
|
||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
|
|
|
||||||
27
Cargo.toml
|
|
@ -22,40 +22,44 @@ name = "oxipng"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
required-features = ["binary"]
|
required-features = ["binary"]
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "zopfli"
|
||||||
|
required-features = ["zopfli"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
zopfli = { version = "0.7.2", optional = true }
|
zopfli = { version = "0.7.4", optional = true, default-features = false, features = ["std", "zlib"] }
|
||||||
rgb = "0.8.33"
|
rgb = "0.8.36"
|
||||||
indexmap = "1.9.1"
|
indexmap = "1.9.3"
|
||||||
libdeflater = "0.13.0"
|
libdeflater = "0.11.0"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
stderrlog = { version = "0.5.3", optional = true, default-features = false }
|
stderrlog = { version = "0.5.4", optional = true, default-features = false }
|
||||||
bitvec = "1.0.1"
|
bitvec = "1.0.1"
|
||||||
rustc-hash = "1.1.0"
|
rustc-hash = "1.1.0"
|
||||||
|
|
||||||
[dependencies.crossbeam-channel]
|
[dependencies.crossbeam-channel]
|
||||||
optional = true
|
optional = true
|
||||||
version = "0.5.6"
|
version = "0.5.8"
|
||||||
|
|
||||||
[dependencies.filetime]
|
[dependencies.filetime]
|
||||||
optional = true
|
optional = true
|
||||||
version = "0.2.17"
|
version = "0.2.21"
|
||||||
|
|
||||||
[dependencies.rayon]
|
[dependencies.rayon]
|
||||||
optional = true
|
optional = true
|
||||||
version = "1.5.3"
|
version = "1.7.0"
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
optional = true
|
version = "3.2.25"
|
||||||
version = "3.2.23"
|
|
||||||
|
|
||||||
[dependencies.wild]
|
[dependencies.wild]
|
||||||
optional = true
|
optional = true
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
|
|
||||||
[dependencies.image]
|
[dependencies.image]
|
||||||
|
optional = true
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["png"]
|
features = ["png"]
|
||||||
version = "0.24.3"
|
version = "0.24.6"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
rustc_version = "0.4.0"
|
rustc_version = "0.4.0"
|
||||||
|
|
@ -65,6 +69,7 @@ binary = ["clap", "wild", "stderrlog"]
|
||||||
default = ["binary", "filetime", "parallel", "zopfli"]
|
default = ["binary", "filetime", "parallel", "zopfli"]
|
||||||
parallel = ["rayon", "indexmap/rayon", "crossbeam-channel"]
|
parallel = ["rayon", "indexmap/rayon", "crossbeam-channel"]
|
||||||
freestanding = ["libdeflater/freestanding"]
|
freestanding = ["libdeflater/freestanding"]
|
||||||
|
sanity-checks = ["image"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "oxipng"
|
name = "oxipng"
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,15 @@
|
||||||
extern crate oxipng;
|
extern crate oxipng;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
|
use oxipng::internal_tests::*;
|
||||||
|
use oxipng::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
use oxipng::internal_tests::*;
|
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn deflate_16_bits(b: &mut Bencher) {
|
fn deflate_16_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let min = AtomicMin::new(None);
|
let min = AtomicMin::new(None);
|
||||||
|
|
@ -22,7 +22,7 @@ fn deflate_16_bits(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn deflate_8_bits(b: &mut Bencher) {
|
fn deflate_8_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let min = AtomicMin::new(None);
|
let min = AtomicMin::new(None);
|
||||||
|
|
@ -35,7 +35,7 @@ fn deflate_4_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let min = AtomicMin::new(None);
|
let min = AtomicMin::new(None);
|
||||||
|
|
@ -48,7 +48,7 @@ fn deflate_2_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let min = AtomicMin::new(None);
|
let min = AtomicMin::new(None);
|
||||||
|
|
@ -61,7 +61,7 @@ fn deflate_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let min = AtomicMin::new(None);
|
let min = AtomicMin::new(None);
|
||||||
|
|
@ -72,7 +72,7 @@ fn deflate_1_bits(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn inflate_generic(b: &mut Bencher) {
|
fn inflate_generic(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| inflate(png.idat_data.as_ref(), png.raw.ihdr.raw_data_size()));
|
b.iter(|| inflate(png.idat_data.as_ref(), png.raw.ihdr.raw_data_size()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,15 @@
|
||||||
extern crate oxipng;
|
extern crate oxipng;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use oxipng::{internal_tests::*, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
|
use oxipng::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_16_bits_filter_0(b: &mut Bencher) {
|
fn filters_16_bits_filter_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::None, false);
|
png.raw.filter_image(RowFilter::None, false);
|
||||||
|
|
@ -20,7 +21,7 @@ fn filters_16_bits_filter_0(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_8_bits_filter_0(b: &mut Bencher) {
|
fn filters_8_bits_filter_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::None, false);
|
png.raw.filter_image(RowFilter::None, false);
|
||||||
|
|
@ -32,7 +33,7 @@ fn filters_4_bits_filter_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::None, false);
|
png.raw.filter_image(RowFilter::None, false);
|
||||||
|
|
@ -44,7 +45,7 @@ fn filters_2_bits_filter_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::None, false);
|
png.raw.filter_image(RowFilter::None, false);
|
||||||
|
|
@ -56,7 +57,7 @@ fn filters_1_bits_filter_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::None, false);
|
png.raw.filter_image(RowFilter::None, false);
|
||||||
|
|
@ -66,7 +67,7 @@ fn filters_1_bits_filter_0(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_16_bits_filter_1(b: &mut Bencher) {
|
fn filters_16_bits_filter_1(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Sub, false);
|
png.raw.filter_image(RowFilter::Sub, false);
|
||||||
|
|
@ -76,7 +77,7 @@ fn filters_16_bits_filter_1(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_8_bits_filter_1(b: &mut Bencher) {
|
fn filters_8_bits_filter_1(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Sub, false);
|
png.raw.filter_image(RowFilter::Sub, false);
|
||||||
|
|
@ -88,7 +89,7 @@ fn filters_4_bits_filter_1(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Sub, false);
|
png.raw.filter_image(RowFilter::Sub, false);
|
||||||
|
|
@ -100,7 +101,7 @@ fn filters_2_bits_filter_1(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Sub, false);
|
png.raw.filter_image(RowFilter::Sub, false);
|
||||||
|
|
@ -112,7 +113,7 @@ fn filters_1_bits_filter_1(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Sub, false);
|
png.raw.filter_image(RowFilter::Sub, false);
|
||||||
|
|
@ -122,7 +123,7 @@ fn filters_1_bits_filter_1(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_16_bits_filter_2(b: &mut Bencher) {
|
fn filters_16_bits_filter_2(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Up, false);
|
png.raw.filter_image(RowFilter::Up, false);
|
||||||
|
|
@ -132,7 +133,7 @@ fn filters_16_bits_filter_2(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_8_bits_filter_2(b: &mut Bencher) {
|
fn filters_8_bits_filter_2(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Up, false);
|
png.raw.filter_image(RowFilter::Up, false);
|
||||||
|
|
@ -144,7 +145,7 @@ fn filters_4_bits_filter_2(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Up, false);
|
png.raw.filter_image(RowFilter::Up, false);
|
||||||
|
|
@ -156,7 +157,7 @@ fn filters_2_bits_filter_2(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Up, false);
|
png.raw.filter_image(RowFilter::Up, false);
|
||||||
|
|
@ -168,7 +169,7 @@ fn filters_1_bits_filter_2(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Up, false);
|
png.raw.filter_image(RowFilter::Up, false);
|
||||||
|
|
@ -178,7 +179,7 @@ fn filters_1_bits_filter_2(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_16_bits_filter_3(b: &mut Bencher) {
|
fn filters_16_bits_filter_3(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Average, false);
|
png.raw.filter_image(RowFilter::Average, false);
|
||||||
|
|
@ -188,7 +189,7 @@ fn filters_16_bits_filter_3(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_8_bits_filter_3(b: &mut Bencher) {
|
fn filters_8_bits_filter_3(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Average, false);
|
png.raw.filter_image(RowFilter::Average, false);
|
||||||
|
|
@ -200,7 +201,7 @@ fn filters_4_bits_filter_3(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Average, false);
|
png.raw.filter_image(RowFilter::Average, false);
|
||||||
|
|
@ -212,7 +213,7 @@ fn filters_2_bits_filter_3(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Average, false);
|
png.raw.filter_image(RowFilter::Average, false);
|
||||||
|
|
@ -224,7 +225,7 @@ fn filters_1_bits_filter_3(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Average, false);
|
png.raw.filter_image(RowFilter::Average, false);
|
||||||
|
|
@ -234,7 +235,7 @@ fn filters_1_bits_filter_3(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_16_bits_filter_4(b: &mut Bencher) {
|
fn filters_16_bits_filter_4(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Paeth, false);
|
png.raw.filter_image(RowFilter::Paeth, false);
|
||||||
|
|
@ -244,7 +245,7 @@ fn filters_16_bits_filter_4(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_8_bits_filter_4(b: &mut Bencher) {
|
fn filters_8_bits_filter_4(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Paeth, false);
|
png.raw.filter_image(RowFilter::Paeth, false);
|
||||||
|
|
@ -256,7 +257,7 @@ fn filters_4_bits_filter_4(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Paeth, false);
|
png.raw.filter_image(RowFilter::Paeth, false);
|
||||||
|
|
@ -268,7 +269,7 @@ fn filters_2_bits_filter_4(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Paeth, false);
|
png.raw.filter_image(RowFilter::Paeth, false);
|
||||||
|
|
@ -280,7 +281,7 @@ fn filters_1_bits_filter_4(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Paeth, false);
|
png.raw.filter_image(RowFilter::Paeth, false);
|
||||||
|
|
@ -290,7 +291,7 @@ fn filters_1_bits_filter_4(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_16_bits_filter_5(b: &mut Bencher) {
|
fn filters_16_bits_filter_5(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum, false);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
|
|
@ -300,7 +301,7 @@ fn filters_16_bits_filter_5(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_8_bits_filter_5(b: &mut Bencher) {
|
fn filters_8_bits_filter_5(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum, false);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
|
|
@ -312,7 +313,7 @@ fn filters_4_bits_filter_5(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum, false);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
|
|
@ -324,7 +325,7 @@ fn filters_2_bits_filter_5(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum, false);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
|
|
@ -336,7 +337,7 @@ fn filters_1_bits_filter_5(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum, false);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,15 @@
|
||||||
extern crate oxipng;
|
extern crate oxipng;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use oxipng::{internal_tests::*, Interlacing};
|
use oxipng::internal_tests::*;
|
||||||
|
use oxipng::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn interlacing_16_bits(b: &mut Bencher) {
|
fn interlacing_16_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +19,7 @@ fn interlacing_16_bits(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn interlacing_8_bits(b: &mut Bencher) {
|
fn interlacing_8_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +29,7 @@ fn interlacing_4_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +39,7 @@ fn interlacing_2_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +49,7 @@ fn interlacing_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
b.iter(|| png.raw.change_interlacing(Interlacing::Adam7));
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +59,7 @@ fn deinterlacing_16_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/interlaced_rgb_16_should_be_rgb_16.png",
|
"tests/files/interlaced_rgb_16_should_be_rgb_16.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +69,7 @@ fn deinterlacing_8_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/interlaced_rgb_8_should_be_rgb_8.png",
|
"tests/files/interlaced_rgb_8_should_be_rgb_8.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
||||||
}
|
}
|
||||||
|
|
@ -78,7 +79,7 @@ fn deinterlacing_4_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/interlaced_palette_4_should_be_palette_4.png",
|
"tests/files/interlaced_palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +89,7 @@ fn deinterlacing_2_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/interlaced_palette_2_should_be_palette_2.png",
|
"tests/files/interlaced_palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +99,7 @@ fn deinterlacing_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/interlaced_palette_1_should_be_palette_1.png",
|
"tests/files/interlaced_palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
b.iter(|| png.raw.change_interlacing(Interlacing::None));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,24 @@ extern crate oxipng;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use oxipng::internal_tests::*;
|
use oxipng::internal_tests::*;
|
||||||
|
use oxipng::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn reductions_16_to_8_bits(b: &mut Bencher) {
|
fn reductions_16_to_8_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduce_bit_depth(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_16_to_8(&png.raw, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn reductions_16_to_8_bits_scaled(b: &mut Bencher) {
|
||||||
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
|
b.iter(|| bit_depth::reduced_bit_depth_16_to_8(&png.raw, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -20,9 +29,9 @@ fn reductions_8_to_4_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_8_should_be_palette_4.png",
|
"tests/files/palette_8_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduce_bit_depth(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -30,9 +39,9 @@ fn reductions_8_to_2_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_8_should_be_palette_2.png",
|
"tests/files/palette_8_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduce_bit_depth(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -40,9 +49,9 @@ fn reductions_8_to_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_8_should_be_palette_1.png",
|
"tests/files/palette_8_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduce_bit_depth(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -50,9 +59,9 @@ fn reductions_4_to_2_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_2.png",
|
"tests/files/palette_4_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduce_bit_depth(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -60,9 +69,9 @@ fn reductions_4_to_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_1.png",
|
"tests/files/palette_4_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduce_bit_depth(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -70,9 +79,9 @@ fn reductions_2_to_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_1.png",
|
"tests/files/palette_2_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduce_bit_depth(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -80,9 +89,9 @@ fn reductions_grayscale_8_to_4_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/grayscale_8_should_be_grayscale_4.png",
|
"tests/files/grayscale_8_should_be_grayscale_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduce_bit_depth(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -90,9 +99,9 @@ fn reductions_grayscale_8_to_2_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/grayscale_8_should_be_grayscale_2.png",
|
"tests/files/grayscale_8_should_be_grayscale_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduce_bit_depth(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -100,9 +109,9 @@ fn reductions_grayscale_8_to_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/grayscale_8_should_be_grayscale_1.png",
|
"tests/files/grayscale_8_should_be_grayscale_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduce_bit_depth(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -110,9 +119,9 @@ fn reductions_grayscale_4_to_2_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/grayscale_4_should_be_grayscale_2.png",
|
"tests/files/grayscale_4_should_be_grayscale_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduce_bit_depth(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -120,9 +129,9 @@ fn reductions_grayscale_4_to_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/grayscale_4_should_be_grayscale_1.png",
|
"tests/files/grayscale_4_should_be_grayscale_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduce_bit_depth(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -130,25 +139,25 @@ fn reductions_grayscale_2_to_1_bits(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/grayscale_2_should_be_grayscale_1.png",
|
"tests/files/grayscale_2_should_be_grayscale_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| bit_depth::reduce_bit_depth(&png.raw, 1));
|
b.iter(|| bit_depth::reduced_bit_depth_8_or_less(&png.raw, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn reductions_rgba_to_rgb_16(b: &mut Bencher) {
|
fn reductions_rgba_to_rgb_16(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgba_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgba_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw, true, false));
|
b.iter(|| alpha::reduced_alpha_channel(&png.raw, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn reductions_rgba_to_rgb_8(b: &mut Bencher) {
|
fn reductions_rgba_to_rgb_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw, true, false));
|
b.iter(|| alpha::reduced_alpha_channel(&png.raw, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -156,9 +165,9 @@ fn reductions_rgba_to_grayscale_alpha_16(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/rgba_16_should_be_grayscale_alpha_16.png",
|
"tests/files/rgba_16_should_be_grayscale_alpha_16.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw, true, false));
|
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -166,9 +175,9 @@ fn reductions_rgba_to_grayscale_alpha_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/rgba_8_should_be_grayscale_alpha_8.png",
|
"tests/files/rgba_8_should_be_grayscale_alpha_8.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw, true, false));
|
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -176,9 +185,12 @@ fn reductions_rgba_to_grayscale_16(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/rgba_16_should_be_grayscale_16.png",
|
"tests/files/rgba_16_should_be_grayscale_16.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw, true, false));
|
b.iter(|| {
|
||||||
|
color::reduced_rgb_to_grayscale(&png.raw)
|
||||||
|
.and_then(|r| alpha::reduced_alpha_channel(&r, false))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -186,9 +198,12 @@ fn reductions_rgba_to_grayscale_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/rgba_8_should_be_grayscale_8.png",
|
"tests/files/rgba_8_should_be_grayscale_8.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw, true, false));
|
b.iter(|| {
|
||||||
|
color::reduced_rgb_to_grayscale(&png.raw)
|
||||||
|
.and_then(|r| alpha::reduced_alpha_channel(&r, false))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -196,33 +211,53 @@ fn reductions_rgb_to_grayscale_16(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/rgb_16_should_be_grayscale_16.png",
|
"tests/files/rgb_16_should_be_grayscale_16.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw, true, false));
|
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn reductions_rgb_to_grayscale_8(b: &mut Bencher) {
|
fn reductions_rgb_to_grayscale_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_grayscale_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_grayscale_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw, true, false));
|
b.iter(|| color::reduced_rgb_to_grayscale(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn reductions_rgba_to_palette_8(b: &mut Bencher) {
|
fn reductions_rgba_to_palette_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_palette_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgba_8_should_be_palette_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw, true, false));
|
b.iter(|| color::reduced_to_indexed(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn reductions_rgb_to_palette_8(b: &mut Bencher) {
|
fn reductions_rgb_to_palette_8(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_palette_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_palette_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduce_color_type(&png.raw, true, false));
|
b.iter(|| color::reduced_to_indexed(&png.raw));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn reductions_grayscale_8_to_palette_8(b: &mut Bencher) {
|
||||||
|
let input = test::black_box(PathBuf::from(
|
||||||
|
"tests/files/grayscale_8_should_be_palette_8.png",
|
||||||
|
));
|
||||||
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
|
b.iter(|| color::reduced_to_indexed(&png.raw));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn reductions_palette_8_to_grayscale_8(b: &mut Bencher) {
|
||||||
|
let input = test::black_box(PathBuf::from(
|
||||||
|
"tests/files/palette_8_should_be_grayscale_8.png",
|
||||||
|
));
|
||||||
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
|
b.iter(|| color::indexed_to_channels(&png.raw, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -230,9 +265,9 @@ fn reductions_palette_duplicate_reduction(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_should_be_reduced_with_dupes.png",
|
"tests/files/palette_should_be_reduced_with_dupes.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduced_palette(&png.raw, false));
|
b.iter(|| palette::reduced_palette(&png.raw, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -240,9 +275,9 @@ fn reductions_palette_unused_reduction(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_should_be_reduced_with_unused.png",
|
"tests/files/palette_should_be_reduced_with_unused.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduced_palette(&png.raw, false));
|
b.iter(|| palette::reduced_palette(&png.raw, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|
@ -250,15 +285,25 @@ fn reductions_palette_full_reduction(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_should_be_reduced_with_both.png",
|
"tests/files/palette_should_be_reduced_with_both.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| reduced_palette(&png.raw, false));
|
b.iter(|| palette::reduced_palette(&png.raw, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn reductions_palette_sort(b: &mut Bencher) {
|
||||||
|
let input = test::black_box(PathBuf::from(
|
||||||
|
"tests/files/palette_8_should_be_palette_8.png",
|
||||||
|
));
|
||||||
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
|
b.iter(|| palette::sorted_palette(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn reductions_alpha(b: &mut Bencher) {
|
fn reductions_alpha(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgba_8_reduce_alpha.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgba_8_reduce_alpha.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| alpha::cleaned_alpha_channel(&png.raw));
|
b.iter(|| alpha::cleaned_alpha_channel(&png.raw));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,15 @@
|
||||||
extern crate oxipng;
|
extern crate oxipng;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use oxipng::{internal_tests::*, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
|
use oxipng::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_minsum(b: &mut Bencher) {
|
fn filters_minsum(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::MinSum, false);
|
png.raw.filter_image(RowFilter::MinSum, false);
|
||||||
|
|
@ -20,7 +21,7 @@ fn filters_minsum(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_entropy(b: &mut Bencher) {
|
fn filters_entropy(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Entropy, false);
|
png.raw.filter_image(RowFilter::Entropy, false);
|
||||||
|
|
@ -30,7 +31,7 @@ fn filters_entropy(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_bigrams(b: &mut Bencher) {
|
fn filters_bigrams(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Bigrams, false);
|
png.raw.filter_image(RowFilter::Bigrams, false);
|
||||||
|
|
@ -40,7 +41,7 @@ fn filters_bigrams(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_bigent(b: &mut Bencher) {
|
fn filters_bigent(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::BigEnt, false);
|
png.raw.filter_image(RowFilter::BigEnt, false);
|
||||||
|
|
@ -50,7 +51,7 @@ fn filters_bigent(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn filters_brute(b: &mut Bencher) {
|
fn filters_brute(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
png.raw.filter_image(RowFilter::Brute, false);
|
png.raw.filter_image(RowFilter::Brute, false);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ extern crate oxipng;
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use oxipng::internal_tests::*;
|
use oxipng::internal_tests::*;
|
||||||
|
use oxipng::*;
|
||||||
use std::num::NonZeroU8;
|
use std::num::NonZeroU8;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
@ -14,7 +15,7 @@ const DEFAULT_ZOPFLI_ITERATIONS: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(1
|
||||||
#[bench]
|
#[bench]
|
||||||
fn zopfli_16_bits_strategy_0(b: &mut Bencher) {
|
fn zopfli_16_bits_strategy_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
||||||
|
|
@ -24,7 +25,7 @@ fn zopfli_16_bits_strategy_0(b: &mut Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn zopfli_8_bits_strategy_0(b: &mut Bencher) {
|
fn zopfli_8_bits_strategy_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
let input = test::black_box(PathBuf::from("tests/files/rgb_8_should_be_rgb_8.png"));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
||||||
|
|
@ -36,7 +37,7 @@ fn zopfli_4_bits_strategy_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
||||||
|
|
@ -48,7 +49,7 @@ fn zopfli_2_bits_strategy_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
||||||
|
|
@ -60,7 +61,7 @@ fn zopfli_1_bits_strategy_0(b: &mut Bencher) {
|
||||||
let input = test::black_box(PathBuf::from(
|
let input = test::black_box(PathBuf::from(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
));
|
));
|
||||||
let png = PngData::new(&input, false).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
zopfli_deflate(png.raw.data.as_ref(), DEFAULT_ZOPFLI_ITERATIONS).ok();
|
||||||
|
|
|
||||||
147
src/colors.rs
|
|
@ -1,119 +1,124 @@
|
||||||
use std::fmt;
|
use rgb::{RGB16, RGBA8};
|
||||||
|
use std::{fmt, fmt::Display};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
use crate::PngError;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
/// The color type used to represent this image
|
/// The color type used to represent this image
|
||||||
pub enum ColorType {
|
pub enum ColorType {
|
||||||
/// Grayscale, with one color channel
|
/// Grayscale, with one color channel
|
||||||
Grayscale,
|
Grayscale {
|
||||||
|
/// Optional shade of gray that should be rendered as transparent
|
||||||
|
transparent_shade: Option<u16>,
|
||||||
|
},
|
||||||
/// RGB, with three color channels
|
/// RGB, with three color channels
|
||||||
RGB,
|
RGB {
|
||||||
/// Indexed, with one byte per pixel representing one of up to 256 colors in the image
|
/// Optional color value that should be rendered as transparent
|
||||||
Indexed,
|
transparent_color: Option<RGB16>,
|
||||||
|
},
|
||||||
|
/// Indexed, with one byte per pixel representing a color from the palette
|
||||||
|
Indexed {
|
||||||
|
/// The palette containing the colors used, up to 256 entries
|
||||||
|
palette: Vec<RGBA8>,
|
||||||
|
},
|
||||||
/// Grayscale + Alpha, with two color channels
|
/// Grayscale + Alpha, with two color channels
|
||||||
GrayscaleAlpha,
|
GrayscaleAlpha,
|
||||||
/// RGBA, with four color channels
|
/// RGBA, with four color channels
|
||||||
RGBA,
|
RGBA,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ColorType {
|
impl Display for ColorType {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
match self {
|
||||||
f,
|
ColorType::Grayscale { .. } => Display::fmt("Grayscale", f),
|
||||||
"{}",
|
ColorType::RGB { .. } => Display::fmt("RGB", f),
|
||||||
match *self {
|
ColorType::Indexed { palette } => {
|
||||||
ColorType::Grayscale => "Grayscale",
|
Display::fmt(&format!("Indexed ({} colors)", palette.len()), f)
|
||||||
ColorType::RGB => "RGB",
|
}
|
||||||
ColorType::Indexed => "Indexed",
|
ColorType::GrayscaleAlpha => Display::fmt("Grayscale + Alpha", f),
|
||||||
ColorType::GrayscaleAlpha => "Grayscale + Alpha",
|
ColorType::RGBA => Display::fmt("RGB + Alpha", f),
|
||||||
ColorType::RGBA => "RGB + Alpha",
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ColorType {
|
impl ColorType {
|
||||||
/// Get the code used by the PNG specification to denote this color type
|
/// Get the code used by the PNG specification to denote this color type
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn png_header_code(self) -> u8 {
|
pub fn png_header_code(&self) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
ColorType::Grayscale => 0,
|
ColorType::Grayscale { .. } => 0,
|
||||||
ColorType::RGB => 2,
|
ColorType::RGB { .. } => 2,
|
||||||
ColorType::Indexed => 3,
|
ColorType::Indexed { .. } => 3,
|
||||||
ColorType::GrayscaleAlpha => 4,
|
ColorType::GrayscaleAlpha => 4,
|
||||||
ColorType::RGBA => 6,
|
ColorType::RGBA => 6,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn channels_per_pixel(self) -> u8 {
|
pub(crate) fn channels_per_pixel(&self) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
ColorType::Grayscale | ColorType::Indexed => 1,
|
ColorType::Grayscale { .. } | ColorType::Indexed { .. } => 1,
|
||||||
ColorType::GrayscaleAlpha => 2,
|
ColorType::GrayscaleAlpha => 2,
|
||||||
ColorType::RGB => 3,
|
ColorType::RGB { .. } => 3,
|
||||||
ColorType::RGBA => 4,
|
ColorType::RGBA => 4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn is_rgb(&self) -> bool {
|
||||||
|
matches!(self, ColorType::RGB { .. } | ColorType::RGBA)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn has_alpha(&self) -> bool {
|
||||||
|
matches!(self, ColorType::GrayscaleAlpha | ColorType::RGBA)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn has_trns(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ColorType::Grayscale { transparent_shade } => transparent_shade.is_some(),
|
||||||
|
ColorType::RGB { transparent_color } => transparent_color.is_some(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||||
/// The number of bits to be used per channel per pixel
|
/// The number of bits to be used per channel per pixel
|
||||||
pub enum BitDepth {
|
pub enum BitDepth {
|
||||||
/// One bit per channel per pixel
|
/// One bit per channel per pixel
|
||||||
One,
|
One = 1,
|
||||||
/// Two bits per channel per pixel
|
/// Two bits per channel per pixel
|
||||||
Two,
|
Two = 2,
|
||||||
/// Four bits per channel per pixel
|
/// Four bits per channel per pixel
|
||||||
Four,
|
Four = 4,
|
||||||
/// Eight bits per channel per pixel
|
/// Eight bits per channel per pixel
|
||||||
Eight,
|
Eight = 8,
|
||||||
/// Sixteen bits per channel per pixel
|
/// Sixteen bits per channel per pixel
|
||||||
Sixteen,
|
Sixteen = 16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for BitDepth {
|
impl TryFrom<u8> for BitDepth {
|
||||||
|
type Error = PngError;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
1 => Ok(Self::One),
|
||||||
|
2 => Ok(Self::Two),
|
||||||
|
4 => Ok(Self::Four),
|
||||||
|
8 => Ok(Self::Eight),
|
||||||
|
16 => Ok(Self::Sixteen),
|
||||||
|
_ => Err(PngError::new("Unexpected bit depth")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for BitDepth {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
Display::fmt(&(*self as u8).to_string(), f)
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
match *self {
|
|
||||||
BitDepth::One => "1",
|
|
||||||
BitDepth::Two => "2",
|
|
||||||
BitDepth::Four => "4",
|
|
||||||
BitDepth::Eight => "8",
|
|
||||||
BitDepth::Sixteen => "16",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BitDepth {
|
|
||||||
/// Retrieve the number of bits per channel per pixel as a `u8`
|
|
||||||
#[inline]
|
|
||||||
pub fn as_u8(self) -> u8 {
|
|
||||||
match self {
|
|
||||||
BitDepth::One => 1,
|
|
||||||
BitDepth::Two => 2,
|
|
||||||
BitDepth::Four => 4,
|
|
||||||
BitDepth::Eight => 8,
|
|
||||||
BitDepth::Sixteen => 16,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Parse a number of bits per channel per pixel into a `BitDepth`
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// If depth is unsupported
|
|
||||||
#[inline]
|
|
||||||
pub fn from_u8(depth: u8) -> BitDepth {
|
|
||||||
match depth {
|
|
||||||
1 => BitDepth::One,
|
|
||||||
2 => BitDepth::Two,
|
|
||||||
4 => BitDepth::Four,
|
|
||||||
8 => BitDepth::Eight,
|
|
||||||
16 => BitDepth::Sixteen,
|
|
||||||
_ => panic!("Unsupported bit depth"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,8 @@ pub fn deflate(data: &[u8], level: u8, max_size: &AtomicMin) -> PngResult<Vec<u8
|
||||||
let len = compressor
|
let len = compressor
|
||||||
.zlib_compress(data, &mut dest)
|
.zlib_compress(data, &mut dest)
|
||||||
.map_err(|err| match err {
|
.map_err(|err| match err {
|
||||||
CompressionError::InsufficientSpace => PngError::DeflatedDataTooLong(capacity),
|
CompressionError::InsufficientSpace => PngError::DeflatedDataTooLong(capacity - 9),
|
||||||
})?;
|
})?;
|
||||||
if let Some(max) = max_size.get() {
|
|
||||||
if len > max {
|
|
||||||
return Err(PngError::DeflatedDataTooLong(max));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dest.truncate(len);
|
dest.truncate(len);
|
||||||
Ok(dest)
|
Ok(dest)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
mod deflater;
|
mod deflater;
|
||||||
|
use crate::AtomicMin;
|
||||||
|
use crate::{PngError, PngResult};
|
||||||
pub use deflater::crc32;
|
pub use deflater::crc32;
|
||||||
pub use deflater::deflate;
|
pub use deflater::deflate;
|
||||||
pub use deflater::inflate;
|
pub use deflater::inflate;
|
||||||
|
use std::{fmt, fmt::Display};
|
||||||
|
|
||||||
#[cfg(feature = "zopfli")]
|
#[cfg(feature = "zopfli")]
|
||||||
use std::num::NonZeroU8;
|
use std::num::NonZeroU8;
|
||||||
|
|
@ -27,3 +30,30 @@ pub enum Deflaters {
|
||||||
iterations: NonZeroU8,
|
iterations: NonZeroU8,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deflaters {
|
||||||
|
pub(crate) fn deflate(self, data: &[u8], max_size: &AtomicMin) -> PngResult<Vec<u8>> {
|
||||||
|
let compressed = match self {
|
||||||
|
Self::Libdeflater { compression } => deflate(data, compression, max_size)?,
|
||||||
|
#[cfg(feature = "zopfli")]
|
||||||
|
Self::Zopfli { iterations } => zopfli_deflate(data, iterations)?,
|
||||||
|
};
|
||||||
|
if let Some(max) = max_size.get() {
|
||||||
|
if compressed.len() > max {
|
||||||
|
return Err(PngError::DeflatedDataTooLong(max));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(compressed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Deflaters {
|
||||||
|
#[inline]
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Libdeflater { compression } => Display::fmt(compression, f),
|
||||||
|
#[cfg(feature = "zopfli")]
|
||||||
|
Self::Zopfli { .. } => Display::fmt("zopfli", f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
11
src/error.rs
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::colors::{BitDepth, ColorType};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
|
@ -11,6 +12,8 @@ pub enum PngError {
|
||||||
InvalidData,
|
InvalidData,
|
||||||
TruncatedData,
|
TruncatedData,
|
||||||
ChunkMissing(&'static str),
|
ChunkMissing(&'static str),
|
||||||
|
InvalidDepthForType(BitDepth, ColorType),
|
||||||
|
IncorrectDataLength(usize, usize),
|
||||||
Other(Box<str>),
|
Other(Box<str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,6 +33,14 @@ impl fmt::Display for PngError {
|
||||||
}
|
}
|
||||||
PngError::APNGNotSupported => f.write_str("APNG files are not (yet) supported"),
|
PngError::APNGNotSupported => f.write_str("APNG files are not (yet) supported"),
|
||||||
PngError::ChunkMissing(s) => write!(f, "Chunk {} missing or empty", s),
|
PngError::ChunkMissing(s) => write!(f, "Chunk {} missing or empty", s),
|
||||||
|
PngError::InvalidDepthForType(d, ref c) => {
|
||||||
|
write!(f, "Invalid bit depth {} for color type {}", d, c)
|
||||||
|
}
|
||||||
|
PngError::IncorrectDataLength(l1, l2) => write!(
|
||||||
|
f,
|
||||||
|
"Data length {} does not match the expected length {}",
|
||||||
|
l1, l2
|
||||||
|
),
|
||||||
PngError::Other(ref s) => f.write_str(s),
|
PngError::Other(ref s) => f.write_str(s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,15 @@
|
||||||
use crate::atomicmin::AtomicMin;
|
use crate::atomicmin::AtomicMin;
|
||||||
use crate::deflate;
|
use crate::deflate;
|
||||||
use crate::filters::RowFilter;
|
use crate::filters::RowFilter;
|
||||||
use crate::png::PngData;
|
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
#[cfg(not(feature = "parallel"))]
|
#[cfg(not(feature = "parallel"))]
|
||||||
use crate::rayon;
|
use crate::rayon;
|
||||||
use crate::Deadline;
|
use crate::Deadline;
|
||||||
|
use crate::PngError;
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
|
use log::trace;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
#[cfg(not(feature = "parallel"))]
|
#[cfg(not(feature = "parallel"))]
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
@ -20,7 +21,9 @@ use std::sync::atomic::Ordering::SeqCst;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct Candidate {
|
pub struct Candidate {
|
||||||
pub image: PngData,
|
pub image: Arc<PngImage>,
|
||||||
|
pub idat_data: Vec<u8>,
|
||||||
|
pub filtered: Vec<u8>,
|
||||||
pub filter: RowFilter,
|
pub filter: RowFilter,
|
||||||
pub is_reduction: bool,
|
pub is_reduction: bool,
|
||||||
// first wins tie-breaker
|
// first wins tie-breaker
|
||||||
|
|
@ -30,9 +33,9 @@ pub struct Candidate {
|
||||||
impl Candidate {
|
impl Candidate {
|
||||||
fn cmp_key(&self) -> impl Ord {
|
fn cmp_key(&self) -> impl Ord {
|
||||||
(
|
(
|
||||||
self.image.idat_data.len(),
|
self.idat_data.len() + self.image.key_chunks_size(),
|
||||||
self.image.raw.data.len(),
|
self.image.data.len(),
|
||||||
self.image.raw.ihdr.bit_depth,
|
self.image.ihdr.bit_depth,
|
||||||
self.filter,
|
self.filter,
|
||||||
self.nth,
|
self.nth,
|
||||||
)
|
)
|
||||||
|
|
@ -79,7 +82,7 @@ impl Evaluator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for all evaluations to finish and return smallest reduction
|
/// Wait for all evaluations to finish and return smallest reduction
|
||||||
/// Or `None` if all reductions were worse than baseline.
|
/// Or `None` if the queue is empty.
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
pub fn get_best_candidate(self) -> Option<Candidate> {
|
pub fn get_best_candidate(self) -> Option<Candidate> {
|
||||||
let (eval_send, eval_recv) = self.eval_channel;
|
let (eval_send, eval_recv) = self.eval_channel;
|
||||||
|
|
@ -131,16 +134,21 @@ impl Evaluator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let filtered = image.filter_image(filter, optimize_alpha);
|
let filtered = image.filter_image(filter, optimize_alpha);
|
||||||
if let Ok(idat_data) =
|
let idat_data = deflate::deflate(&filtered, compression, &best_candidate_size);
|
||||||
deflate::deflate(&filtered, compression, &best_candidate_size)
|
if let Ok(idat_data) = idat_data {
|
||||||
{
|
let size = idat_data.len() + image.key_chunks_size();
|
||||||
best_candidate_size.set_min(idat_data.len());
|
best_candidate_size.set_min(size);
|
||||||
|
trace!(
|
||||||
|
"Eval: {}-bit {:20} {:8} {} bytes",
|
||||||
|
image.ihdr.bit_depth,
|
||||||
|
image.ihdr.color_type,
|
||||||
|
filter,
|
||||||
|
size
|
||||||
|
);
|
||||||
let new = Candidate {
|
let new = Candidate {
|
||||||
image: PngData {
|
image: image.clone(),
|
||||||
idat_data,
|
idat_data,
|
||||||
filtered,
|
filtered,
|
||||||
raw: Arc::clone(&image),
|
|
||||||
},
|
|
||||||
filter,
|
filter,
|
||||||
is_reduction,
|
is_reduction,
|
||||||
nth,
|
nth,
|
||||||
|
|
@ -158,6 +166,14 @@ impl Evaluator {
|
||||||
best => *best = Some(new),
|
best => *best = Some(new),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if let Err(PngError::DeflatedDataTooLong(size)) = idat_data {
|
||||||
|
trace!(
|
||||||
|
"Eval: {}-bit {:20} {:8} >{} bytes",
|
||||||
|
image.ihdr.bit_depth,
|
||||||
|
image.ihdr.color_type,
|
||||||
|
filter,
|
||||||
|
size
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use std::{fmt::Display, mem::transmute};
|
use std::mem::transmute;
|
||||||
|
use std::{fmt, fmt::Display};
|
||||||
|
|
||||||
use crate::error::PngError;
|
use crate::error::PngError;
|
||||||
|
|
||||||
|
|
@ -31,11 +32,9 @@ impl TryFrom<u8> for RowFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for RowFilter {
|
impl Display for RowFilter {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
Display::fmt(
|
||||||
f,
|
match self {
|
||||||
"{:8}",
|
|
||||||
match *self {
|
|
||||||
Self::None => "None",
|
Self::None => "None",
|
||||||
Self::Sub => "Sub",
|
Self::Sub => "Sub",
|
||||||
Self::Up => "Up",
|
Self::Up => "Up",
|
||||||
|
|
@ -46,17 +45,19 @@ impl Display for RowFilter {
|
||||||
Self::Bigrams => "Bigrams",
|
Self::Bigrams => "Bigrams",
|
||||||
Self::BigEnt => "BigEnt",
|
Self::BigEnt => "BigEnt",
|
||||||
Self::Brute => "Brute",
|
Self::Brute => "Brute",
|
||||||
}
|
},
|
||||||
|
f,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RowFilter {
|
impl RowFilter {
|
||||||
pub const LAST: u8 = Self::Brute as u8;
|
pub const LAST: u8 = Self::Brute as u8;
|
||||||
pub const STANDARD: [Self; 5] = [Self::None, Self::Sub, Self::Up, Self::Average, Self::Paeth];
|
pub(crate) const STANDARD: [Self; 5] =
|
||||||
pub const SINGLE_LINE: [Self; 2] = [Self::None, Self::Sub];
|
[Self::None, Self::Sub, Self::Up, Self::Average, Self::Paeth];
|
||||||
|
pub(crate) const SINGLE_LINE: [Self; 2] = [Self::None, Self::Sub];
|
||||||
|
|
||||||
pub fn filter_line(
|
pub(crate) fn filter_line(
|
||||||
self,
|
self,
|
||||||
bpp: usize,
|
bpp: usize,
|
||||||
data: &mut [u8],
|
data: &mut [u8],
|
||||||
|
|
@ -176,7 +177,7 @@ impl RowFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unfilter_line(
|
pub(crate) fn unfilter_line(
|
||||||
self,
|
self,
|
||||||
bpp: usize,
|
bpp: usize,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
|
|
|
||||||
219
src/headers.rs
|
|
@ -1,13 +1,15 @@
|
||||||
use crate::colors::{BitDepth, ColorType};
|
use crate::colors::{BitDepth, ColorType};
|
||||||
use crate::deflate::crc32;
|
use crate::deflate::{crc32, inflate};
|
||||||
use crate::error::PngError;
|
use crate::error::PngError;
|
||||||
use crate::interlace::Interlacing;
|
use crate::interlace::Interlacing;
|
||||||
|
use crate::AtomicMin;
|
||||||
|
use crate::Deflaters;
|
||||||
use crate::PngResult;
|
use crate::PngResult;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use std::io;
|
use log::warn;
|
||||||
use std::io::{Cursor, Read};
|
use rgb::{RGB16, RGBA8};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
/// Headers from the IHDR chunk of the image
|
/// Headers from the IHDR chunk of the image
|
||||||
pub struct IhdrData {
|
pub struct IhdrData {
|
||||||
/// The width of the image in pixels
|
/// The width of the image in pixels
|
||||||
|
|
@ -18,10 +20,6 @@ pub struct IhdrData {
|
||||||
pub color_type: ColorType,
|
pub color_type: ColorType,
|
||||||
/// The bit depth of the image
|
/// 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,
|
|
||||||
/// The filter mode used for this image (currently only 0 is valid)
|
|
||||||
pub filter: u8,
|
|
||||||
/// The interlacing mode of the image
|
/// The interlacing mode of the image
|
||||||
pub interlaced: Interlacing,
|
pub interlaced: Interlacing,
|
||||||
}
|
}
|
||||||
|
|
@ -30,8 +28,8 @@ impl IhdrData {
|
||||||
/// Bits per pixel
|
/// Bits per pixel
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn bpp(&self) -> u8 {
|
pub fn bpp(&self) -> usize {
|
||||||
self.bit_depth.as_u8() * self.color_type.channels_per_pixel()
|
self.bit_depth as usize * self.color_type.channels_per_pixel() as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Byte length of IDAT that is correct for this IHDR
|
/// Byte length of IDAT that is correct for this IHDR
|
||||||
|
|
@ -41,8 +39,8 @@ impl IhdrData {
|
||||||
let h = self.height as usize;
|
let h = self.height as usize;
|
||||||
let bpp = self.bpp();
|
let bpp = self.bpp();
|
||||||
|
|
||||||
fn bitmap_size(bpp: u8, w: usize, h: usize) -> usize {
|
fn bitmap_size(bpp: usize, w: usize, h: usize) -> usize {
|
||||||
(((w / 8) * bpp as usize) + ((w & 7) * bpp as usize + 7) / 8) * h
|
((w * bpp + 7) / 8) * h
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.interlaced == Interlacing::None {
|
if self.interlaced == Interlacing::None {
|
||||||
|
|
@ -65,21 +63,42 @@ impl IhdrData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Chunk {
|
||||||
|
pub name: [u8; 4],
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
/// Options to use for performing operations on headers (such as stripping)
|
/// Options to use when stripping chunks
|
||||||
pub enum Headers {
|
pub enum StripChunks {
|
||||||
/// None
|
/// None
|
||||||
None,
|
None,
|
||||||
/// Remove specific chunks
|
/// Remove specific chunks
|
||||||
Strip(Vec<String>),
|
Strip(IndexSet<[u8; 4]>),
|
||||||
/// Headers that won't affect rendering (all but cICP, iCCP, sBIT, sRGB, pHYs)
|
/// Remove all chunks that won't affect rendering
|
||||||
Safe,
|
Safe,
|
||||||
/// Remove all non-critical chunks except these
|
/// Remove all non-critical chunks except these
|
||||||
Keep(IndexSet<String>),
|
Keep(IndexSet<[u8; 4]>),
|
||||||
/// All non-critical headers
|
/// All non-critical chunks
|
||||||
All,
|
All,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl StripChunks {
|
||||||
|
/// List of chunks that will be kept when using the `Safe` option
|
||||||
|
pub const KEEP_SAFE: [[u8; 4]; 4] = [*b"cICP", *b"iCCP", *b"sRGB", *b"pHYs"];
|
||||||
|
|
||||||
|
pub(crate) fn keep(&self, name: &[u8; 4]) -> bool {
|
||||||
|
match &self {
|
||||||
|
StripChunks::None => true,
|
||||||
|
StripChunks::Keep(names) => names.contains(name),
|
||||||
|
StripChunks::Strip(names) => !names.contains(name),
|
||||||
|
StripChunks::Safe => Self::KEEP_SAFE.contains(name),
|
||||||
|
StripChunks::All => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn file_header_is_valid(bytes: &[u8]) -> bool {
|
pub fn file_header_is_valid(bytes: &[u8]) -> bool {
|
||||||
let expected_header: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
|
let expected_header: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
|
||||||
|
|
@ -88,27 +107,26 @@ pub fn file_header_is_valid(bytes: &[u8]) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct RawHeader<'a> {
|
pub struct RawChunk<'a> {
|
||||||
pub name: [u8; 4],
|
pub name: [u8; 4],
|
||||||
pub data: &'a [u8],
|
pub data: &'a [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_next_header<'a>(
|
pub fn parse_next_chunk<'a>(
|
||||||
byte_data: &'a [u8],
|
byte_data: &'a [u8],
|
||||||
byte_offset: &mut usize,
|
byte_offset: &mut usize,
|
||||||
fix_errors: bool,
|
fix_errors: bool,
|
||||||
) -> PngResult<Option<RawHeader<'a>>> {
|
) -> PngResult<Option<RawChunk<'a>>> {
|
||||||
let mut rdr = Cursor::new(
|
let length = read_be_u32(
|
||||||
byte_data
|
byte_data
|
||||||
.get(*byte_offset..*byte_offset + 4)
|
.get(*byte_offset..*byte_offset + 4)
|
||||||
.ok_or(PngError::TruncatedData)?,
|
.ok_or(PngError::TruncatedData)?,
|
||||||
);
|
);
|
||||||
let length = read_be_u32(&mut rdr).unwrap();
|
|
||||||
*byte_offset += 4;
|
*byte_offset += 4;
|
||||||
|
|
||||||
let header_start = *byte_offset;
|
let chunk_start = *byte_offset;
|
||||||
let chunk_name = byte_data
|
let chunk_name = byte_data
|
||||||
.get(header_start..header_start + 4)
|
.get(chunk_start..chunk_start + 4)
|
||||||
.ok_or(PngError::TruncatedData)?;
|
.ok_or(PngError::TruncatedData)?;
|
||||||
if chunk_name == b"IEND" {
|
if chunk_name == b"IEND" {
|
||||||
// End of data
|
// End of data
|
||||||
|
|
@ -120,61 +138,150 @@ pub fn parse_next_header<'a>(
|
||||||
.get(*byte_offset..*byte_offset + length as usize)
|
.get(*byte_offset..*byte_offset + length as usize)
|
||||||
.ok_or(PngError::TruncatedData)?;
|
.ok_or(PngError::TruncatedData)?;
|
||||||
*byte_offset += length as usize;
|
*byte_offset += length as usize;
|
||||||
let mut rdr = Cursor::new(
|
let crc = read_be_u32(
|
||||||
byte_data
|
byte_data
|
||||||
.get(*byte_offset..*byte_offset + 4)
|
.get(*byte_offset..*byte_offset + 4)
|
||||||
.ok_or(PngError::TruncatedData)?,
|
.ok_or(PngError::TruncatedData)?,
|
||||||
);
|
);
|
||||||
let crc = read_be_u32(&mut rdr).unwrap();
|
|
||||||
*byte_offset += 4;
|
*byte_offset += 4;
|
||||||
|
|
||||||
let header_bytes = byte_data
|
let chunk_bytes = byte_data
|
||||||
.get(header_start..header_start + 4 + length as usize)
|
.get(chunk_start..chunk_start + 4 + length as usize)
|
||||||
.ok_or(PngError::TruncatedData)?;
|
.ok_or(PngError::TruncatedData)?;
|
||||||
if !fix_errors && crc32(header_bytes) != crc {
|
if !fix_errors && crc32(chunk_bytes) != crc {
|
||||||
return Err(PngError::new(&format!(
|
return Err(PngError::new(&format!(
|
||||||
"CRC Mismatch in {} header; May be recoverable by using --fix",
|
"CRC Mismatch in {} chunk; May be recoverable by using --fix",
|
||||||
String::from_utf8_lossy(chunk_name)
|
String::from_utf8_lossy(chunk_name)
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut name = [0_u8; 4];
|
let name: [u8; 4] = chunk_name.try_into().unwrap();
|
||||||
name.copy_from_slice(chunk_name);
|
Ok(Some(RawChunk { name, data }))
|
||||||
Ok(Some(RawHeader { name, data }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_ihdr_header(byte_data: &[u8]) -> PngResult<IhdrData> {
|
pub fn parse_ihdr_chunk(
|
||||||
|
byte_data: &[u8],
|
||||||
|
palette_data: Option<Vec<u8>>,
|
||||||
|
trns_data: Option<Vec<u8>>,
|
||||||
|
) -> PngResult<IhdrData> {
|
||||||
// This eliminates bounds checks for the rest of the function
|
// This eliminates bounds checks for the rest of the function
|
||||||
let interlaced = byte_data.get(12).copied().ok_or(PngError::TruncatedData)?;
|
let interlaced = byte_data.get(12).copied().ok_or(PngError::TruncatedData)?;
|
||||||
let mut rdr = Cursor::new(&byte_data[0..8]);
|
|
||||||
Ok(IhdrData {
|
Ok(IhdrData {
|
||||||
color_type: match byte_data[9] {
|
color_type: match byte_data[9] {
|
||||||
0 => ColorType::Grayscale,
|
0 => ColorType::Grayscale {
|
||||||
2 => ColorType::RGB,
|
transparent_shade: trns_data
|
||||||
3 => ColorType::Indexed,
|
.filter(|t| t.len() >= 2)
|
||||||
|
.map(|t| u16::from_be_bytes([t[0], t[1]])),
|
||||||
|
},
|
||||||
|
2 => ColorType::RGB {
|
||||||
|
transparent_color: trns_data.filter(|t| t.len() >= 6).map(|t| RGB16 {
|
||||||
|
r: u16::from_be_bytes([t[0], t[1]]),
|
||||||
|
g: u16::from_be_bytes([t[2], t[3]]),
|
||||||
|
b: u16::from_be_bytes([t[4], t[5]]),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
3 => ColorType::Indexed {
|
||||||
|
palette: palette_to_rgba(palette_data, trns_data).unwrap_or_default(),
|
||||||
|
},
|
||||||
4 => ColorType::GrayscaleAlpha,
|
4 => ColorType::GrayscaleAlpha,
|
||||||
6 => ColorType::RGBA,
|
6 => ColorType::RGBA,
|
||||||
_ => return Err(PngError::new("Unexpected color type in header")),
|
_ => return Err(PngError::new("Unexpected color type in header")),
|
||||||
},
|
},
|
||||||
bit_depth: match byte_data[8] {
|
bit_depth: byte_data[8].try_into()?,
|
||||||
1 => BitDepth::One,
|
width: read_be_u32(&byte_data[0..4]),
|
||||||
2 => BitDepth::Two,
|
height: read_be_u32(&byte_data[4..8]),
|
||||||
4 => BitDepth::Four,
|
|
||||||
8 => BitDepth::Eight,
|
|
||||||
16 => BitDepth::Sixteen,
|
|
||||||
_ => return Err(PngError::new("Unexpected bit depth in header")),
|
|
||||||
},
|
|
||||||
width: read_be_u32(&mut rdr).map_err(|_| PngError::TruncatedData)?,
|
|
||||||
height: read_be_u32(&mut rdr).map_err(|_| PngError::TruncatedData)?,
|
|
||||||
compression: byte_data[10],
|
|
||||||
filter: byte_data[11],
|
|
||||||
interlaced: interlaced.try_into()?,
|
interlaced: interlaced.try_into()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
/// Construct an RGBA palette from the raw palette and transparency data
|
||||||
fn read_be_u32<T: AsRef<[u8]>>(rdr: &mut Cursor<T>) -> Result<u32, io::Error> {
|
fn palette_to_rgba(
|
||||||
let mut int_buf = [0; 4];
|
palette_data: Option<Vec<u8>>,
|
||||||
rdr.read_exact(&mut int_buf)?;
|
trns_data: Option<Vec<u8>>,
|
||||||
Ok(u32::from_be_bytes(int_buf))
|
) -> Result<Vec<RGBA8>, PngError> {
|
||||||
|
let palette_data = palette_data.ok_or_else(|| PngError::new("no palette in indexed image"))?;
|
||||||
|
let mut palette: Vec<_> = palette_data
|
||||||
|
.chunks(3)
|
||||||
|
.map(|color| RGBA8::new(color[0], color[1], color[2], 255))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if let Some(trns_data) = trns_data {
|
||||||
|
for (color, trns) in palette.iter_mut().zip(trns_data) {
|
||||||
|
color.a = trns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(palette)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn read_be_u32(bytes: &[u8]) -> u32 {
|
||||||
|
u32::from_be_bytes(bytes.try_into().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract and decompress the ICC profile from an iCCP chunk
|
||||||
|
pub fn extract_icc(iccp: &Chunk) -> Option<Vec<u8>> {
|
||||||
|
// Skip (useless) profile name
|
||||||
|
let mut data = iccp.data.as_slice();
|
||||||
|
loop {
|
||||||
|
let (&n, rest) = data.split_first()?;
|
||||||
|
data = rest;
|
||||||
|
if n == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (&compression_method, compressed_data) = data.split_first()?;
|
||||||
|
if compression_method != 0 {
|
||||||
|
return None; // The profile is supposed to be compressed (method 0)
|
||||||
|
}
|
||||||
|
// The decompressed size is unknown so we have to guess the required buffer size
|
||||||
|
let max_size = compressed_data.len() * 2 + 1000;
|
||||||
|
match inflate(compressed_data, max_size) {
|
||||||
|
Ok(icc) => Some(icc),
|
||||||
|
Err(e) => {
|
||||||
|
// Log the error so we can know if the buffer size needs to be adjusted
|
||||||
|
warn!("Failed to decompress icc: {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct an iCCP chunk by compressing the ICC profile
|
||||||
|
pub fn construct_iccp(icc: &[u8], deflater: Deflaters) -> PngResult<Chunk> {
|
||||||
|
let mut compressed = deflater.deflate(icc, &AtomicMin::new(None))?;
|
||||||
|
let mut data = Vec::with_capacity(compressed.len() + 5);
|
||||||
|
data.extend(b"icc"); // Profile name - generally unused, can be anything
|
||||||
|
data.extend([0, 0]); // Null separator, zlib compression method
|
||||||
|
data.append(&mut compressed);
|
||||||
|
Ok(Chunk {
|
||||||
|
name: *b"iCCP",
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the profile is sRGB, extracts the rendering intent value from it
|
||||||
|
pub fn srgb_rendering_intent(icc_data: &[u8]) -> Option<u8> {
|
||||||
|
let rendering_intent = *icc_data.get(67)?;
|
||||||
|
|
||||||
|
// The known profiles are the same as in libpng's `png_sRGB_checks`.
|
||||||
|
// The Profile ID header of ICC has a fixed layout,
|
||||||
|
// and is supposed to contain MD5 of profile data at this offset
|
||||||
|
match icc_data.get(84..100)? {
|
||||||
|
b"\x29\xf8\x3d\xde\xaf\xf2\x55\xae\x78\x42\xfa\xe4\xca\x83\x39\x0d"
|
||||||
|
| b"\xc9\x5b\xd6\x37\xe9\x5d\x8a\x3b\x0d\xf3\x8f\x99\xc1\x32\x03\x89"
|
||||||
|
| b"\xfc\x66\x33\x78\x37\xe2\x88\x6b\xfd\x72\xe9\x83\x82\x28\xf1\xb8"
|
||||||
|
| b"\x34\x56\x2a\xbf\x99\x4c\xcd\x06\x6d\x2c\x57\x21\xd0\xd6\x8c\x5d" => {
|
||||||
|
Some(rendering_intent)
|
||||||
|
}
|
||||||
|
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" => {
|
||||||
|
// Known-bad profiles are identified by their CRC
|
||||||
|
match (crc32(icc_data), icc_data.len()) {
|
||||||
|
(0x5d51_29ce, 3024) | (0x182e_a552, 3144) | (0xf29e_526d, 3144) => {
|
||||||
|
Some(rendering_intent)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::fmt::Display;
|
use std::{fmt, fmt::Display};
|
||||||
|
|
||||||
use crate::headers::IhdrData;
|
use crate::headers::IhdrData;
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
|
|
@ -25,14 +25,13 @@ impl TryFrom<u8> for Interlacing {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Interlacing {
|
impl Display for Interlacing {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
Display::fmt(
|
||||||
f,
|
match self {
|
||||||
"{}",
|
|
||||||
match *self {
|
|
||||||
Self::None => "non-interlaced",
|
Self::None => "non-interlaced",
|
||||||
Self::Adam7 => "interlaced",
|
Self::Adam7 => "interlaced",
|
||||||
}
|
},
|
||||||
|
f,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -45,11 +44,11 @@ pub fn interlace_image(png: &PngImage) -> PngImage {
|
||||||
let bit_vec = line.data.view_bits::<Msb0>();
|
let bit_vec = line.data.view_bits::<Msb0>();
|
||||||
for (i, bit) in bit_vec.iter().by_vals().enumerate() {
|
for (i, bit) in bit_vec.iter().by_vals().enumerate() {
|
||||||
// Avoid moving padded 0's into new image
|
// Avoid moving padded 0's into new image
|
||||||
if i >= (png.ihdr.width * u32::from(bits_per_pixel)) as usize {
|
if i >= (png.ihdr.width as usize * bits_per_pixel) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Copy pixels into interlaced passes
|
// Copy pixels into interlaced passes
|
||||||
let pix_modulo = (i / bits_per_pixel as usize) % 8;
|
let pix_modulo = (i / bits_per_pixel) % 8;
|
||||||
match index % 8 {
|
match index % 8 {
|
||||||
0 => match pix_modulo {
|
0 => match pix_modulo {
|
||||||
0 => passes[0].push(bit),
|
0 => passes[0].push(bit),
|
||||||
|
|
@ -87,12 +86,10 @@ pub fn interlace_image(png: &PngImage) -> PngImage {
|
||||||
PngImage {
|
PngImage {
|
||||||
data: output,
|
data: output,
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
|
color_type: png.ihdr.color_type.clone(),
|
||||||
interlaced: Interlacing::Adam7,
|
interlaced: Interlacing::Adam7,
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers: png.aux_headers.clone(),
|
|
||||||
palette: png.palette.clone(),
|
|
||||||
transparency_pixel: png.transparency_pixel.clone(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,19 +100,17 @@ pub fn deinterlace_image(png: &PngImage) -> PngImage {
|
||||||
_ => deinterlace_bits(png),
|
_ => deinterlace_bits(png),
|
||||||
},
|
},
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
|
color_type: png.ihdr.color_type.clone(),
|
||||||
interlaced: Interlacing::None,
|
interlaced: Interlacing::None,
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers: png.aux_headers.clone(),
|
|
||||||
palette: png.palette.clone(),
|
|
||||||
transparency_pixel: png.transparency_pixel.clone(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deinterlace by bits, for images with less than 8bpp
|
/// Deinterlace by bits, for images with less than 8bpp
|
||||||
fn deinterlace_bits(png: &PngImage) -> Vec<u8> {
|
fn deinterlace_bits(png: &PngImage) -> Vec<u8> {
|
||||||
let bits_per_pixel = png.ihdr.bpp();
|
let bits_per_pixel = png.ihdr.bpp();
|
||||||
let bits_per_line = bits_per_pixel as usize * png.ihdr.width as usize;
|
let bits_per_line = bits_per_pixel * png.ihdr.width as usize;
|
||||||
// Initialize each output line with blank data
|
// Initialize each output line with blank data
|
||||||
let mut lines: Vec<BitVec<u8, Msb0>> =
|
let mut lines: Vec<BitVec<u8, Msb0>> =
|
||||||
vec![bitvec![u8, Msb0; 0; bits_per_line]; png.ihdr.height as usize];
|
vec![bitvec![u8, Msb0; 0; bits_per_line]; png.ihdr.height as usize];
|
||||||
|
|
@ -128,22 +123,22 @@ fn deinterlace_bits(png: &PngImage) -> Vec<u8> {
|
||||||
+ u32::from(pass_constants.x_step)
|
+ u32::from(pass_constants.x_step)
|
||||||
- 1)
|
- 1)
|
||||||
/ u32::from(pass_constants.x_step)) as usize
|
/ u32::from(pass_constants.x_step)) as usize
|
||||||
* bits_per_pixel as usize;
|
* bits_per_pixel;
|
||||||
for (i, bit) in bit_vec.iter().by_vals().enumerate() {
|
for (i, bit) in bit_vec.iter().by_vals().enumerate() {
|
||||||
// Avoid moving padded 0's into new image
|
// Avoid moving padded 0's into new image
|
||||||
if i >= bits_in_line {
|
if i >= bits_in_line {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let current_x: usize = pass_constants.x_shift as usize
|
let current_x: usize = pass_constants.x_shift as usize
|
||||||
+ (i / bits_per_pixel as usize) * pass_constants.x_step as usize;
|
+ (i / bits_per_pixel) * pass_constants.x_step as usize;
|
||||||
// Copy this bit into the output line
|
// Copy this bit into the output line
|
||||||
let index = (i % bits_per_pixel as usize) + current_x * bits_per_pixel as usize;
|
let index = (i % bits_per_pixel) + current_x * bits_per_pixel;
|
||||||
lines[current_y].set(index, bit);
|
lines[current_y].set(index, bit);
|
||||||
}
|
}
|
||||||
// Calculate the next line and move to next pass if necessary
|
// Calculate the next line and move to next pass if necessary
|
||||||
current_y += pass_constants.y_step as usize;
|
current_y += pass_constants.y_step as usize;
|
||||||
if current_y >= png.ihdr.height as usize {
|
if current_y >= png.ihdr.height as usize {
|
||||||
if !increment_pass(&mut current_pass, png.ihdr) {
|
if !increment_pass(&mut current_pass, &png.ihdr) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
pass_constants = interlaced_constants(current_pass);
|
pass_constants = interlaced_constants(current_pass);
|
||||||
|
|
@ -163,7 +158,7 @@ fn deinterlace_bits(png: &PngImage) -> Vec<u8> {
|
||||||
/// Deinterlace by bytes, for images with at least 8bpp
|
/// Deinterlace by bytes, for images with at least 8bpp
|
||||||
fn deinterlace_bytes(png: &PngImage) -> Vec<u8> {
|
fn deinterlace_bytes(png: &PngImage) -> Vec<u8> {
|
||||||
let bytes_per_pixel = png.ihdr.bpp() / 8;
|
let bytes_per_pixel = png.ihdr.bpp() / 8;
|
||||||
let bytes_per_line = bytes_per_pixel as usize * png.ihdr.width as usize;
|
let bytes_per_line = bytes_per_pixel * png.ihdr.width as usize;
|
||||||
// Initialize each output line with some blank data
|
// Initialize each output line with some blank data
|
||||||
let mut lines: Vec<Vec<u8>> = vec![vec![0; bytes_per_line]; png.ihdr.height as usize];
|
let mut lines: Vec<Vec<u8>> = vec![vec![0; bytes_per_line]; png.ihdr.height as usize];
|
||||||
let mut current_pass = 1;
|
let mut current_pass = 1;
|
||||||
|
|
@ -172,15 +167,15 @@ fn deinterlace_bytes(png: &PngImage) -> Vec<u8> {
|
||||||
for line in png.scan_lines(false) {
|
for line in png.scan_lines(false) {
|
||||||
for (i, byte) in line.data.iter().enumerate() {
|
for (i, byte) in line.data.iter().enumerate() {
|
||||||
let current_x: usize = pass_constants.x_shift as usize
|
let current_x: usize = pass_constants.x_shift as usize
|
||||||
+ (i / bytes_per_pixel as usize) * pass_constants.x_step as usize;
|
+ (i / bytes_per_pixel) * pass_constants.x_step as usize;
|
||||||
// Copy this byte into the output line
|
// Copy this byte into the output line
|
||||||
let index = (i % bytes_per_pixel as usize) + current_x * bytes_per_pixel as usize;
|
let index = (i % bytes_per_pixel) + current_x * bytes_per_pixel;
|
||||||
lines[current_y][index] = *byte;
|
lines[current_y][index] = *byte;
|
||||||
}
|
}
|
||||||
// Calculate the next line and move to next pass if necessary
|
// Calculate the next line and move to next pass if necessary
|
||||||
current_y += pass_constants.y_step as usize;
|
current_y += pass_constants.y_step as usize;
|
||||||
if current_y >= png.ihdr.height as usize {
|
if current_y >= png.ihdr.height as usize {
|
||||||
if !increment_pass(&mut current_pass, png.ihdr) {
|
if !increment_pass(&mut current_pass, &png.ihdr) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
pass_constants = interlaced_constants(current_pass);
|
pass_constants = interlaced_constants(current_pass);
|
||||||
|
|
@ -190,7 +185,7 @@ fn deinterlace_bytes(png: &PngImage) -> Vec<u8> {
|
||||||
lines.concat()
|
lines.concat()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn increment_pass(current_pass: &mut u8, ihdr: IhdrData) -> bool {
|
fn increment_pass(current_pass: &mut u8, ihdr: &IhdrData) -> bool {
|
||||||
if *current_pass == 7 {
|
if *current_pass == 7 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
727
src/lib.rs
|
|
@ -25,29 +25,29 @@ extern crate rayon;
|
||||||
mod rayon;
|
mod rayon;
|
||||||
|
|
||||||
use crate::atomicmin::AtomicMin;
|
use crate::atomicmin::AtomicMin;
|
||||||
use crate::colors::BitDepth;
|
|
||||||
use crate::deflate::{crc32, inflate};
|
|
||||||
use crate::evaluate::Evaluator;
|
use crate::evaluate::Evaluator;
|
||||||
|
use crate::headers::*;
|
||||||
use crate::png::PngData;
|
use crate::png::PngData;
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
use crate::reduction::*;
|
use crate::reduction::*;
|
||||||
use image::{DynamicImage, GenericImageView, ImageFormat, Pixel};
|
use log::{debug, info, trace, warn};
|
||||||
use log::{debug, error, info, warn};
|
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fs::{copy, File, Metadata};
|
use std::fs::{copy, File, Metadata};
|
||||||
use std::io::{stdin, stdout, BufWriter, Cursor, Read, Write};
|
use std::io::{stdin, stdout, BufWriter, Read, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
pub use crate::colors::{BitDepth, ColorType};
|
||||||
pub use crate::deflate::Deflaters;
|
pub use crate::deflate::Deflaters;
|
||||||
pub use crate::error::PngError;
|
pub use crate::error::PngError;
|
||||||
pub use crate::filters::RowFilter;
|
pub use crate::filters::RowFilter;
|
||||||
pub use crate::headers::Headers;
|
pub use crate::headers::StripChunks;
|
||||||
pub use crate::interlace::Interlacing;
|
pub use crate::interlace::Interlacing;
|
||||||
pub use indexmap::{indexset, IndexMap, IndexSet};
|
pub use indexmap::{indexset, IndexSet};
|
||||||
|
pub use rgb::{RGB16, RGBA8};
|
||||||
|
|
||||||
mod atomicmin;
|
mod atomicmin;
|
||||||
mod colors;
|
mod colors;
|
||||||
|
|
@ -59,16 +59,18 @@ mod headers;
|
||||||
mod interlace;
|
mod interlace;
|
||||||
mod png;
|
mod png;
|
||||||
mod reduction;
|
mod reduction;
|
||||||
|
#[cfg(feature = "sanity-checks")]
|
||||||
|
mod sanity_checks;
|
||||||
|
|
||||||
/// Private to oxipng; don't use outside tests and benches
|
/// Private to oxipng; don't use outside tests and benches
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod internal_tests {
|
pub mod internal_tests {
|
||||||
pub use crate::atomicmin::*;
|
pub use crate::atomicmin::*;
|
||||||
pub use crate::colors::*;
|
|
||||||
pub use crate::deflate::*;
|
pub use crate::deflate::*;
|
||||||
pub use crate::headers::*;
|
|
||||||
pub use crate::png::*;
|
pub use crate::png::*;
|
||||||
pub use crate::reduction::*;
|
pub use crate::reduction::*;
|
||||||
|
#[cfg(feature = "sanity-checks")]
|
||||||
|
pub use crate::sanity_checks::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -157,7 +159,7 @@ pub struct Options {
|
||||||
///
|
///
|
||||||
/// `Some(x)` will change the file to interlacing mode `x`.
|
/// `Some(x)` will change the file to interlacing mode `x`.
|
||||||
///
|
///
|
||||||
/// Default: `None`
|
/// Default: `Some(None)`
|
||||||
pub interlace: Option<Interlacing>,
|
pub interlace: Option<Interlacing>,
|
||||||
/// Whether to allow transparent pixels to be altered to improve compression.
|
/// Whether to allow transparent pixels to be altered to improve compression.
|
||||||
pub optimize_alpha: bool,
|
pub optimize_alpha: bool,
|
||||||
|
|
@ -184,10 +186,14 @@ pub struct Options {
|
||||||
///
|
///
|
||||||
/// Default: `true`
|
/// Default: `true`
|
||||||
pub idat_recoding: bool,
|
pub idat_recoding: bool,
|
||||||
/// Which headers to strip from the PNG file, if any
|
/// Whether to forcibly reduce 16-bit to 8-bit by scaling
|
||||||
|
///
|
||||||
|
/// Default: `false`
|
||||||
|
pub scale_16: bool,
|
||||||
|
/// Which chunks to strip from the PNG file, if any
|
||||||
///
|
///
|
||||||
/// Default: `None`
|
/// Default: `None`
|
||||||
pub strip: Headers,
|
pub strip: StripChunks,
|
||||||
/// Which DEFLATE algorithm to use
|
/// Which DEFLATE algorithm to use
|
||||||
///
|
///
|
||||||
/// Default: `Libdeflater`
|
/// Default: `Libdeflater`
|
||||||
|
|
@ -294,14 +300,15 @@ impl Default for Options {
|
||||||
force: false,
|
force: false,
|
||||||
preserve_attrs: false,
|
preserve_attrs: false,
|
||||||
filter: indexset! {RowFilter::None, RowFilter::Sub, RowFilter::Entropy, RowFilter::Bigrams},
|
filter: indexset! {RowFilter::None, RowFilter::Sub, RowFilter::Entropy, RowFilter::Bigrams},
|
||||||
interlace: None,
|
interlace: Some(Interlacing::None),
|
||||||
optimize_alpha: false,
|
optimize_alpha: false,
|
||||||
bit_depth_reduction: true,
|
bit_depth_reduction: true,
|
||||||
color_type_reduction: true,
|
color_type_reduction: true,
|
||||||
palette_reduction: true,
|
palette_reduction: true,
|
||||||
grayscale_reduction: true,
|
grayscale_reduction: true,
|
||||||
idat_recoding: true,
|
idat_recoding: true,
|
||||||
strip: Headers::None,
|
scale_16: false,
|
||||||
|
strip: StripChunks::None,
|
||||||
deflate: Deflaters::Libdeflater { compression: 11 },
|
deflate: Deflaters::Libdeflater { compression: 11 },
|
||||||
fast_evaluation: true,
|
fast_evaluation: true,
|
||||||
timeout: None,
|
timeout: None,
|
||||||
|
|
@ -309,6 +316,94 @@ impl Default for Options {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// A raw image definition which can be used to create an optimized png
|
||||||
|
pub struct RawImage {
|
||||||
|
png: Arc<PngImage>,
|
||||||
|
aux_chunks: Vec<Chunk>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawImage {
|
||||||
|
/// Construct a new raw image definition
|
||||||
|
///
|
||||||
|
/// * `width` - The width of the image in pixels
|
||||||
|
/// * `height` - The height of the image in pixels
|
||||||
|
/// * `color_type` - The color type of the image
|
||||||
|
/// * `bit_depth` - The bit depth of the image
|
||||||
|
/// * `data` - The raw pixel data of the image
|
||||||
|
pub fn new(
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
color_type: ColorType,
|
||||||
|
bit_depth: BitDepth,
|
||||||
|
data: Vec<u8>,
|
||||||
|
) -> Result<Self, PngError> {
|
||||||
|
// Validate bit depth
|
||||||
|
let valid_depth = match color_type {
|
||||||
|
ColorType::Grayscale { .. } => true,
|
||||||
|
ColorType::Indexed { .. } => (bit_depth as u8) <= 8,
|
||||||
|
_ => (bit_depth as u8) >= 8,
|
||||||
|
};
|
||||||
|
if !valid_depth {
|
||||||
|
return Err(PngError::InvalidDepthForType(bit_depth, color_type));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate data length
|
||||||
|
let bpp = bit_depth as usize * color_type.channels_per_pixel() as usize;
|
||||||
|
let row_bytes = (bpp * width as usize + 7) / 8;
|
||||||
|
let expected_len = row_bytes * height as usize;
|
||||||
|
if data.len() != expected_len {
|
||||||
|
return Err(PngError::IncorrectDataLength(data.len(), expected_len));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
png: Arc::new(PngImage {
|
||||||
|
ihdr: IhdrData {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
color_type,
|
||||||
|
bit_depth,
|
||||||
|
interlaced: Interlacing::None,
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
}),
|
||||||
|
aux_chunks: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a png chunk, such as "iTXt", to be included in the output
|
||||||
|
pub fn add_png_chunk(&mut self, name: [u8; 4], data: Vec<u8>) {
|
||||||
|
self.aux_chunks.push(Chunk { name, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an ICC profile for the image
|
||||||
|
pub fn add_icc_profile(&mut self, data: &[u8]) {
|
||||||
|
// Compress with fastest compression level - will be recompressed during optimization
|
||||||
|
let deflater = Deflaters::Libdeflater { compression: 1 };
|
||||||
|
if let Ok(iccp) = construct_iccp(data, deflater) {
|
||||||
|
self.aux_chunks.push(iccp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an optimized png from the raw image data using the options provided
|
||||||
|
pub fn create_optimized_png(&self, opts: &Options) -> PngResult<Vec<u8>> {
|
||||||
|
let deadline = Arc::new(Deadline::new(opts.timeout));
|
||||||
|
let mut png = optimize_raw(self.png.clone(), opts, deadline, None)
|
||||||
|
.ok_or_else(|| PngError::new("Failed to optimize input data"))?;
|
||||||
|
|
||||||
|
// Process aux chunks
|
||||||
|
png.aux_chunks = self
|
||||||
|
.aux_chunks
|
||||||
|
.iter()
|
||||||
|
.filter(|c| opts.strip.keep(&c.name))
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
postprocess_chunks(&mut png, opts, &self.png.ihdr);
|
||||||
|
|
||||||
|
Ok(png.output())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Perform optimization on the input file using the options provided
|
/// Perform optimization on the input file using the options provided
|
||||||
pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<()> {
|
pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<()> {
|
||||||
// Read in the file and try to decode as PNG.
|
// Read in the file and try to decode as PNG.
|
||||||
|
|
@ -331,7 +426,7 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.map(Some)?;
|
.map(Some)?;
|
||||||
debug!("preserving metadata: {:?}", opt_metadata_preserved);
|
trace!("preserving metadata: {:?}", opt_metadata_preserved);
|
||||||
} else {
|
} else {
|
||||||
opt_metadata_preserved = None;
|
opt_metadata_preserved = None;
|
||||||
}
|
}
|
||||||
|
|
@ -347,7 +442,7 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut png = PngData::from_slice(&in_data, opts.fix_errors)?;
|
let mut png = PngData::from_slice(&in_data, opts)?;
|
||||||
|
|
||||||
if opts.check {
|
if opts.check {
|
||||||
info!("Running in check mode, not optimizing");
|
info!("Running in check mode, not optimizing");
|
||||||
|
|
@ -435,7 +530,7 @@ pub fn optimize_from_memory(data: &[u8], opts: &Options) -> PngResult<Vec<u8>> {
|
||||||
let deadline = Arc::new(Deadline::new(opts.timeout));
|
let deadline = Arc::new(Deadline::new(opts.timeout));
|
||||||
|
|
||||||
let original_size = data.len();
|
let original_size = data.len();
|
||||||
let mut png = PngData::from_slice(data, opts.fix_errors)?;
|
let mut png = PngData::from_slice(data, opts)?;
|
||||||
|
|
||||||
// Run the optimizer on the decoded PNG.
|
// Run the optimizer on the decoded PNG.
|
||||||
let optimized_output = optimize_png(&mut png, data, opts, deadline)?;
|
let optimized_output = optimize_png(&mut png, data, opts, deadline)?;
|
||||||
|
|
@ -448,13 +543,7 @@ pub fn optimize_from_memory(data: &[u8], opts: &Options) -> PngResult<Vec<u8>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
|
type TrialResult = (RowFilter, Vec<u8>);
|
||||||
/// Defines options to be used for a single compression trial
|
|
||||||
struct TrialOptions {
|
|
||||||
pub filter: RowFilter,
|
|
||||||
pub compression: u8,
|
|
||||||
}
|
|
||||||
type TrialWithData = (TrialOptions, Vec<u8>);
|
|
||||||
|
|
||||||
/// Perform optimization on the input PNG object using the options provided
|
/// Perform optimization on the input PNG object using the options provided
|
||||||
fn optimize_png(
|
fn optimize_png(
|
||||||
|
|
@ -466,167 +555,26 @@ fn optimize_png(
|
||||||
// Print png info
|
// Print png info
|
||||||
let file_original_size = original_data.len();
|
let file_original_size = original_data.len();
|
||||||
let idat_original_size = png.idat_data.len();
|
let idat_original_size = png.idat_data.len();
|
||||||
|
let raw = png.raw.clone();
|
||||||
debug!(
|
debug!(
|
||||||
" {}x{} pixels, PNG format",
|
" {}x{} pixels, PNG format",
|
||||||
png.raw.ihdr.width, png.raw.ihdr.height
|
raw.ihdr.width, raw.ihdr.height
|
||||||
);
|
);
|
||||||
report_format(" ", &png.raw);
|
report_format(" ", &raw);
|
||||||
debug!(" IDAT size = {} bytes", idat_original_size);
|
debug!(" IDAT size = {} bytes", idat_original_size);
|
||||||
debug!(" File size = {} bytes", file_original_size);
|
debug!(" File size = {} bytes", file_original_size);
|
||||||
|
|
||||||
// Do this first so that reductions can ignore certain chunks such as bKGD
|
let max_size = if opts.force {
|
||||||
perform_strip(png, opts);
|
|
||||||
let stripped_png = png.clone();
|
|
||||||
|
|
||||||
// Interlacing is not part of the evaluator trials but must be done first to evaluate the rest correctly
|
|
||||||
let mut reduction_occurred = false;
|
|
||||||
if let Some(interlacing) = opts.interlace {
|
|
||||||
if let Some(reduced) = png.raw.change_interlacing(interlacing) {
|
|
||||||
png.raw = Arc::new(reduced);
|
|
||||||
reduction_occurred = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If alpha optimization is enabled, perform a black alpha reduction before evaluating reductions
|
|
||||||
// This can allow reductions from alpha to indexed which may not have been possible otherwise
|
|
||||||
if opts.optimize_alpha {
|
|
||||||
if let Some(reduced) = cleaned_alpha_channel(&png.raw) {
|
|
||||||
png.raw = Arc::new(reduced);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must use normal (lazy) compression, as faster ones (greedy) are not representative
|
|
||||||
let eval_compression = 5;
|
|
||||||
// None and Bigrams work well together, especially for alpha reductions
|
|
||||||
let eval_filters = indexset! {RowFilter::None, RowFilter::Bigrams};
|
|
||||||
// This will collect all versions of images and pick one that compresses best
|
|
||||||
let eval = Evaluator::new(
|
|
||||||
deadline.clone(),
|
|
||||||
eval_filters.clone(),
|
|
||||||
eval_compression,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
perform_reductions(png.raw.clone(), opts, &deadline, &eval);
|
|
||||||
let mut eval_filter = if let Some(result) = eval.get_best_candidate() {
|
|
||||||
*png = result.image;
|
|
||||||
if result.is_reduction {
|
|
||||||
reduction_occurred = true;
|
|
||||||
}
|
|
||||||
Some(result.filter)
|
|
||||||
} else {
|
|
||||||
None
|
None
|
||||||
|
} else {
|
||||||
|
Some(png.estimated_output_size())
|
||||||
};
|
};
|
||||||
|
if let Some(new_png) = optimize_raw(raw.clone(), opts, deadline, max_size) {
|
||||||
if reduction_occurred {
|
png.raw = new_png.raw;
|
||||||
report_format("Reducing image to ", &png.raw);
|
png.idat_data = new_png.idat_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.idat_recoding || reduction_occurred {
|
postprocess_chunks(png, opts, &raw.ihdr);
|
||||||
let mut filters = opts.filter.clone();
|
|
||||||
let fast_eval = opts.fast_evaluation && (filters.len() > 1 || eval_filter.is_some());
|
|
||||||
let best: Option<TrialWithData> = if fast_eval {
|
|
||||||
// Perform a fast evaluation of selected filters followed by a single main compression trial
|
|
||||||
|
|
||||||
if eval_filter.is_some() {
|
|
||||||
// Some filters have already been evaluated, we don't need to try them again
|
|
||||||
filters = filters.difference(&eval_filters).cloned().collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
if !filters.is_empty() {
|
|
||||||
debug!("Evaluating: {} filters", filters.len());
|
|
||||||
let eval = Evaluator::new(deadline, filters, eval_compression, opts.optimize_alpha);
|
|
||||||
if eval_filter.is_some() {
|
|
||||||
eval.set_best_size(png.idat_data.len());
|
|
||||||
}
|
|
||||||
eval.try_image(png.raw.clone());
|
|
||||||
if let Some(result) = eval.get_best_candidate() {
|
|
||||||
*png = result.image;
|
|
||||||
eval_filter = Some(result.filter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let trial = TrialOptions {
|
|
||||||
filter: eval_filter.unwrap(),
|
|
||||||
compression: match opts.deflate {
|
|
||||||
Deflaters::Libdeflater { compression } => compression,
|
|
||||||
_ => 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if trial.compression > 0 && trial.compression <= eval_compression {
|
|
||||||
// No further compression required
|
|
||||||
if png.idat_data.len() < idat_original_size || opts.force {
|
|
||||||
Some((trial, png.idat_data.clone()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug!("Trying: {}", trial.filter);
|
|
||||||
let original_len = idat_original_size;
|
|
||||||
let best_size = AtomicMin::new(if opts.force { None } else { Some(original_len) });
|
|
||||||
perform_trial(&png.filtered, opts, trial, &best_size)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Perform full compression trials of selected filters and determine the best
|
|
||||||
|
|
||||||
if filters.is_empty() {
|
|
||||||
// Pick a filter automatically
|
|
||||||
if png.raw.ihdr.bit_depth.as_u8() >= 8 {
|
|
||||||
// Bigrams is the best all-rounder when there's at least one byte per pixel
|
|
||||||
filters.insert(RowFilter::Bigrams);
|
|
||||||
} else {
|
|
||||||
// Otherwise delta filters generally don't work well, so just stick with None
|
|
||||||
filters.insert(RowFilter::None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut results: Vec<TrialOptions> = Vec::with_capacity(filters.len());
|
|
||||||
|
|
||||||
for f in &filters {
|
|
||||||
results.push(TrialOptions {
|
|
||||||
filter: *f,
|
|
||||||
compression: match opts.deflate {
|
|
||||||
Deflaters::Libdeflater { compression } => compression,
|
|
||||||
_ => 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("Trying: {} filters", results.len());
|
|
||||||
|
|
||||||
let original_len = idat_original_size;
|
|
||||||
let best_size = AtomicMin::new(if opts.force { None } else { Some(original_len) });
|
|
||||||
let results_iter = results.into_par_iter().with_max_len(1);
|
|
||||||
let best = results_iter.filter_map(|trial| {
|
|
||||||
if deadline.passed() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let filtered = &png.raw.filter_image(trial.filter, opts.optimize_alpha);
|
|
||||||
perform_trial(filtered, opts, trial, &best_size)
|
|
||||||
});
|
|
||||||
best.reduce_with(|i, j| {
|
|
||||||
if i.1.len() < j.1.len() || (i.1.len() == j.1.len() && i.0 < j.0) {
|
|
||||||
i
|
|
||||||
} else {
|
|
||||||
j
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some((opts, idat_data)) = best {
|
|
||||||
png.idat_data = idat_data;
|
|
||||||
debug!("Found better combination:");
|
|
||||||
debug!(
|
|
||||||
" zc = {} f = {} {} bytes",
|
|
||||||
opts.compression,
|
|
||||||
opts.filter,
|
|
||||||
png.idat_data.len()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
*png = stripped_png;
|
|
||||||
}
|
|
||||||
} else if png.idat_data.len() >= idat_original_size {
|
|
||||||
*png = stripped_png;
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = png.output();
|
let output = png.output();
|
||||||
|
|
||||||
|
|
@ -659,103 +607,180 @@ fn optimize_png(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_assert!(validate_output(&output, original_data));
|
#[cfg(feature = "sanity-checks")]
|
||||||
|
assert!(sanity_checks::validate_output(&output, original_data));
|
||||||
|
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_reductions(
|
/// Perform optimization on the input image data using the options provided
|
||||||
|
fn optimize_raw(
|
||||||
mut png: Arc<PngImage>,
|
mut png: Arc<PngImage>,
|
||||||
opts: &Options,
|
opts: &Options,
|
||||||
deadline: &Deadline,
|
deadline: Arc<Deadline>,
|
||||||
eval: &Evaluator,
|
max_size: Option<usize>,
|
||||||
) {
|
) -> Option<PngData> {
|
||||||
// The eval baseline will be set from the original png only if we attempt any reductions
|
// Must use normal (lazy) compression, as faster ones (greedy) are not representative
|
||||||
let baseline = png.clone();
|
let eval_compression = 5;
|
||||||
let mut reduction_occurred = false;
|
// None and Bigrams work well together, especially for alpha reductions
|
||||||
|
let eval_filters = indexset! {RowFilter::None, RowFilter::Bigrams};
|
||||||
if opts.palette_reduction {
|
// This will collect all versions of images and pick one that compresses best
|
||||||
if let Some(reduced) = reduced_palette(&png, opts.optimize_alpha) {
|
let eval = Evaluator::new(
|
||||||
png = Arc::new(reduced);
|
deadline.clone(),
|
||||||
eval.try_image(png.clone());
|
eval_filters.clone(),
|
||||||
|
eval_compression,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
let (baseline, mut reduction_occurred) =
|
||||||
|
perform_reductions(png.clone(), opts, &deadline, &eval);
|
||||||
|
png = baseline;
|
||||||
|
let mut eval_result = eval.get_best_candidate();
|
||||||
|
if let Some(ref result) = eval_result {
|
||||||
|
if result.is_reduction {
|
||||||
|
png = result.image.clone();
|
||||||
reduction_occurred = true;
|
reduction_occurred = true;
|
||||||
}
|
}
|
||||||
if deadline.passed() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.bit_depth_reduction {
|
|
||||||
if let Some(reduced) = reduce_bit_depth(&png, 1) {
|
|
||||||
let previous = png.clone();
|
|
||||||
let bits = reduced.ihdr.bit_depth;
|
|
||||||
png = Arc::new(reduced);
|
|
||||||
eval.try_image(png.clone());
|
|
||||||
if (bits == BitDepth::One || bits == BitDepth::Two)
|
|
||||||
&& previous.ihdr.bit_depth != BitDepth::Four
|
|
||||||
{
|
|
||||||
// Also try 16-color mode for all lower bits images, since that may compress better
|
|
||||||
if let Some(reduced) = reduce_bit_depth(&previous, 4) {
|
|
||||||
eval.try_image(Arc::new(reduced));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reduction_occurred = true;
|
|
||||||
}
|
|
||||||
if deadline.passed() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.color_type_reduction {
|
|
||||||
if let Some(reduced) =
|
|
||||||
reduce_color_type(&png, opts.grayscale_reduction, opts.optimize_alpha)
|
|
||||||
{
|
|
||||||
png = Arc::new(reduced);
|
|
||||||
eval.try_image(png.clone());
|
|
||||||
reduction_occurred = true;
|
|
||||||
}
|
|
||||||
if deadline.passed() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if reduction_occurred {
|
if reduction_occurred {
|
||||||
eval.set_baseline(baseline);
|
report_format("Reducing image to ", &png);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.idat_recoding || reduction_occurred {
|
||||||
|
let mut filters = opts.filter.clone();
|
||||||
|
let fast_eval = opts.fast_evaluation && (filters.len() > 1 || eval_result.is_some());
|
||||||
|
let best: Option<TrialResult> = if fast_eval {
|
||||||
|
// Perform a fast evaluation of selected filters followed by a single main compression trial
|
||||||
|
|
||||||
|
if eval_result.is_some() {
|
||||||
|
// Some filters have already been evaluated, we don't need to try them again
|
||||||
|
filters = filters.difference(&eval_filters).cloned().collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filters.is_empty() {
|
||||||
|
trace!("Evaluating: {} filters", filters.len());
|
||||||
|
let eval = Evaluator::new(deadline, filters, eval_compression, opts.optimize_alpha);
|
||||||
|
if let Some(ref result) = eval_result {
|
||||||
|
eval.set_best_size(result.idat_data.len());
|
||||||
|
}
|
||||||
|
eval.try_image(png.clone());
|
||||||
|
if let Some(result) = eval.get_best_candidate() {
|
||||||
|
eval_result = Some(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We should have a result here - fail if not (e.g. deadline passed)
|
||||||
|
let result = eval_result?;
|
||||||
|
|
||||||
|
match opts.deflate {
|
||||||
|
Deflaters::Libdeflater { compression } if compression <= eval_compression => {
|
||||||
|
// No further compression required
|
||||||
|
Some((result.filter, result.idat_data))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug!("Trying: {}", result.filter);
|
||||||
|
let best_size = AtomicMin::new(max_size);
|
||||||
|
perform_trial(&result.filtered, opts, result.filter, &best_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Perform full compression trials of selected filters and determine the best
|
||||||
|
|
||||||
|
if filters.is_empty() {
|
||||||
|
// Pick a filter automatically
|
||||||
|
if png.ihdr.bit_depth as u8 >= 8 {
|
||||||
|
// Bigrams is the best all-rounder when there's at least one byte per pixel
|
||||||
|
filters.insert(RowFilter::Bigrams);
|
||||||
|
} else {
|
||||||
|
// Otherwise delta filters generally don't work well, so just stick with None
|
||||||
|
filters.insert(RowFilter::None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Trying: {} filters", filters.len());
|
||||||
|
|
||||||
|
let best_size = AtomicMin::new(max_size);
|
||||||
|
let results_iter = filters.into_par_iter().with_max_len(1);
|
||||||
|
let best = results_iter.filter_map(|filter| {
|
||||||
|
if deadline.passed() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let filtered = &png.filter_image(filter, opts.optimize_alpha);
|
||||||
|
perform_trial(filtered, opts, filter, &best_size)
|
||||||
|
});
|
||||||
|
best.reduce_with(|i, j| {
|
||||||
|
if i.1.len() < j.1.len() || (i.1.len() == j.1.len() && i.0 < j.0) {
|
||||||
|
i
|
||||||
|
} else {
|
||||||
|
j
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some((filter, idat_data)) = best {
|
||||||
|
let image = PngData {
|
||||||
|
raw: png,
|
||||||
|
idat_data,
|
||||||
|
aux_chunks: Vec::new(),
|
||||||
|
};
|
||||||
|
if image.estimated_output_size() < max_size.unwrap_or(usize::MAX) {
|
||||||
|
debug!("Found better combination:");
|
||||||
|
debug!(
|
||||||
|
" zc = {} f = {:8} {} bytes",
|
||||||
|
opts.deflate,
|
||||||
|
filter,
|
||||||
|
image.idat_data.len()
|
||||||
|
);
|
||||||
|
return Some(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(result) = eval_result {
|
||||||
|
// If idat_recoding is off and reductions were attempted but ended up choosing the baseline,
|
||||||
|
// we should still check if the evaluator compressed the baseline smaller than the original.
|
||||||
|
let image = PngData {
|
||||||
|
raw: result.image,
|
||||||
|
idat_data: result.idat_data,
|
||||||
|
aux_chunks: Vec::new(),
|
||||||
|
};
|
||||||
|
if image.estimated_output_size() < max_size.unwrap_or(usize::MAX) {
|
||||||
|
debug!("Found better combination:");
|
||||||
|
debug!(
|
||||||
|
" zc = {} f = {:8} {} bytes",
|
||||||
|
eval_compression,
|
||||||
|
result.filter,
|
||||||
|
image.idat_data.len()
|
||||||
|
);
|
||||||
|
return Some(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a compression trial
|
/// Execute a compression trial
|
||||||
fn perform_trial(
|
fn perform_trial(
|
||||||
filtered: &[u8],
|
filtered: &[u8],
|
||||||
opts: &Options,
|
opts: &Options,
|
||||||
trial: TrialOptions,
|
filter: RowFilter,
|
||||||
best_size: &AtomicMin,
|
best_size: &AtomicMin,
|
||||||
) -> Option<TrialWithData> {
|
) -> Option<TrialResult> {
|
||||||
let new_idat = match opts.deflate {
|
match opts.deflate.deflate(filtered, best_size) {
|
||||||
Deflaters::Libdeflater { .. } => deflate::deflate(filtered, trial.compression, best_size),
|
Ok(new_idat) => {
|
||||||
#[cfg(feature = "zopfli")]
|
let bytes = new_idat.len();
|
||||||
Deflaters::Zopfli { iterations } => deflate::zopfli_deflate(filtered, iterations),
|
best_size.set_min(bytes);
|
||||||
};
|
trace!(
|
||||||
|
" zc = {} f = {:8} {} bytes",
|
||||||
// update best size or convert to error if not smaller
|
opts.deflate,
|
||||||
let new_idat = match new_idat {
|
filter,
|
||||||
Ok(n) if !best_size.set_min(n.len()) => Err(PngError::DeflatedDataTooLong(n.len())),
|
bytes
|
||||||
_ => new_idat,
|
|
||||||
};
|
|
||||||
|
|
||||||
match new_idat {
|
|
||||||
Ok(n) => {
|
|
||||||
let bytes = n.len();
|
|
||||||
debug!(
|
|
||||||
" zc = {} f = {} {} bytes",
|
|
||||||
trial.compression, trial.filter, bytes
|
|
||||||
);
|
);
|
||||||
Some((trial, n))
|
Some((filter, new_idat))
|
||||||
}
|
}
|
||||||
Err(PngError::DeflatedDataTooLong(bytes)) => {
|
Err(PngError::DeflatedDataTooLong(bytes)) => {
|
||||||
debug!(
|
trace!(
|
||||||
" zc = {} f = {} >{} bytes",
|
" zc = {} f = {:8} >{} bytes",
|
||||||
trial.compression, trial.filter, bytes,
|
opts.deflate,
|
||||||
|
filter,
|
||||||
|
bytes,
|
||||||
);
|
);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -814,126 +839,71 @@ impl Deadline {
|
||||||
|
|
||||||
/// Display the format of the image data
|
/// Display the format of the image data
|
||||||
fn report_format(prefix: &str, png: &PngImage) {
|
fn report_format(prefix: &str, png: &PngImage) {
|
||||||
if let Some(ref palette) = png.palette {
|
|
||||||
debug!(
|
debug!(
|
||||||
"{}{} bits/pixel, {} colors in palette ({})",
|
"{}{}-bit {}, {}",
|
||||||
prefix,
|
prefix, png.ihdr.bit_depth, png.ihdr.color_type, png.ihdr.interlaced
|
||||||
png.ihdr.bit_depth,
|
|
||||||
palette.len(),
|
|
||||||
png.ihdr.interlaced
|
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
debug!(
|
|
||||||
"{}{}x{} bits/pixel, {} ({})",
|
|
||||||
prefix,
|
|
||||||
png.channels_per_pixel(),
|
|
||||||
png.ihdr.bit_depth,
|
|
||||||
png.ihdr.color_type,
|
|
||||||
png.ihdr.interlaced
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Strip headers from the `PngData` object, as requested by the passed `Options`
|
/// Perform cleanup of certain chunks from the `PngData` object, after optimization has been completed
|
||||||
fn perform_strip(png: &mut PngData, opts: &Options) {
|
fn postprocess_chunks(png: &mut PngData, opts: &Options, orig_ihdr: &IhdrData) {
|
||||||
let raw = Arc::make_mut(&mut png.raw);
|
if let Some(iccp_idx) = png.aux_chunks.iter().position(|c| &c.name == b"iCCP") {
|
||||||
match opts.strip {
|
// See if we can replace an iCCP chunk with an sRGB chunk
|
||||||
// Strip headers
|
let may_replace_iccp = opts.strip != StripChunks::None && opts.strip.keep(b"sRGB");
|
||||||
Headers::None => (),
|
if may_replace_iccp && png.aux_chunks.iter().any(|c| &c.name == b"sRGB") {
|
||||||
Headers::Keep(ref hdrs) => raw
|
|
||||||
.aux_headers
|
|
||||||
.retain(|hdr, _| std::str::from_utf8(hdr).map_or(false, |name| hdrs.contains(name))),
|
|
||||||
Headers::Strip(ref hdrs) => {
|
|
||||||
for hdr in hdrs {
|
|
||||||
raw.aux_headers.remove(hdr.as_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Headers::Safe => {
|
|
||||||
const PRESERVED_HEADERS: [[u8; 4]; 5] =
|
|
||||||
[*b"cICP", *b"iCCP", *b"sBIT", *b"sRGB", *b"pHYs"];
|
|
||||||
let keys: Vec<[u8; 4]> = raw.aux_headers.keys().cloned().collect();
|
|
||||||
for hdr in &keys {
|
|
||||||
if !PRESERVED_HEADERS.contains(hdr) {
|
|
||||||
raw.aux_headers.remove(hdr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Headers::All => {
|
|
||||||
raw.aux_headers = IndexMap::new();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let may_replace_iccp = match opts.strip {
|
|
||||||
Headers::Keep(ref hdrs) => hdrs.contains("sRGB"),
|
|
||||||
Headers::Strip(ref hdrs) => !hdrs.iter().any(|v| v == "sRGB"),
|
|
||||||
Headers::Safe => true,
|
|
||||||
Headers::None | Headers::All => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if may_replace_iccp {
|
|
||||||
if raw.aux_headers.get(b"sRGB").is_some() {
|
|
||||||
// Files aren't supposed to have both chunks, so we chose to honor sRGB
|
// Files aren't supposed to have both chunks, so we chose to honor sRGB
|
||||||
raw.aux_headers.remove(b"iCCP");
|
trace!("Removing iCCP chunk due to conflict with sRGB chunk");
|
||||||
} else if let Some(intent) = raw
|
png.aux_chunks.remove(iccp_idx);
|
||||||
.aux_headers
|
} else if let Some(icc) = extract_icc(&png.aux_chunks[iccp_idx]) {
|
||||||
.get(b"iCCP")
|
let intent = if may_replace_iccp {
|
||||||
.and_then(|iccp| srgb_rendering_intent(iccp))
|
srgb_rendering_intent(&icc)
|
||||||
{
|
} else {
|
||||||
// sRGB-like profile can be safely replaced with
|
None
|
||||||
// an sRGB chunk with the same rendering intent
|
};
|
||||||
raw.aux_headers.remove(b"iCCP");
|
// sRGB-like profile can be replaced with an sRGB chunk with the same rendering intent
|
||||||
raw.aux_headers.insert(*b"sRGB", vec![intent]);
|
// Otherwise try recompressing the profile
|
||||||
|
if let Some(intent) = intent {
|
||||||
|
trace!("Replacing iCCP chunk with equivalent sRGB chunk");
|
||||||
|
png.aux_chunks[iccp_idx] = Chunk {
|
||||||
|
name: *b"sRGB",
|
||||||
|
data: vec![intent],
|
||||||
|
};
|
||||||
|
} else if let Ok(iccp) = construct_iccp(&icc, opts.deflate) {
|
||||||
|
let cur_len = png.aux_chunks[iccp_idx].data.len();
|
||||||
|
let new_len = iccp.data.len();
|
||||||
|
if new_len < cur_len {
|
||||||
|
debug!(
|
||||||
|
"Recompressed iCCP chunk: {} ({} bytes decrease)",
|
||||||
|
new_len,
|
||||||
|
cur_len - new_len
|
||||||
|
);
|
||||||
|
png.aux_chunks[iccp_idx] = iccp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// If the profile is sRGB, extracts the rendering intent value from it
|
|
||||||
fn srgb_rendering_intent(mut iccp: &[u8]) -> Option<u8> {
|
|
||||||
// Skip (useless) profile name
|
|
||||||
loop {
|
|
||||||
let (&n, rest) = iccp.split_first()?;
|
|
||||||
iccp = rest;
|
|
||||||
if n == 0 {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (&compression_method, compressed_data) = iccp.split_first()?;
|
// If the depth/color type has changed, some chunks may be invalid and should be dropped
|
||||||
if compression_method != 0 {
|
// While these could potentially be converted, they have no known use case today and are
|
||||||
return None; // The profile is supposed to be compressed (method 0)
|
// generally more trouble than they're worth
|
||||||
|
let ihdr = &png.raw.ihdr;
|
||||||
|
if orig_ihdr.bit_depth != ihdr.bit_depth || orig_ihdr.color_type != ihdr.color_type {
|
||||||
|
png.aux_chunks.retain(|c| {
|
||||||
|
let invalid = &c.name == b"bKGD" || &c.name == b"sBIT" || &c.name == b"hIST";
|
||||||
|
if invalid {
|
||||||
|
warn!(
|
||||||
|
"Removing {} chunk as it no longer matches the image data",
|
||||||
|
std::str::from_utf8(&c.name).unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// The decompressed size is unknown so we have to guess the required buffer size
|
!invalid
|
||||||
let max_size = (compressed_data.len() * 2).max(1000);
|
});
|
||||||
let icc_data = inflate(compressed_data, max_size).ok()?;
|
|
||||||
|
|
||||||
let rendering_intent = *icc_data.get(67)?;
|
|
||||||
|
|
||||||
// The known profiles are the same as in libpng's `png_sRGB_checks`.
|
|
||||||
// The Profile ID header of ICC has a fixed layout,
|
|
||||||
// and is supposed to contain MD5 of profile data at this offset
|
|
||||||
match icc_data.get(84..100)? {
|
|
||||||
b"\x29\xf8\x3d\xde\xaf\xf2\x55\xae\x78\x42\xfa\xe4\xca\x83\x39\x0d"
|
|
||||||
| b"\xc9\x5b\xd6\x37\xe9\x5d\x8a\x3b\x0d\xf3\x8f\x99\xc1\x32\x03\x89"
|
|
||||||
| b"\xfc\x66\x33\x78\x37\xe2\x88\x6b\xfd\x72\xe9\x83\x82\x28\xf1\xb8"
|
|
||||||
| b"\x34\x56\x2a\xbf\x99\x4c\xcd\x06\x6d\x2c\x57\x21\xd0\xd6\x8c\x5d" => {
|
|
||||||
Some(rendering_intent)
|
|
||||||
}
|
|
||||||
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" => {
|
|
||||||
// Known-bad profiles are identified by their CRC
|
|
||||||
match (crc32(&icc_data), icc_data.len()) {
|
|
||||||
(0x5d51_29ce, 3024) | (0x182e_a552, 3144) | (0xf29e_526d, 3144) => {
|
|
||||||
Some(rendering_intent)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if an image was already optimized prior to oxipng's operations
|
/// Check if an image was already optimized prior to oxipng's operations
|
||||||
fn is_fully_optimized(original_size: usize, optimized_size: usize, opts: &Options) -> bool {
|
fn is_fully_optimized(original_size: usize, optimized_size: usize, opts: &Options) -> bool {
|
||||||
original_size <= optimized_size && !opts.force && opts.interlace.is_none()
|
original_size <= optimized_size && !opts.force
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_backup(input_path: &Path) -> PngResult<()> {
|
fn perform_backup(input_path: &Path) -> PngResult<()> {
|
||||||
|
|
@ -1032,9 +1002,10 @@ fn copy_times(_: &Metadata, _: &Path) -> PngResult<()> {
|
||||||
fn copy_times(input_path_meta: &Metadata, out_path: &Path) -> PngResult<()> {
|
fn copy_times(input_path_meta: &Metadata, out_path: &Path) -> PngResult<()> {
|
||||||
let atime = filetime::FileTime::from_last_access_time(input_path_meta);
|
let atime = filetime::FileTime::from_last_access_time(input_path_meta);
|
||||||
let mtime = filetime::FileTime::from_last_modification_time(input_path_meta);
|
let mtime = filetime::FileTime::from_last_modification_time(input_path_meta);
|
||||||
debug!(
|
trace!(
|
||||||
"attempting to set file times: atime: {:?}, mtime: {:?}",
|
"attempting to set file times: atime: {:?}, mtime: {:?}",
|
||||||
atime, mtime
|
atime,
|
||||||
|
mtime
|
||||||
);
|
);
|
||||||
filetime::set_file_times(out_path, atime, mtime).map_err(|err_io| {
|
filetime::set_file_times(out_path, atime, mtime).map_err(|err_io| {
|
||||||
PngError::new(&format!(
|
PngError::new(&format!(
|
||||||
|
|
@ -1043,47 +1014,3 @@ fn copy_times(input_path_meta: &Metadata, out_path: &Path) -> PngResult<()> {
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validate that the output png data still matches the original image
|
|
||||||
fn validate_output(output: &[u8], original_data: &[u8]) -> bool {
|
|
||||||
let (old_png, new_png) = rayon::join(
|
|
||||||
|| load_png_image_from_memory(original_data),
|
|
||||||
|| load_png_image_from_memory(output),
|
|
||||||
);
|
|
||||||
|
|
||||||
match (new_png, old_png) {
|
|
||||||
(Err(new_err), _) => {
|
|
||||||
error!("Failed to read output image for validation: {}", new_err);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
(_, Err(old_err)) => {
|
|
||||||
// The original image might be invalid if, for example, there is a CRC error,
|
|
||||||
// and we set fix_errors to true. In that case, all we can do is check that the
|
|
||||||
// new image is decodable.
|
|
||||||
warn!("Failed to read input image for validation: {}", old_err);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
(Ok(new_png), Ok(old_png)) => images_equal(&old_png, &new_png),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loads a PNG image from memory to a [DynamicImage]
|
|
||||||
fn load_png_image_from_memory(png_data: &[u8]) -> Result<DynamicImage, image::ImageError> {
|
|
||||||
let mut reader = image::io::Reader::new(Cursor::new(png_data));
|
|
||||||
reader.set_format(ImageFormat::Png);
|
|
||||||
reader.no_limits();
|
|
||||||
reader.decode()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compares images pixel by pixel for equivalent content
|
|
||||||
fn images_equal(old_png: &DynamicImage, new_png: &DynamicImage) -> bool {
|
|
||||||
let a = old_png.pixels().filter(|x| {
|
|
||||||
let p = x.2.channels();
|
|
||||||
!(p.len() == 4 && p[3] == 0)
|
|
||||||
});
|
|
||||||
let b = new_png.pixels().filter(|x| {
|
|
||||||
let p = x.2.channels();
|
|
||||||
!(p.len() == 4 && p[3] == 0)
|
|
||||||
});
|
|
||||||
a.eq(b)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
88
src/main.rs
|
|
@ -13,13 +13,13 @@
|
||||||
#![warn(clippy::range_plus_one)]
|
#![warn(clippy::range_plus_one)]
|
||||||
#![allow(clippy::cognitive_complexity)]
|
#![allow(clippy::cognitive_complexity)]
|
||||||
|
|
||||||
use clap::{AppSettings, Arg, ArgMatches, Command};
|
use clap::{AppSettings, Arg, ArgAction, ArgMatches, Command};
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use log::{error, warn};
|
use log::{error, warn};
|
||||||
use oxipng::Deflaters;
|
use oxipng::Deflaters;
|
||||||
use oxipng::Headers;
|
|
||||||
use oxipng::Options;
|
use oxipng::Options;
|
||||||
use oxipng::RowFilter;
|
use oxipng::RowFilter;
|
||||||
|
use oxipng::StripChunks;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::{InFile, OutFile};
|
||||||
use std::fs::DirBuilder;
|
use std::fs::DirBuilder;
|
||||||
#[cfg(feature = "zopfli")]
|
#[cfg(feature = "zopfli")]
|
||||||
|
|
@ -144,19 +144,26 @@ fn main() {
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("interlace")
|
Arg::new("interlace")
|
||||||
.help("PNG interlace type")
|
.help("PNG interlace type - Default: 0")
|
||||||
.short('i')
|
.short('i')
|
||||||
.long("interlace")
|
.long("interlace")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.value_name("0/1")
|
.value_name("type")
|
||||||
.possible_value("0")
|
.possible_value("0")
|
||||||
.possible_value("1"),
|
.possible_value("1")
|
||||||
|
.possible_value("keep"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("scale16")
|
||||||
|
.help("Forcibly reduce 16-bit images to 8-bit")
|
||||||
|
.long("scale16"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("verbose")
|
Arg::new("verbose")
|
||||||
.help("Run in verbose mode")
|
.help("Run in verbose mode (use multiple times to increase verbosity)")
|
||||||
.short('v')
|
.short('v')
|
||||||
.long("verbose")
|
.long("verbose")
|
||||||
|
.action(ArgAction::Count)
|
||||||
.conflicts_with("quiet"),
|
.conflicts_with("quiet"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
|
|
@ -381,7 +388,7 @@ fn parse_opts_into_struct(
|
||||||
stderrlog::new()
|
stderrlog::new()
|
||||||
.module(module_path!())
|
.module(module_path!())
|
||||||
.quiet(matches.is_present("quiet"))
|
.quiet(matches.is_present("quiet"))
|
||||||
.verbosity(if matches.is_present("verbose") { 3 } else { 2 })
|
.verbosity(matches.get_count("verbose") as usize + 2)
|
||||||
.show_level(false)
|
.show_level(false)
|
||||||
.init()
|
.init()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -393,7 +400,11 @@ fn parse_opts_into_struct(
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(x) = matches.value_of("interlace") {
|
if let Some(x) = matches.value_of("interlace") {
|
||||||
opts.interlace = x.parse::<u8>().unwrap().try_into().ok();
|
opts.interlace = if x == "keep" {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
x.parse::<u8>().unwrap().try_into().ok()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(x) = matches.value_of("filters") {
|
if let Some(x) = matches.value_of("filters") {
|
||||||
|
|
@ -440,6 +451,10 @@ fn parse_opts_into_struct(
|
||||||
opts.optimize_alpha = true;
|
opts.optimize_alpha = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matches.is_present("scale16") {
|
||||||
|
opts.scale_16 = true;
|
||||||
|
}
|
||||||
|
|
||||||
if matches.is_present("fast") {
|
if matches.is_present("fast") {
|
||||||
opts.fast_evaluation = true;
|
opts.fast_evaluation = true;
|
||||||
}
|
}
|
||||||
|
|
@ -495,40 +510,44 @@ fn parse_opts_into_struct(
|
||||||
opts.idat_recoding = false;
|
opts.idat_recoding = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(hdrs) = matches.value_of("keep") {
|
if let Some(keep) = matches.value_of("keep") {
|
||||||
opts.strip = Headers::Keep(hdrs.split(',').map(|x| x.trim().to_owned()).collect())
|
let names = keep
|
||||||
|
.split(',')
|
||||||
|
.map(parse_chunk_name)
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
opts.strip = StripChunks::Keep(names)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(hdrs) = matches.value_of("strip") {
|
if let Some(strip) = matches.value_of("strip") {
|
||||||
let hdrs = hdrs
|
if strip == "safe" {
|
||||||
.split(',')
|
opts.strip = StripChunks::Safe;
|
||||||
.map(|x| x.trim().to_owned())
|
} else if strip == "all" {
|
||||||
.collect::<Vec<String>>();
|
opts.strip = StripChunks::All;
|
||||||
if hdrs.contains(&"safe".to_owned()) || hdrs.contains(&"all".to_owned()) {
|
|
||||||
if hdrs.len() > 1 {
|
|
||||||
return Err(
|
|
||||||
"'safe' or 'all' presets for --strip should be used by themselves".to_owned(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if hdrs[0] == "safe" {
|
|
||||||
opts.strip = Headers::Safe;
|
|
||||||
} else {
|
|
||||||
opts.strip = Headers::All;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const FORBIDDEN_CHUNKS: [[u8; 4]; 5] =
|
const FORBIDDEN_CHUNKS: [[u8; 4]; 5] =
|
||||||
[*b"IHDR", *b"IDAT", *b"tRNS", *b"PLTE", *b"IEND"];
|
[*b"IHDR", *b"IDAT", *b"tRNS", *b"PLTE", *b"IEND"];
|
||||||
for i in &hdrs {
|
let names = strip
|
||||||
if FORBIDDEN_CHUNKS.iter().any(|chunk| chunk == i.as_bytes()) {
|
.split(',')
|
||||||
return Err(format!("{} chunk is not allowed to be stripped", i));
|
.map(|x| {
|
||||||
|
if x == "safe" || x == "all" {
|
||||||
|
return Err(
|
||||||
|
"'safe' or 'all' presets for --strip should be used by themselves"
|
||||||
|
.to_owned(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
let name = parse_chunk_name(x)?;
|
||||||
|
if FORBIDDEN_CHUNKS.contains(&name) {
|
||||||
|
return Err(format!("{} chunk is not allowed to be stripped", x));
|
||||||
}
|
}
|
||||||
opts.strip = Headers::Strip(hdrs);
|
Ok(name)
|
||||||
|
})
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
opts.strip = StripChunks::Strip(names);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches.is_present("strip-safe") {
|
if matches.is_present("strip-safe") {
|
||||||
opts.strip = Headers::Safe;
|
opts.strip = StripChunks::Safe;
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches.is_present("zopfli") {
|
if matches.is_present("zopfli") {
|
||||||
|
|
@ -555,6 +574,13 @@ fn parse_opts_into_struct(
|
||||||
Ok((out_file, out_dir, opts))
|
Ok((out_file, out_dir, opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_chunk_name(name: &str) -> Result<[u8; 4], String> {
|
||||||
|
name.trim()
|
||||||
|
.as_bytes()
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| format!("Invalid chunk name {}", name))
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_numeric_range_opts(
|
fn parse_numeric_range_opts(
|
||||||
input: &str,
|
input: &str,
|
||||||
min_value: u8,
|
min_value: u8,
|
||||||
|
|
|
||||||
243
src/png/mod.rs
|
|
@ -1,14 +1,13 @@
|
||||||
use crate::colors::ColorType;
|
use crate::colors::{BitDepth, ColorType};
|
||||||
use crate::deflate;
|
use crate::deflate;
|
||||||
use crate::error::PngError;
|
use crate::error::PngError;
|
||||||
use crate::filters::*;
|
use crate::filters::*;
|
||||||
use crate::headers::*;
|
use crate::headers::*;
|
||||||
use crate::interlace::{deinterlace_image, interlace_image, Interlacing};
|
use crate::interlace::{deinterlace_image, interlace_image, Interlacing};
|
||||||
|
use crate::Options;
|
||||||
use bitvec::bitarr;
|
use bitvec::bitarr;
|
||||||
use indexmap::IndexMap;
|
|
||||||
use libdeflater::{CompressionLvl, Compressor};
|
use libdeflater::{CompressionLvl, Compressor};
|
||||||
use rgb::ComponentSlice;
|
use rgb::ComponentSlice;
|
||||||
use rgb::RGBA8;
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufReader, Read, Write};
|
use std::io::{BufReader, Read, Write};
|
||||||
|
|
@ -31,13 +30,6 @@ pub struct PngImage {
|
||||||
pub ihdr: IhdrData,
|
pub ihdr: IhdrData,
|
||||||
/// The uncompressed, unfiltered data from the IDAT chunk
|
/// The uncompressed, unfiltered data from the IDAT chunk
|
||||||
pub data: Vec<u8>,
|
pub 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<RGBA8>>,
|
|
||||||
/// The pixel value that should be rendered as transparent
|
|
||||||
pub transparency_pixel: Option<Vec<u8>>,
|
|
||||||
/// All non-critical headers from the PNG are stored here
|
|
||||||
pub aux_headers: IndexMap<[u8; 4], Vec<u8>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains all data relevant to a PNG image
|
/// Contains all data relevant to a PNG image
|
||||||
|
|
@ -47,19 +39,17 @@ pub struct PngData {
|
||||||
pub raw: Arc<PngImage>,
|
pub raw: Arc<PngImage>,
|
||||||
/// The filtered and compressed data of the IDAT chunk
|
/// The filtered and compressed data of the IDAT chunk
|
||||||
pub idat_data: Vec<u8>,
|
pub idat_data: Vec<u8>,
|
||||||
/// The filtered, uncompressed data of the IDAT chunk
|
/// All non-critical chunks from the PNG are stored here
|
||||||
pub filtered: Vec<u8>,
|
pub aux_chunks: Vec<Chunk>,
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaletteWithTrns = (Option<Vec<RGBA8>>, Option<Vec<u8>>);
|
|
||||||
|
|
||||||
impl PngData {
|
impl PngData {
|
||||||
/// Create a new `PngData` struct by opening a file
|
/// Create a new `PngData` struct by opening a file
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(filepath: &Path, fix_errors: bool) -> Result<Self, PngError> {
|
pub fn new(filepath: &Path, opts: &Options) -> Result<Self, PngError> {
|
||||||
let byte_data = Self::read_file(filepath)?;
|
let byte_data = Self::read_file(filepath)?;
|
||||||
|
|
||||||
Self::from_slice(&byte_data, fix_errors)
|
Self::from_slice(&byte_data, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_file(filepath: &Path) -> Result<Vec<u8>, PngError> {
|
pub fn read_file(filepath: &Path) -> Result<Vec<u8>, PngError> {
|
||||||
|
|
@ -88,7 +78,7 @@ impl PngData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new `PngData` struct by reading a slice
|
/// Create a new `PngData` struct by reading a slice
|
||||||
pub fn from_slice(byte_data: &[u8], fix_errors: bool) -> Result<Self, PngError> {
|
pub fn from_slice(byte_data: &[u8], opts: &Options) -> Result<Self, PngError> {
|
||||||
let mut byte_offset: usize = 0;
|
let mut byte_offset: usize = 0;
|
||||||
// Test that png header is valid
|
// Test that png header is valid
|
||||||
let header = byte_data.get(0..8).ok_or(PngError::TruncatedData)?;
|
let header = byte_data.get(0..8).ok_or(PngError::TruncatedData)?;
|
||||||
|
|
@ -96,79 +86,65 @@ impl PngData {
|
||||||
return Err(PngError::NotPNG);
|
return Err(PngError::NotPNG);
|
||||||
}
|
}
|
||||||
byte_offset += 8;
|
byte_offset += 8;
|
||||||
// Read the data headers
|
|
||||||
let mut aux_headers: IndexMap<[u8; 4], Vec<u8>> = IndexMap::new();
|
// Read the data chunks
|
||||||
let mut idat_headers: Vec<u8> = Vec::new();
|
let mut idat_data: Vec<u8> = Vec::new();
|
||||||
while let Some(header) = parse_next_header(byte_data, &mut byte_offset, fix_errors)? {
|
let mut key_chunks: FxHashMap<[u8; 4], Vec<u8>> = FxHashMap::default();
|
||||||
match &header.name {
|
let mut aux_chunks: Vec<Chunk> = Vec::new();
|
||||||
b"IDAT" => idat_headers.extend_from_slice(header.data),
|
while let Some(chunk) = parse_next_chunk(byte_data, &mut byte_offset, opts.fix_errors)? {
|
||||||
|
match &chunk.name {
|
||||||
|
b"IDAT" => idat_data.extend_from_slice(chunk.data),
|
||||||
b"acTL" => return Err(PngError::APNGNotSupported),
|
b"acTL" => return Err(PngError::APNGNotSupported),
|
||||||
|
b"IHDR" | b"PLTE" | b"tRNS" => {
|
||||||
|
key_chunks.insert(chunk.name, chunk.data.to_owned());
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
aux_headers.insert(header.name, header.data.to_owned());
|
if opts.strip.keep(&chunk.name) {
|
||||||
|
aux_chunks.push(Chunk {
|
||||||
|
name: chunk.name,
|
||||||
|
data: chunk.data.to_owned(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Parse the headers into our PngData
|
}
|
||||||
if idat_headers.is_empty() {
|
|
||||||
|
// Parse the chunks into our PngData
|
||||||
|
if idat_data.is_empty() {
|
||||||
return Err(PngError::ChunkMissing("IDAT"));
|
return Err(PngError::ChunkMissing("IDAT"));
|
||||||
}
|
}
|
||||||
let ihdr = match aux_headers.remove(b"IHDR") {
|
let ihdr_chunk = match key_chunks.remove(b"IHDR") {
|
||||||
Some(ihdr) => ihdr,
|
Some(ihdr) => ihdr,
|
||||||
None => return Err(PngError::ChunkMissing("IHDR")),
|
None => return Err(PngError::ChunkMissing("IHDR")),
|
||||||
};
|
};
|
||||||
let ihdr_header = parse_ihdr_header(&ihdr)?;
|
let ihdr = parse_ihdr_chunk(
|
||||||
let raw_data = deflate::inflate(idat_headers.as_ref(), ihdr_header.raw_data_size())?;
|
&ihdr_chunk,
|
||||||
|
key_chunks.remove(b"PLTE"),
|
||||||
|
key_chunks.remove(b"tRNS"),
|
||||||
|
)?;
|
||||||
|
let raw_data = deflate::inflate(idat_data.as_ref(), ihdr.raw_data_size())?;
|
||||||
|
|
||||||
// Reject files with incorrect width/height or truncated data
|
// Reject files with incorrect width/height or truncated data
|
||||||
if raw_data.len() != ihdr_header.raw_data_size() {
|
if raw_data.len() != ihdr.raw_data_size() {
|
||||||
return Err(PngError::TruncatedData);
|
return Err(PngError::TruncatedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (palette, transparency_pixel) = Self::palette_to_rgba(
|
|
||||||
ihdr_header.color_type,
|
|
||||||
aux_headers.remove(b"PLTE"),
|
|
||||||
aux_headers.remove(b"tRNS"),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut raw = PngImage {
|
let mut raw = PngImage {
|
||||||
ihdr: ihdr_header,
|
ihdr,
|
||||||
data: raw_data,
|
data: raw_data,
|
||||||
palette,
|
|
||||||
transparency_pixel,
|
|
||||||
aux_headers,
|
|
||||||
};
|
};
|
||||||
let unfiltered = raw.unfilter_image()?;
|
raw.data = raw.unfilter_image()?;
|
||||||
// Return the PngData
|
// Return the PngData
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
idat_data: idat_headers,
|
idat_data,
|
||||||
filtered: std::mem::replace(&mut raw.data, unfiltered),
|
|
||||||
raw: Arc::new(raw),
|
raw: Arc::new(raw),
|
||||||
|
aux_chunks,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle transparency header
|
/// Return an estimate of the output size which can help with evaluation of very small data
|
||||||
fn palette_to_rgba(
|
pub fn estimated_output_size(&self) -> usize {
|
||||||
color_type: ColorType,
|
self.idat_data.len() + self.raw.key_chunks_size()
|
||||||
palette_data: Option<Vec<u8>>,
|
|
||||||
trns_data: Option<Vec<u8>>,
|
|
||||||
) -> Result<PaletteWithTrns, PngError> {
|
|
||||||
if color_type == ColorType::Indexed {
|
|
||||||
let palette_data =
|
|
||||||
palette_data.ok_or_else(|| PngError::new("no palette in indexed image"))?;
|
|
||||||
let mut palette: Vec<_> = palette_data
|
|
||||||
.chunks(3)
|
|
||||||
.map(|color| RGBA8::new(color[0], color[1], color[2], 255))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if let Some(trns_data) = trns_data {
|
|
||||||
for (color, trns) in palette.iter_mut().zip(trns_data) {
|
|
||||||
color.a = trns;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok((Some(palette), None))
|
|
||||||
} else {
|
|
||||||
Ok((None, trns_data))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format the `PngData` struct into a valid PNG bytestream
|
/// Format the `PngData` struct into a valid PNG bytestream
|
||||||
|
|
@ -181,7 +157,7 @@ impl PngData {
|
||||||
ihdr_data
|
ihdr_data
|
||||||
.write_all(&self.raw.ihdr.height.to_be_bytes())
|
.write_all(&self.raw.ihdr.height.to_be_bytes())
|
||||||
.ok();
|
.ok();
|
||||||
ihdr_data.write_all(&[self.raw.ihdr.bit_depth.as_u8()]).ok();
|
ihdr_data.write_all(&[self.raw.ihdr.bit_depth as u8]).ok();
|
||||||
ihdr_data
|
ihdr_data
|
||||||
.write_all(&[self.raw.ihdr.color_type.png_header_code()])
|
.write_all(&[self.raw.ihdr.color_type.png_header_code()])
|
||||||
.ok();
|
.ok();
|
||||||
|
|
@ -189,58 +165,49 @@ impl PngData {
|
||||||
ihdr_data.write_all(&[0]).ok(); // Filter method -- 5-way adaptive filtering
|
ihdr_data.write_all(&[0]).ok(); // Filter method -- 5-way adaptive filtering
|
||||||
ihdr_data.write_all(&[self.raw.ihdr.interlaced as u8]).ok();
|
ihdr_data.write_all(&[self.raw.ihdr.interlaced as u8]).ok();
|
||||||
write_png_block(b"IHDR", &ihdr_data, &mut output);
|
write_png_block(b"IHDR", &ihdr_data, &mut output);
|
||||||
// Ancillary headers
|
// Ancillary chunks
|
||||||
for (key, header) in self
|
for chunk in self
|
||||||
.raw
|
.aux_chunks
|
||||||
.aux_headers
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&(key, _)| !(key == b"bKGD" || key == b"hIST" || key == b"tRNS"))
|
.filter(|c| !(&c.name == b"bKGD" || &c.name == b"hIST" || &c.name == b"tRNS"))
|
||||||
{
|
{
|
||||||
write_png_block(key, header, &mut output);
|
write_png_block(&chunk.name, &chunk.data, &mut output);
|
||||||
}
|
}
|
||||||
// Palette
|
// Palette and transparency
|
||||||
if let Some(ref palette) = self.raw.palette {
|
match &self.raw.ihdr.color_type {
|
||||||
|
ColorType::Indexed { palette } => {
|
||||||
let mut palette_data = Vec::with_capacity(palette.len() * 3);
|
let mut palette_data = Vec::with_capacity(palette.len() * 3);
|
||||||
let mut max_palette_size = 1 << (self.raw.ihdr.bit_depth.as_u8() as usize);
|
for px in palette {
|
||||||
// Ensure bKGD color doesn't get truncated from palette
|
|
||||||
if let Some(&idx) = self.raw.aux_headers.get(b"bKGD").and_then(|b| b.first()) {
|
|
||||||
max_palette_size = max_palette_size.max(idx as usize + 1);
|
|
||||||
}
|
|
||||||
for px in palette.iter().take(max_palette_size) {
|
|
||||||
palette_data.extend_from_slice(px.rgb().as_slice());
|
palette_data.extend_from_slice(px.rgb().as_slice());
|
||||||
}
|
}
|
||||||
write_png_block(b"PLTE", &palette_data, &mut output);
|
write_png_block(b"PLTE", &palette_data, &mut output);
|
||||||
let num_transparent =
|
if let Some(last_trns) = palette.iter().rposition(|px| px.a != 255) {
|
||||||
palette
|
let trns_data: Vec<_> = palette[0..=last_trns].iter().map(|px| px.a).collect();
|
||||||
.iter()
|
|
||||||
.take(max_palette_size)
|
|
||||||
.enumerate()
|
|
||||||
.fold(
|
|
||||||
0,
|
|
||||||
|prev, (index, px)| {
|
|
||||||
if px.a == 255 {
|
|
||||||
prev
|
|
||||||
} else {
|
|
||||||
index + 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if num_transparent > 0 {
|
|
||||||
let trns_data: Vec<_> = palette[0..num_transparent].iter().map(|px| px.a).collect();
|
|
||||||
write_png_block(b"tRNS", &trns_data, &mut output);
|
write_png_block(b"tRNS", &trns_data, &mut output);
|
||||||
}
|
}
|
||||||
} else if let Some(ref transparency_pixel) = self.raw.transparency_pixel {
|
|
||||||
// Transparency pixel
|
|
||||||
write_png_block(b"tRNS", transparency_pixel, &mut output);
|
|
||||||
}
|
}
|
||||||
// Special ancillary headers that need to come after PLTE but before IDAT
|
ColorType::Grayscale {
|
||||||
for (key, header) in self
|
transparent_shade: Some(trns),
|
||||||
.raw
|
} => {
|
||||||
.aux_headers
|
// Transparency pixel - 2 byte u16
|
||||||
|
write_png_block(b"tRNS", &trns.to_be_bytes(), &mut output);
|
||||||
|
}
|
||||||
|
ColorType::RGB {
|
||||||
|
transparent_color: Some(trns),
|
||||||
|
} => {
|
||||||
|
// Transparency pixel - 6 byte RGB16
|
||||||
|
let trns_data: Vec<_> = trns.iter().flat_map(|c| c.to_be_bytes()).collect();
|
||||||
|
write_png_block(b"tRNS", &trns_data, &mut output);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
// Special ancillary chunks that need to come after PLTE but before IDAT
|
||||||
|
for chunk in self
|
||||||
|
.aux_chunks
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&(key, _)| key == b"bKGD" || key == b"hIST" || key == b"tRNS")
|
.filter(|c| &c.name == b"bKGD" || &c.name == b"hIST" || &c.name == b"tRNS")
|
||||||
{
|
{
|
||||||
write_png_block(key, header, &mut output);
|
write_png_block(&chunk.name, &chunk.data, &mut output);
|
||||||
}
|
}
|
||||||
// IDAT data
|
// IDAT data
|
||||||
write_png_block(b"IDAT", &self.idat_data, &mut output);
|
write_png_block(b"IDAT", &self.idat_data, &mut output);
|
||||||
|
|
@ -274,8 +241,36 @@ impl PngImage {
|
||||||
|
|
||||||
/// Return the number of channels in the image, based on color type
|
/// Return the number of channels in the image, based on color type
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn channels_per_pixel(&self) -> u8 {
|
pub fn channels_per_pixel(&self) -> usize {
|
||||||
self.ihdr.color_type.channels_per_pixel()
|
self.ihdr.color_type.channels_per_pixel() as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the number of bytes per channel in the image
|
||||||
|
#[inline]
|
||||||
|
pub fn bytes_per_channel(&self) -> usize {
|
||||||
|
match self.ihdr.bit_depth {
|
||||||
|
BitDepth::Sixteen => 2,
|
||||||
|
// Depths lower than 8 will round up to 1 byte
|
||||||
|
_ => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the size of the PLTE and tRNS chunks
|
||||||
|
pub fn key_chunks_size(&self) -> usize {
|
||||||
|
match &self.ihdr.color_type {
|
||||||
|
ColorType::Indexed { palette } => {
|
||||||
|
let plte = 12 + palette.len() * 3;
|
||||||
|
let trns = palette.iter().filter(|p| p.a != 255).count();
|
||||||
|
if trns != 0 {
|
||||||
|
plte + 12 + trns
|
||||||
|
} else {
|
||||||
|
plte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ColorType::Grayscale { transparent_shade } if transparent_shade.is_some() => 12 + 2,
|
||||||
|
ColorType::RGB { transparent_color } if transparent_color.is_some() => 12 + 6,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an iterator over the scanlines of the image
|
/// Return an iterator over the scanlines of the image
|
||||||
|
|
@ -287,7 +282,7 @@ impl PngImage {
|
||||||
/// Reverse all filters applied on the image, returning an unfiltered IDAT bytestream
|
/// Reverse all filters applied on the image, returning an unfiltered IDAT bytestream
|
||||||
fn unfilter_image(&self) -> Result<Vec<u8>, PngError> {
|
fn unfilter_image(&self) -> Result<Vec<u8>, PngError> {
|
||||||
let mut unfiltered = Vec::with_capacity(self.data.len());
|
let mut unfiltered = Vec::with_capacity(self.data.len());
|
||||||
let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize;
|
let bpp = self.bytes_per_channel() * self.channels_per_pixel();
|
||||||
let mut last_line: Vec<u8> = Vec::new();
|
let mut last_line: Vec<u8> = Vec::new();
|
||||||
let mut last_pass = None;
|
let mut last_pass = None;
|
||||||
let mut unfiltered_buf = Vec::new();
|
let mut unfiltered_buf = Vec::new();
|
||||||
|
|
@ -309,13 +304,12 @@ impl PngImage {
|
||||||
/// Apply the specified filter type to all rows in the image
|
/// Apply the specified filter type to all rows in the image
|
||||||
pub fn filter_image(&self, filter: RowFilter, optimize_alpha: bool) -> Vec<u8> {
|
pub fn filter_image(&self, filter: RowFilter, optimize_alpha: bool) -> Vec<u8> {
|
||||||
let mut filtered = Vec::with_capacity(self.data.len());
|
let mut filtered = Vec::with_capacity(self.data.len());
|
||||||
let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize;
|
let bpp = self.bytes_per_channel() * self.channels_per_pixel();
|
||||||
// If alpha optimization is enabled, determine how many bytes of alpha there are per pixel
|
// If alpha optimization is enabled, determine how many bytes of alpha there are per pixel
|
||||||
let alpha_bytes = match self.ihdr.color_type {
|
let alpha_bytes = if optimize_alpha && self.ihdr.color_type.has_alpha() {
|
||||||
ColorType::RGBA | ColorType::GrayscaleAlpha if optimize_alpha => {
|
self.bytes_per_channel()
|
||||||
(self.ihdr.bit_depth.as_u8() / 8) as usize
|
} else {
|
||||||
}
|
0
|
||||||
_ => 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut prev_line = Vec::new();
|
let mut prev_line = Vec::new();
|
||||||
|
|
@ -465,14 +459,15 @@ impl PngImage {
|
||||||
filtered
|
filtered
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn write_png_block(key: &[u8], header: &[u8], output: &mut Vec<u8>) {
|
|
||||||
let mut header_data = Vec::with_capacity(header.len() + 4);
|
fn write_png_block(key: &[u8], chunk: &[u8], output: &mut Vec<u8>) {
|
||||||
header_data.extend_from_slice(key);
|
let mut chunk_data = Vec::with_capacity(chunk.len() + 4);
|
||||||
header_data.extend_from_slice(header);
|
chunk_data.extend_from_slice(key);
|
||||||
output.reserve(header_data.len() + 8);
|
chunk_data.extend_from_slice(chunk);
|
||||||
output.extend_from_slice(&(header_data.len() as u32 - 4).to_be_bytes());
|
output.reserve(chunk_data.len() + 8);
|
||||||
let crc = deflate::crc32(&header_data);
|
output.extend_from_slice(&(chunk_data.len() as u32 - 4).to_be_bytes());
|
||||||
output.append(&mut header_data);
|
let crc = deflate::crc32(&chunk_data);
|
||||||
|
output.append(&mut chunk_data);
|
||||||
output.extend_from_slice(&crc.to_be_bytes());
|
output.extend_from_slice(&crc.to_be_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ impl<'a> Iterator for ScanLines<'a> {
|
||||||
struct ScanLineRanges {
|
struct ScanLineRanges {
|
||||||
/// Current pass number, and 0-indexed row within the pass
|
/// Current pass number, and 0-indexed row within the pass
|
||||||
pass: Option<(u8, u32)>,
|
pass: Option<(u8, u32)>,
|
||||||
bits_per_pixel: u8,
|
bits_per_pixel: usize,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
left: usize,
|
left: usize,
|
||||||
|
|
@ -53,7 +53,7 @@ struct ScanLineRanges {
|
||||||
impl ScanLineRanges {
|
impl ScanLineRanges {
|
||||||
pub fn new(png: &PngImage, has_filter: bool) -> Self {
|
pub fn new(png: &PngImage, has_filter: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bits_per_pixel: png.ihdr.bit_depth.as_u8() * png.channels_per_pixel(),
|
bits_per_pixel: png.ihdr.bpp(),
|
||||||
width: png.ihdr.width,
|
width: png.ihdr.width,
|
||||||
height: png.ihdr.height,
|
height: png.ihdr.height,
|
||||||
left: png.data.len(),
|
left: png.data.len(),
|
||||||
|
|
@ -143,8 +143,8 @@ impl Iterator for ScanLineRanges {
|
||||||
// Standard, non-interlaced PNG scanlines
|
// Standard, non-interlaced PNG scanlines
|
||||||
(self.width, None)
|
(self.width, None)
|
||||||
};
|
};
|
||||||
let bits_per_line = pixels_per_line * u32::from(self.bits_per_pixel);
|
let bits_per_line = pixels_per_line as usize * self.bits_per_pixel;
|
||||||
let mut len = ((bits_per_line + 7) / 8) as usize;
|
let mut len = (bits_per_line + 7) / 8;
|
||||||
if self.has_filter {
|
if self.has_filter {
|
||||||
len += 1;
|
len += 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ where
|
||||||
|
|
||||||
impl<I: Iterator> ParallelIterator for I {}
|
impl<I: Iterator> ParallelIterator for I {}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn join<A, B>(a: impl FnOnce() -> A, b: impl FnOnce() -> B) -> (A, B) {
|
pub fn join<A, B>(a: impl FnOnce() -> A, b: impl FnOnce() -> B) -> (A, B) {
|
||||||
(a(), b())
|
(a(), b())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,21 @@
|
||||||
|
use rgb::RGB16;
|
||||||
|
|
||||||
use crate::colors::{BitDepth, ColorType};
|
use crate::colors::{BitDepth, ColorType};
|
||||||
use crate::headers::IhdrData;
|
use crate::headers::IhdrData;
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
|
|
||||||
/// Clean the alpha channel by setting the color of all fully transparent pixels to black
|
/// Clean the alpha channel by setting the color of all fully transparent pixels to black
|
||||||
pub fn cleaned_alpha_channel(png: &PngImage) -> Option<PngImage> {
|
pub fn cleaned_alpha_channel(png: &PngImage) -> Option<PngImage> {
|
||||||
let (bpc, bpp) = match png.ihdr.color_type {
|
if !png.ihdr.color_type.has_alpha() {
|
||||||
ColorType::RGBA | ColorType::GrayscaleAlpha => {
|
|
||||||
let cpp = png.channels_per_pixel();
|
|
||||||
let bpc = png.ihdr.bit_depth.as_u8() / 8;
|
|
||||||
(bpc as usize, (bpc * cpp) as usize)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
let byte_depth = png.bytes_per_channel();
|
||||||
|
let bpp = png.channels_per_pixel() * byte_depth;
|
||||||
|
let colored_bytes = bpp - byte_depth;
|
||||||
|
|
||||||
let mut reduced = Vec::with_capacity(png.data.len());
|
let mut reduced = Vec::with_capacity(png.data.len());
|
||||||
for pixel in png.data.chunks(bpp) {
|
for pixel in png.data.chunks(bpp) {
|
||||||
if pixel.iter().skip(bpp - bpc).all(|b| *b == 0) {
|
if pixel.iter().skip(colored_bytes).all(|b| *b == 0) {
|
||||||
reduced.resize(reduced.len() + bpp, 0);
|
reduced.resize(reduced.len() + bpp, 0);
|
||||||
} else {
|
} else {
|
||||||
reduced.extend_from_slice(pixel);
|
reduced.extend_from_slice(pixel);
|
||||||
|
|
@ -26,23 +24,17 @@ pub fn cleaned_alpha_channel(png: &PngImage) -> Option<PngImage> {
|
||||||
|
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
data: reduced,
|
data: reduced,
|
||||||
ihdr: png.ihdr,
|
ihdr: png.ihdr.clone(),
|
||||||
palette: png.palette.clone(),
|
|
||||||
transparency_pixel: png.transparency_pixel.clone(),
|
|
||||||
aux_headers: png.aux_headers.clone(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<PngImage> {
|
pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<PngImage> {
|
||||||
let target_color_type = match png.ihdr.color_type {
|
if !png.ihdr.color_type.has_alpha() {
|
||||||
ColorType::GrayscaleAlpha => ColorType::Grayscale,
|
return None;
|
||||||
ColorType::RGBA => ColorType::RGB,
|
}
|
||||||
_ => return None,
|
let byte_depth = png.bytes_per_channel();
|
||||||
};
|
let bpp = png.channels_per_pixel() * byte_depth;
|
||||||
let byte_depth = (png.ihdr.bit_depth.as_u8() >> 3) as usize;
|
|
||||||
let channels = png.channels_per_pixel() as usize;
|
|
||||||
let bpp = channels * byte_depth;
|
|
||||||
let colored_bytes = bpp - byte_depth;
|
let colored_bytes = bpp - byte_depth;
|
||||||
|
|
||||||
// If alpha optimisation is enabled, see if the image contains only fully opaque and fully transparent pixels.
|
// If alpha optimisation is enabled, see if the image contains only fully opaque and fully transparent pixels.
|
||||||
|
|
@ -66,13 +58,7 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
|
||||||
|
|
||||||
let transparency_pixel = if has_transparency {
|
let transparency_pixel = if has_transparency {
|
||||||
// If no unused color was found we will have to fail here
|
// If no unused color was found we will have to fail here
|
||||||
// Otherwise, proceed to construct the tRNS chunk
|
Some(used_colors.iter().position(|b| !*b)? as u8)
|
||||||
let unused_color = used_colors.iter().position(|b| !*b)? as u8;
|
|
||||||
Some(match png.ihdr.bit_depth {
|
|
||||||
BitDepth::Sixteen => vec![unused_color; colored_bytes],
|
|
||||||
// 8-bit is still stored as 16-bit, with the high byte set to 0
|
|
||||||
_ => [0, unused_color].repeat(colored_bytes),
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
@ -80,20 +66,26 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
|
||||||
let mut raw_data = Vec::with_capacity(png.data.len());
|
let mut raw_data = Vec::with_capacity(png.data.len());
|
||||||
for pixel in png.data.chunks(bpp) {
|
for pixel in png.data.chunks(bpp) {
|
||||||
match transparency_pixel {
|
match transparency_pixel {
|
||||||
Some(ref trns) if pixel.iter().skip(colored_bytes).all(|b| *b == 0) => {
|
Some(trns) if pixel.iter().skip(colored_bytes).all(|b| *b == 0) => {
|
||||||
raw_data.resize(raw_data.len() + colored_bytes, trns[1]);
|
raw_data.resize(raw_data.len() + colored_bytes, trns);
|
||||||
}
|
}
|
||||||
_ => raw_data.extend_from_slice(&pixel[0..colored_bytes]),
|
_ => raw_data.extend_from_slice(&pixel[0..colored_bytes]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut aux_headers = png.aux_headers.clone();
|
// Construct the color type with appropriate transparency data
|
||||||
// sBIT contains information about alpha channel's original depth,
|
let transparent = transparency_pixel.map(|trns| match png.ihdr.bit_depth {
|
||||||
// and alpha has just been removed
|
BitDepth::Sixteen => (trns as u16) << 8 | trns as u16,
|
||||||
if let Some(sbit_header) = png.aux_headers.get(b"sBIT") {
|
_ => trns as u16,
|
||||||
// Some programs save the sBIT header as RGB even if the image is RGBA.
|
});
|
||||||
aux_headers.insert(*b"sBIT", sbit_header.iter().cloned().take(3).collect());
|
let target_color_type = match png.ihdr.color_type {
|
||||||
}
|
ColorType::GrayscaleAlpha => ColorType::Grayscale {
|
||||||
|
transparent_shade: transparent,
|
||||||
|
},
|
||||||
|
_ => ColorType::RGB {
|
||||||
|
transparent_color: transparent.map(|t| RGB16::new(t, t, t)),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
data: raw_data,
|
data: raw_data,
|
||||||
|
|
@ -101,8 +93,5 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
|
||||||
color_type: target_color_type,
|
color_type: target_color_type,
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers,
|
|
||||||
transparency_pixel,
|
|
||||||
palette: None,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,17 @@ use crate::colors::{BitDepth, ColorType};
|
||||||
use crate::headers::IhdrData;
|
use crate::headers::IhdrData;
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
|
|
||||||
/// Attempt to reduce the bit depth of the image
|
/// Attempt to reduce a 16-bit image to 8-bit, returning the reduced image if successful
|
||||||
/// Returns true if the bit depth was reduced, false otherwise
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage> {
|
pub fn reduced_bit_depth_16_to_8(png: &PngImage, force_scale: bool) -> Option<PngImage> {
|
||||||
if png.ihdr.bit_depth != BitDepth::Sixteen {
|
if png.ihdr.bit_depth != BitDepth::Sixteen {
|
||||||
if png.ihdr.color_type == ColorType::Indexed || png.ihdr.color_type == ColorType::Grayscale
|
|
||||||
{
|
|
||||||
return reduce_bit_depth_8_or_less(png, minimum_bits);
|
|
||||||
}
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if force_scale {
|
||||||
|
return scaled_bit_depth_16_to_8(png);
|
||||||
|
}
|
||||||
|
|
||||||
// Reduce from 16 to 8 bits per channel per pixel
|
// Reduce from 16 to 8 bits per channel per pixel
|
||||||
if png.data.chunks(2).any(|pair| pair[0] != pair[1]) {
|
if png.data.chunks(2).any(|pair| pair[0] != pair[1]) {
|
||||||
// Can't reduce
|
// Can't reduce
|
||||||
|
|
@ -23,56 +22,72 @@ pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage>
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
data: png.data.iter().step_by(2).cloned().collect(),
|
data: png.data.iter().step_by(2).cloned().collect(),
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
|
color_type: png.ihdr.color_type.clone(),
|
||||||
bit_depth: BitDepth::Eight,
|
bit_depth: BitDepth::Eight,
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
palette: None,
|
|
||||||
transparency_pixel: png.transparency_pixel.clone(),
|
|
||||||
aux_headers: png.aux_headers.clone(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Forcibly reduce a 16-bit image to 8-bit by scaling, returning the reduced image if successful
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Option<PngImage> {
|
pub fn scaled_bit_depth_16_to_8(png: &PngImage) -> Option<PngImage> {
|
||||||
|
if png.ihdr.bit_depth != BitDepth::Sixteen {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce from 16 to 8 bits per channel per pixel by scaling when necessary
|
||||||
|
let data = png
|
||||||
|
.data
|
||||||
|
.chunks(2)
|
||||||
|
.map(|pair| {
|
||||||
|
if pair[0] == pair[1] {
|
||||||
|
return pair[0];
|
||||||
|
}
|
||||||
|
// See: http://www.libpng.org/pub/png/spec/1.2/PNG-Decoders.html#D.Sample-depth-rescaling
|
||||||
|
// This allows values such as 0x00FF to be rounded to 0x01 rather than truncated to 0x00
|
||||||
|
let val = u16::from_be_bytes([pair[0], pair[1]]) as f64;
|
||||||
|
(val * 255.0 / 65535.0).round() as u8
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Some(PngImage {
|
||||||
|
data,
|
||||||
|
ihdr: IhdrData {
|
||||||
|
color_type: png.ihdr.color_type.clone(),
|
||||||
|
bit_depth: BitDepth::Eight,
|
||||||
|
..png.ihdr
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to reduce an 8/4/2-bit image to a lower bit depth, returning the reduced image if successful
|
||||||
|
#[must_use]
|
||||||
|
pub fn reduced_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Option<PngImage> {
|
||||||
assert!((1..8).contains(&minimum_bits));
|
assert!((1..8).contains(&minimum_bits));
|
||||||
let bit_depth: usize = png.ihdr.bit_depth.as_u8() as usize;
|
let bit_depth = png.ihdr.bit_depth as usize;
|
||||||
if minimum_bits >= bit_depth || bit_depth > 8 {
|
if minimum_bits >= bit_depth || bit_depth > 8 || png.channels_per_pixel() != 1 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// Calculate the current number of pixels per byte
|
// Calculate the current number of pixels per byte
|
||||||
let ppb = 8 / bit_depth;
|
let ppb = 8 / bit_depth;
|
||||||
|
|
||||||
if png.ihdr.color_type == ColorType::Indexed {
|
if let ColorType::Indexed { palette } = &png.ihdr.color_type {
|
||||||
for line in png.scan_lines(false) {
|
// We can easily determine minimum depth by the palette size
|
||||||
let line_max = line
|
let required_bits = match palette.len() {
|
||||||
.data
|
0..=2 => 1,
|
||||||
.iter()
|
3..=4 => 2,
|
||||||
.map(|&byte| match png.ihdr.bit_depth {
|
5..=16 => 4,
|
||||||
BitDepth::Two => (byte & 0x3)
|
_ => 8,
|
||||||
.max((byte >> 2) & 0x3)
|
|
||||||
.max((byte >> 4) & 0x3)
|
|
||||||
.max(byte >> 6),
|
|
||||||
BitDepth::Four => (byte & 0xF).max(byte >> 4),
|
|
||||||
_ => byte,
|
|
||||||
})
|
|
||||||
.max()
|
|
||||||
.unwrap_or(0);
|
|
||||||
let required_bits = match line_max {
|
|
||||||
x if x > 0x0F => 8,
|
|
||||||
x if x > 0x03 => 4,
|
|
||||||
x if x > 0x01 => 2,
|
|
||||||
_ => 1,
|
|
||||||
};
|
};
|
||||||
if required_bits > minimum_bits {
|
if required_bits >= bit_depth {
|
||||||
minimum_bits = required_bits;
|
|
||||||
if minimum_bits >= bit_depth {
|
|
||||||
// Not reducable
|
// Not reducable
|
||||||
return None;
|
return None;
|
||||||
}
|
} else if required_bits > minimum_bits {
|
||||||
}
|
minimum_bits = required_bits;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Checking for grayscale depth reduction is quite different than for indexed
|
// Finding minimum depth for grayscale is much more complicated
|
||||||
let mut mask = (1 << minimum_bits) - 1;
|
let mut mask = (1 << minimum_bits) - 1;
|
||||||
let mut divisions = 1..(bit_depth / minimum_bits);
|
let mut divisions = 1..(bit_depth / minimum_bits);
|
||||||
for &b in &png.data {
|
for &b in &png.data {
|
||||||
|
|
@ -129,12 +144,11 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the image is grayscale we also need to reduce the transparency pixel
|
// If the image is grayscale we also need to reduce the transparency pixel
|
||||||
let mut transparency_pixel = png
|
let color_type = if let ColorType::Grayscale {
|
||||||
.transparency_pixel
|
transparent_shade: Some(trans),
|
||||||
.clone()
|
} = png.ihdr.color_type
|
||||||
.filter(|t| png.ihdr.color_type == ColorType::Grayscale && t.len() >= 2);
|
{
|
||||||
if let Some(trans) = transparency_pixel {
|
let reduced_trans = (trans & 0xFF) >> (bit_depth - minimum_bits);
|
||||||
let reduced_trans = trans[1] >> (bit_depth - minimum_bits);
|
|
||||||
// Verify the reduction is valid by restoring back to original bit depth
|
// Verify the reduction is valid by restoring back to original bit depth
|
||||||
let mut check = reduced_trans;
|
let mut check = reduced_trans;
|
||||||
let mut bits = minimum_bits;
|
let mut bits = minimum_bits;
|
||||||
|
|
@ -142,22 +156,24 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
|
||||||
check = check << bits | check;
|
check = check << bits | check;
|
||||||
bits <<= 1;
|
bits <<= 1;
|
||||||
}
|
}
|
||||||
if trans[0] == 0 && trans[1] == check {
|
// If the transparency doesn't fit the new bit depth it is therefore unused - set it to None
|
||||||
transparency_pixel = Some(vec![0, reduced_trans]);
|
ColorType::Grayscale {
|
||||||
|
transparent_shade: if trans == check {
|
||||||
|
Some(reduced_trans)
|
||||||
} else {
|
} else {
|
||||||
// The transparency doesn't fit the new bit depth and is therefore unused - set it to None
|
None
|
||||||
transparency_pixel = None;
|
},
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
png.ihdr.color_type.clone()
|
||||||
|
};
|
||||||
|
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
data: reduced,
|
data: reduced,
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
bit_depth: BitDepth::from_u8(minimum_bits as u8),
|
color_type,
|
||||||
|
bit_depth: (minimum_bits as u8).try_into().unwrap(),
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers: png.aux_headers.clone(),
|
|
||||||
palette: png.palette.clone(),
|
|
||||||
transparency_pixel,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,151 +1,104 @@
|
||||||
use crate::colors::{BitDepth, ColorType};
|
use crate::colors::{BitDepth, ColorType};
|
||||||
use crate::headers::IhdrData;
|
use crate::headers::IhdrData;
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexSet;
|
||||||
use rgb::{FromSlice, RGB8, RGBA, RGBA8};
|
use rgb::alt::Gray;
|
||||||
|
use rgb::{ComponentMap, ComponentSlice, FromSlice, RGB, RGBA};
|
||||||
use rustc_hash::FxHasher;
|
use rustc_hash::FxHasher;
|
||||||
use std::hash::{BuildHasherDefault, Hash};
|
use std::hash::{BuildHasherDefault, Hash};
|
||||||
|
|
||||||
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
|
type FxIndexSet<V> = IndexSet<V, BuildHasherDefault<FxHasher>>;
|
||||||
|
|
||||||
fn reduce_scanline_to_palette<T>(
|
/// Maximum size difference between indexed and channels to consider a candidate for evaluation
|
||||||
|
pub const INDEXED_MAX_DIFF: usize = 20000;
|
||||||
|
|
||||||
|
fn build_palette<T>(
|
||||||
iter: impl IntoIterator<Item = T>,
|
iter: impl IntoIterator<Item = T>,
|
||||||
palette: &mut FxIndexMap<T, u8>,
|
|
||||||
reduced: &mut Vec<u8>,
|
reduced: &mut Vec<u8>,
|
||||||
) -> bool
|
) -> Option<FxIndexSet<T>>
|
||||||
where
|
where
|
||||||
T: Eq + Hash,
|
T: Eq + Hash,
|
||||||
{
|
{
|
||||||
|
let mut palette = FxIndexSet::default();
|
||||||
|
palette.reserve(257);
|
||||||
for pixel in iter {
|
for pixel in iter {
|
||||||
let idx = if let Some(&idx) = palette.get(&pixel) {
|
let (idx, _) = palette.insert_full(pixel);
|
||||||
idx
|
if idx == 256 {
|
||||||
} else {
|
return None;
|
||||||
let len = palette.len();
|
|
||||||
if len == 256 {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
let idx = len as u8;
|
reduced.push(idx as u8);
|
||||||
palette.insert(pixel, idx);
|
|
||||||
idx
|
|
||||||
};
|
|
||||||
reduced.push(idx);
|
|
||||||
}
|
}
|
||||||
true
|
Some(palette)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn reduce_to_palette(png: &PngImage) -> Option<PngImage> {
|
pub fn reduced_to_indexed(png: &PngImage) -> Option<PngImage> {
|
||||||
if png.ihdr.bit_depth != BitDepth::Eight {
|
if png.ihdr.bit_depth != BitDepth::Eight {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut raw_data = Vec::with_capacity(png.data.len());
|
if matches!(png.ihdr.color_type, ColorType::Indexed { .. }) {
|
||||||
let mut palette = FxIndexMap::default();
|
return None;
|
||||||
palette.reserve(257);
|
}
|
||||||
let transparency_pixel = png
|
|
||||||
.transparency_pixel
|
let mut raw_data = Vec::with_capacity(png.data.len() / png.channels_per_pixel());
|
||||||
.as_ref()
|
let palette: Vec<_> = match png.ihdr.color_type {
|
||||||
.filter(|t| png.ihdr.color_type == ColorType::RGB && t.len() >= 6)
|
ColorType::Grayscale { transparent_shade } => {
|
||||||
.map(|t| RGB8::new(t[1], t[3], t[5]));
|
let pmap = build_palette(png.data.as_gray().iter().cloned(), &mut raw_data)?;
|
||||||
let ok = if png.ihdr.color_type == ColorType::RGB {
|
// Convert the Gray16 transparency to Gray8
|
||||||
reduce_scanline_to_palette(
|
let transparency_pixel = transparent_shade.map(|t| Gray::from(t as u8));
|
||||||
png.data.as_rgb().iter().cloned().map(|px| {
|
pmap.into_iter()
|
||||||
|
.map(|px| {
|
||||||
|
RGB::from(px).alpha(if Some(px) != transparency_pixel {
|
||||||
|
255
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
ColorType::RGB { transparent_color } => {
|
||||||
|
let pmap = build_palette(png.data.as_rgb().iter().cloned(), &mut raw_data)?;
|
||||||
|
// Convert the RGB16 transparency to RGB8
|
||||||
|
let transparency_pixel = transparent_color.map(|t| t.map(|c| c as u8));
|
||||||
|
pmap.into_iter()
|
||||||
|
.map(|px| {
|
||||||
px.alpha(if Some(px) != transparency_pixel {
|
px.alpha(if Some(px) != transparency_pixel {
|
||||||
255
|
255
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
})
|
})
|
||||||
}),
|
|
||||||
&mut palette,
|
|
||||||
&mut raw_data,
|
|
||||||
)
|
|
||||||
} else if png.ihdr.color_type == ColorType::GrayscaleAlpha {
|
|
||||||
reduce_scanline_to_palette(
|
|
||||||
png.data.as_gray_alpha().iter().cloned().map(|px| RGBA {
|
|
||||||
r: px.0,
|
|
||||||
g: px.0,
|
|
||||||
b: px.0,
|
|
||||||
a: px.1,
|
|
||||||
}),
|
|
||||||
&mut palette,
|
|
||||||
&mut raw_data,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
debug_assert_eq!(png.ihdr.color_type, ColorType::RGBA);
|
|
||||||
reduce_scanline_to_palette(
|
|
||||||
png.data.as_rgba().iter().cloned(),
|
|
||||||
&mut palette,
|
|
||||||
&mut raw_data,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
if !ok {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let num_transparent = palette
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(px, &idx)| {
|
|
||||||
if px.a != 255 {
|
|
||||||
Some(idx as usize + 1)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.max();
|
.collect()
|
||||||
let trns_size = num_transparent.map_or(0, |n| n + 8);
|
|
||||||
|
|
||||||
let headers_size = palette.len() * 3 + 8 + trns_size;
|
|
||||||
if raw_data.len() + headers_size > png.data.len() {
|
|
||||||
// Reduction would result in a larger image
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
ColorType::GrayscaleAlpha => {
|
||||||
let mut aux_headers = png.aux_headers.clone();
|
let pmap = build_palette(png.data.as_gray_alpha().iter().cloned(), &mut raw_data)?;
|
||||||
if let Some(bkgd_header) = png.aux_headers.get(b"bKGD") {
|
pmap.into_iter().map(RGBA::from).collect()
|
||||||
if bkgd_header.len() != 6 {
|
|
||||||
// malformed chunk?
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
// In bKGD 16-bit values are used even for 8-bit images
|
ColorType::RGBA => {
|
||||||
let bg = RGBA8::new(bkgd_header[1], bkgd_header[3], bkgd_header[5], 255);
|
let pmap = build_palette(png.data.as_rgba().iter().cloned(), &mut raw_data)?;
|
||||||
let entry = if let Some(&entry) = palette.get(&bg) {
|
pmap.into_iter().collect()
|
||||||
entry
|
}
|
||||||
} else if palette.len() < 256 {
|
_ => return None,
|
||||||
let entry = palette.len() as u8;
|
|
||||||
palette.insert(bg, entry);
|
|
||||||
entry
|
|
||||||
} else {
|
|
||||||
return None; // No space in palette to store the bg as an index
|
|
||||||
};
|
};
|
||||||
aux_headers.insert(*b"bKGD", vec![entry]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(sbit_header) = png.aux_headers.get(b"sBIT") {
|
|
||||||
// Some programs save the sBIT header as RGB even if the image is RGBA.
|
|
||||||
aux_headers.insert(*b"sBIT", sbit_header.iter().cloned().take(3).collect());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut palette_vec = vec![RGBA8::new(0, 0, 0, 0); palette.len()];
|
|
||||||
for (color, idx) in palette {
|
|
||||||
palette_vec[idx as usize] = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
data: raw_data,
|
data: raw_data,
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
color_type: ColorType::Indexed,
|
color_type: ColorType::Indexed { palette },
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers,
|
|
||||||
transparency_pixel: None,
|
|
||||||
palette: Some(palette_vec),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn reduce_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
|
pub fn reduced_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
|
||||||
|
if !png.ihdr.color_type.is_rgb() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
let mut reduced = Vec::with_capacity(png.data.len());
|
let mut reduced = Vec::with_capacity(png.data.len());
|
||||||
let byte_depth = png.ihdr.bit_depth.as_u8() as usize >> 3;
|
let byte_depth = png.bytes_per_channel();
|
||||||
let bpp = png.channels_per_pixel() as usize * byte_depth;
|
let bpp = png.channels_per_pixel() * byte_depth;
|
||||||
let last_color = 2 * byte_depth;
|
let last_color = 2 * byte_depth;
|
||||||
for pixel in png.data.chunks(bpp) {
|
for pixel in png.data.chunks(bpp) {
|
||||||
if byte_depth == 1 {
|
if byte_depth == 1 {
|
||||||
|
|
@ -158,39 +111,75 @@ pub fn reduce_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
|
||||||
reduced.extend_from_slice(&pixel[last_color..]);
|
reduced.extend_from_slice(&pixel[last_color..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let transparency_pixel = if let Some(ref trns) = png.transparency_pixel {
|
let color_type = match png.ihdr.color_type {
|
||||||
if trns.len() != 6 || trns[0..2] != trns[2..4] || trns[2..4] != trns[4..6] {
|
ColorType::RGB { transparent_color } => ColorType::Grayscale {
|
||||||
None
|
// Copy the transparent component if it is also gray
|
||||||
} else {
|
transparent_shade: transparent_color
|
||||||
Some(trns[0..2].to_owned())
|
.filter(|t| t.r == t.g && t.g == t.b)
|
||||||
}
|
.map(|t| t.r),
|
||||||
} else {
|
},
|
||||||
png.transparency_pixel.clone()
|
_ => ColorType::GrayscaleAlpha,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut aux_headers = png.aux_headers.clone();
|
|
||||||
if let Some(sbit_header) = png.aux_headers.get(b"sBIT") {
|
|
||||||
if let Some(&byte) = sbit_header.first() {
|
|
||||||
aux_headers.insert(*b"sBIT", vec![byte]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(bkgd_header) = png.aux_headers.get(b"bKGD") {
|
|
||||||
if let Some(b) = bkgd_header.get(0..2) {
|
|
||||||
aux_headers.insert(*b"bKGD", b.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(PngImage {
|
Some(PngImage {
|
||||||
data: reduced,
|
data: reduced,
|
||||||
ihdr: IhdrData {
|
ihdr: IhdrData {
|
||||||
color_type: match png.ihdr.color_type {
|
color_type,
|
||||||
ColorType::RGBA => ColorType::GrayscaleAlpha,
|
|
||||||
_ => ColorType::Grayscale,
|
|
||||||
},
|
|
||||||
..png.ihdr
|
..png.ihdr
|
||||||
},
|
},
|
||||||
aux_headers,
|
})
|
||||||
palette: None,
|
}
|
||||||
transparency_pixel,
|
|
||||||
|
/// Attempt to convert indexed to a different color type, returning the resulting image if successful
|
||||||
|
#[must_use]
|
||||||
|
pub fn indexed_to_channels(png: &PngImage, allow_grayscale: bool) -> Option<PngImage> {
|
||||||
|
if png.ihdr.bit_depth != BitDepth::Eight {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let palette = match &png.ihdr.color_type {
|
||||||
|
ColorType::Indexed { palette } => palette,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine which channels are required
|
||||||
|
let is_gray = if allow_grayscale {
|
||||||
|
palette.iter().all(|c| c.r == c.g && c.g == c.b)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let has_alpha = palette.iter().any(|c| c.a != 255);
|
||||||
|
let color_type = match (is_gray, has_alpha) {
|
||||||
|
(false, true) => ColorType::RGBA,
|
||||||
|
(false, false) => ColorType::RGB {
|
||||||
|
transparent_color: None,
|
||||||
|
},
|
||||||
|
(true, true) => ColorType::GrayscaleAlpha,
|
||||||
|
(true, false) => ColorType::Grayscale {
|
||||||
|
transparent_shade: None,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Don't proceed if output would be too much larger
|
||||||
|
let out_size = color_type.channels_per_pixel() as usize * png.data.len();
|
||||||
|
if out_size - png.data.len() > INDEXED_MAX_DIFF {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the new data
|
||||||
|
let black = RGBA::new(0, 0, 0, 255);
|
||||||
|
let ch_start = if is_gray { 2 } else { 0 };
|
||||||
|
let ch_end = if has_alpha { 3 } else { 2 };
|
||||||
|
let mut data = Vec::with_capacity(out_size);
|
||||||
|
for b in &png.data {
|
||||||
|
let color = palette.get(*b as usize).unwrap_or(&black);
|
||||||
|
data.extend_from_slice(&color.as_slice()[ch_start..=ch_end]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(PngImage {
|
||||||
|
ihdr: IhdrData {
|
||||||
|
color_type,
|
||||||
|
..png.ihdr
|
||||||
|
},
|
||||||
|
data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,251 +1,143 @@
|
||||||
use crate::colors::{BitDepth, ColorType};
|
use crate::evaluate::Evaluator;
|
||||||
use crate::headers::IhdrData;
|
|
||||||
use crate::png::PngImage;
|
use crate::png::PngImage;
|
||||||
use indexmap::map::{Entry::*, IndexMap};
|
use crate::Deadline;
|
||||||
use rgb::RGBA8;
|
use crate::Options;
|
||||||
use std::borrow::Cow;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub mod alpha;
|
pub mod alpha;
|
||||||
use crate::alpha::reduced_alpha_channel;
|
use crate::alpha::*;
|
||||||
pub mod bit_depth;
|
pub mod bit_depth;
|
||||||
use crate::bit_depth::reduce_bit_depth_8_or_less;
|
use crate::bit_depth::*;
|
||||||
pub mod color;
|
pub mod color;
|
||||||
use crate::color::*;
|
use crate::color::*;
|
||||||
|
pub mod palette;
|
||||||
|
use crate::palette::*;
|
||||||
|
|
||||||
pub(crate) use crate::alpha::cleaned_alpha_channel;
|
pub(crate) fn perform_reductions(
|
||||||
pub(crate) use crate::bit_depth::reduce_bit_depth;
|
mut png: Arc<PngImage>,
|
||||||
|
opts: &Options,
|
||||||
|
deadline: &Deadline,
|
||||||
|
eval: &Evaluator,
|
||||||
|
) -> (Arc<PngImage>, bool) {
|
||||||
|
let mut reduction_occurred = false;
|
||||||
|
let mut evaluation_added = false;
|
||||||
|
|
||||||
/// Attempt to reduce the number of colors in the palette
|
// Interlacing must be processed first in order to evaluate the rest correctly
|
||||||
/// Returns `None` if palette hasn't changed
|
if let Some(interlacing) = opts.interlace {
|
||||||
pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage> {
|
if let Some(reduced) = png.change_interlacing(interlacing) {
|
||||||
if png.ihdr.color_type != ColorType::Indexed {
|
png = Arc::new(reduced);
|
||||||
// Can't reduce if there is no palette
|
reduction_occurred = true;
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if png.ihdr.bit_depth == BitDepth::One {
|
|
||||||
// Gains from 1-bit images will be at most 1 byte
|
|
||||||
// Not worth the CPU time
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut palette_map = [None; 256];
|
|
||||||
let mut used = [false; 256];
|
|
||||||
{
|
|
||||||
let palette = png.palette.as_ref()?;
|
|
||||||
|
|
||||||
// Find palette entries that are never used
|
|
||||||
match png.ihdr.bit_depth {
|
|
||||||
BitDepth::Eight => {
|
|
||||||
for &byte in &png.data {
|
|
||||||
used[byte as usize] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BitDepth::Four => {
|
|
||||||
for &byte in &png.data {
|
|
||||||
used[(byte & 0x0F) as usize] = true;
|
|
||||||
used[(byte >> 4) as usize] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BitDepth::Two => {
|
|
||||||
for &byte in &png.data {
|
|
||||||
used[(byte & 0x03) as usize] = true;
|
|
||||||
used[((byte >> 2) & 0x03) as usize] = true;
|
|
||||||
used[((byte >> 4) & 0x03) as usize] = true;
|
|
||||||
used[(byte >> 6) as usize] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut used_enumerated: Vec<(usize, &bool)> = used.iter().enumerate().collect();
|
|
||||||
used_enumerated.sort_by(|a, b| {
|
|
||||||
//Sort by ascending alpha and descending luma.
|
|
||||||
let color_val = |i| {
|
|
||||||
let color = palette
|
|
||||||
.get(i)
|
|
||||||
.copied()
|
|
||||||
.unwrap_or_else(|| RGBA8::new(0, 0, 0, 255));
|
|
||||||
((color.a as i32) << 18)
|
|
||||||
// These are coefficients for standard sRGB to luma conversion
|
|
||||||
- i32::from(color.r) * 299
|
|
||||||
- i32::from(color.g) * 587
|
|
||||||
- i32::from(color.b) * 114
|
|
||||||
};
|
|
||||||
color_val(a.0).cmp(&color_val(b.0))
|
|
||||||
});
|
|
||||||
|
|
||||||
// Make sure the background is also included, but only after sorting since it may not be used in idat
|
|
||||||
if let Some(&idx) = png.aux_headers.get(b"bKGD").and_then(|b| b.first()) {
|
|
||||||
if !used[idx as usize] {
|
|
||||||
used_enumerated.push((idx as usize, &true));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut next_index = 0_u16;
|
// If alpha optimization is enabled, clean the alpha channel before continuing
|
||||||
let mut seen = IndexMap::with_capacity(palette.len());
|
// This can allow some color type reductions which may not have been possible otherwise
|
||||||
for (i, used) in used_enumerated.iter().cloned() {
|
if opts.optimize_alpha && !deadline.passed() {
|
||||||
if !used {
|
if let Some(reduced) = cleaned_alpha_channel(&png) {
|
||||||
continue;
|
png = Arc::new(reduced);
|
||||||
|
// This does not count as a reduction
|
||||||
}
|
}
|
||||||
// There are invalid files that use pixel indices beyond palette size
|
|
||||||
let mut color = palette
|
|
||||||
.get(i)
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(|| RGBA8::new(0, 0, 0, 255));
|
|
||||||
// If there are multiple fully transparent entries, reduce them into one
|
|
||||||
if optimize_alpha && color.a == 0 {
|
|
||||||
color.r = 0;
|
|
||||||
color.g = 0;
|
|
||||||
color.b = 0;
|
|
||||||
}
|
}
|
||||||
match seen.entry(color) {
|
|
||||||
Vacant(new) => {
|
// Attempt to reduce 16-bit to 8-bit
|
||||||
palette_map[i] = Some(next_index as u8);
|
// This is just removal of bytes and does not need to be evaluated
|
||||||
new.insert(next_index as u8);
|
if opts.bit_depth_reduction && !deadline.passed() {
|
||||||
next_index += 1;
|
if let Some(reduced) = reduced_bit_depth_16_to_8(&png, opts.scale_16) {
|
||||||
|
png = Arc::new(reduced);
|
||||||
|
reduction_occurred = true;
|
||||||
}
|
}
|
||||||
Occupied(remap_to) => palette_map[i] = Some(*remap_to.get()),
|
}
|
||||||
|
|
||||||
|
// Attempt to reduce RGB to grayscale
|
||||||
|
// This is just removal of bytes and does not need to be evaluated
|
||||||
|
if opts.color_type_reduction && opts.grayscale_reduction && !deadline.passed() {
|
||||||
|
if let Some(reduced) = reduced_rgb_to_grayscale(&png) {
|
||||||
|
png = Arc::new(reduced);
|
||||||
|
reduction_occurred = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to reduce the palette
|
||||||
|
// This may change bytes but should always be beneficial
|
||||||
|
if opts.palette_reduction && !deadline.passed() {
|
||||||
|
if let Some(reduced) = reduced_palette(&png, opts.optimize_alpha) {
|
||||||
|
png = Arc::new(reduced);
|
||||||
|
reduction_occurred = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now retain the current png for the evaluator baseline
|
||||||
|
// It will only be entered into the evaluator if there are also others to evaluate
|
||||||
|
let mut baseline = png.clone();
|
||||||
|
|
||||||
|
// Attempt alpha removal
|
||||||
|
if opts.color_type_reduction && !deadline.passed() {
|
||||||
|
if let Some(reduced) = reduced_alpha_channel(&png, opts.optimize_alpha) {
|
||||||
|
png = Arc::new(reduced);
|
||||||
|
// For small differences, if a tRNS chunk is required then enter this into the evaluator
|
||||||
|
// Otherwise it is mostly just removal of bytes and should become the baseline
|
||||||
|
if png.ihdr.color_type.has_trns() && baseline.data.len() - png.data.len() <= 1000 {
|
||||||
|
eval.try_image(png.clone());
|
||||||
|
evaluation_added = true;
|
||||||
|
} else {
|
||||||
|
baseline = png.clone();
|
||||||
|
reduction_occurred = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
do_palette_reduction(png, &palette_map)
|
// Attempt to sort the palette
|
||||||
}
|
if opts.palette_reduction && !deadline.passed() {
|
||||||
|
if let Some(reduced) = sorted_palette(&png) {
|
||||||
#[must_use]
|
png = Arc::new(reduced);
|
||||||
fn do_palette_reduction(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Option<PngImage> {
|
eval.try_image(png.clone());
|
||||||
let byte_map = palette_map_to_byte_map(png, palette_map)?;
|
evaluation_added = true;
|
||||||
|
}
|
||||||
// Reassign data bytes to new indices
|
}
|
||||||
let raw_data = png.data.iter().map(|b| byte_map[*b as usize]).collect();
|
|
||||||
|
// Attempt to convert from indexed to channels
|
||||||
let mut aux_headers = png.aux_headers.clone();
|
// This may give a better result due to dropping the PLTE chunk
|
||||||
if let Some(bkgd_header) = png.aux_headers.get(b"bKGD") {
|
if opts.color_type_reduction && !deadline.passed() {
|
||||||
if let Some(Some(map_to)) = bkgd_header
|
if let Some(reduced) = indexed_to_channels(&png, opts.grayscale_reduction) {
|
||||||
.first()
|
// This result should not be passed on to subsequent reductions
|
||||||
.and_then(|&idx| palette_map.get(idx as usize))
|
eval.try_image(Arc::new(reduced));
|
||||||
{
|
evaluation_added = true;
|
||||||
aux_headers.insert(*b"bKGD", vec![*map_to]);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Attempt to reduce to indexed
|
||||||
Some(PngImage {
|
let mut indexed = None;
|
||||||
ihdr: IhdrData {
|
if opts.color_type_reduction && !deadline.passed() {
|
||||||
color_type: ColorType::Indexed,
|
if let Some(reduced) = reduced_to_indexed(&png) {
|
||||||
..png.ihdr
|
// Make sure the palette gets sorted (but don't bother evaluating both results)
|
||||||
},
|
let new = Arc::new(sorted_palette(&reduced).unwrap_or(reduced));
|
||||||
data: raw_data,
|
// For relatively small differences, enter this into the evaluator
|
||||||
transparency_pixel: None,
|
// Otherwise we're confident enough for it to become the baseline
|
||||||
palette: Some(reordered_palette(png.palette.as_ref()?, palette_map)),
|
if png.data.len() - new.data.len() <= INDEXED_MAX_DIFF {
|
||||||
aux_headers,
|
eval.try_image(new.clone());
|
||||||
})
|
evaluation_added = true;
|
||||||
}
|
} else {
|
||||||
|
baseline = new.clone();
|
||||||
fn palette_map_to_byte_map(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Option<[u8; 256]> {
|
reduction_occurred = true;
|
||||||
if (0..256).all(|i| palette_map[i].map_or(true, |to| to == i as u8)) {
|
}
|
||||||
// No reduction necessary
|
indexed = Some(new);
|
||||||
return None;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut byte_map = [0_u8; 256];
|
// Attempt to reduce to a lower bit depth
|
||||||
|
if opts.bit_depth_reduction && !deadline.passed() {
|
||||||
// low bit-depths can be pre-computed for every byte value
|
// Try reducing the previous png, falling back to the indexed one if it exists
|
||||||
match png.ihdr.bit_depth {
|
// This allows a grayscale depth reduction to be preferred over an indexed depth reduction
|
||||||
BitDepth::Eight => {
|
let reduced = reduced_bit_depth_8_or_less(&png, 1)
|
||||||
for byte in 0..=255usize {
|
.or_else(|| indexed.and_then(|png| reduced_bit_depth_8_or_less(&png, 1)));
|
||||||
byte_map[byte] = palette_map[byte].unwrap_or(0)
|
if let Some(reduced) = reduced {
|
||||||
}
|
eval.try_image(Arc::new(reduced));
|
||||||
}
|
evaluation_added = true;
|
||||||
BitDepth::Four => {
|
}
|
||||||
for byte in 0..=255usize {
|
}
|
||||||
byte_map[byte] = palette_map[(byte & 0x0F)].unwrap_or(0)
|
|
||||||
| (palette_map[(byte >> 4)].unwrap_or(0) << 4);
|
if evaluation_added {
|
||||||
}
|
eval.set_baseline(baseline.clone());
|
||||||
}
|
}
|
||||||
BitDepth::Two => {
|
(baseline, reduction_occurred)
|
||||||
for byte in 0..=255usize {
|
|
||||||
byte_map[byte] = palette_map[(byte & 0x03)].unwrap_or(0)
|
|
||||||
| (palette_map[((byte >> 2) & 0x03)].unwrap_or(0) << 2)
|
|
||||||
| (palette_map[((byte >> 4) & 0x03)].unwrap_or(0) << 4)
|
|
||||||
| (palette_map[(byte >> 6)].unwrap_or(0) << 6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(byte_map)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reordered_palette(palette: &[RGBA8], palette_map: &[Option<u8>; 256]) -> Vec<RGBA8> {
|
|
||||||
let max_index = palette_map.iter().cloned().flatten().max().unwrap_or(0) as usize;
|
|
||||||
let mut new_palette = vec![RGBA8::new(0, 0, 0, 255); max_index + 1];
|
|
||||||
for (&color, &map_to) in palette.iter().zip(palette_map.iter()) {
|
|
||||||
if let Some(map_to) = map_to {
|
|
||||||
new_palette[map_to as usize] = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new_palette
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempt to reduce the color type of the image
|
|
||||||
/// Returns true if the color type was reduced, false otherwise
|
|
||||||
pub fn reduce_color_type(
|
|
||||||
png: &PngImage,
|
|
||||||
grayscale_reduction: bool,
|
|
||||||
optimize_alpha: bool,
|
|
||||||
) -> Option<PngImage> {
|
|
||||||
let mut should_reduce_bit_depth = false;
|
|
||||||
let mut reduced = Cow::Borrowed(png);
|
|
||||||
|
|
||||||
// Go down one step at a time
|
|
||||||
// Maybe not the most efficient, but it's safe
|
|
||||||
if grayscale_reduction && matches!(reduced.ihdr.color_type, ColorType::RGBA | ColorType::RGB) {
|
|
||||||
if let Some(r) = reduce_rgb_to_grayscale(&reduced) {
|
|
||||||
reduced = Cow::Owned(r);
|
|
||||||
should_reduce_bit_depth = reduced.ihdr.color_type == ColorType::Grayscale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt grayscale alpha reduction before palette, as grayscale will typically be smaller than indexed
|
|
||||||
if reduced.ihdr.color_type == ColorType::GrayscaleAlpha {
|
|
||||||
if let Some(r) = reduced_alpha_channel(&reduced, optimize_alpha) {
|
|
||||||
reduced = Cow::Owned(r);
|
|
||||||
should_reduce_bit_depth = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
reduced.ihdr.color_type,
|
|
||||||
ColorType::RGBA | ColorType::RGB | ColorType::GrayscaleAlpha
|
|
||||||
) {
|
|
||||||
if let Some(r) = reduce_to_palette(&reduced) {
|
|
||||||
reduced = Cow::Owned(r);
|
|
||||||
should_reduce_bit_depth = true;
|
|
||||||
|
|
||||||
// Make sure that palette gets sorted. Ideally, this should be done within reduce_to_palette.
|
|
||||||
if let Some(r) = reduced_palette(&reduced, optimize_alpha) {
|
|
||||||
reduced = Cow::Owned(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt RGBA alpha reduction after palette, so it can be skipped if palette was successful
|
|
||||||
if reduced.ihdr.color_type == ColorType::RGBA {
|
|
||||||
if let Some(r) = reduced_alpha_channel(&reduced, optimize_alpha) {
|
|
||||||
reduced = Cow::Owned(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if should_reduce_bit_depth {
|
|
||||||
// Some conversions will allow us to perform bit depth reduction that
|
|
||||||
// wasn't possible before
|
|
||||||
if let Some(r) = reduce_bit_depth_8_or_less(&reduced, 1) {
|
|
||||||
reduced = Cow::Owned(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match reduced {
|
|
||||||
Cow::Owned(r) => Some(r),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
172
src/reduction/palette.rs
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
use crate::colors::{BitDepth, ColorType};
|
||||||
|
use crate::headers::IhdrData;
|
||||||
|
use crate::png::PngImage;
|
||||||
|
use indexmap::IndexSet;
|
||||||
|
use rgb::RGBA8;
|
||||||
|
|
||||||
|
/// Attempt to reduce the number of colors in the palette, returning the reduced image if successful
|
||||||
|
#[must_use]
|
||||||
|
pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage> {
|
||||||
|
let palette = match &png.ihdr.color_type {
|
||||||
|
ColorType::Indexed { palette } if palette.len() > 1 => palette,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let used = get_used_entries(png);
|
||||||
|
|
||||||
|
let black = RGBA8::new(0, 0, 0, 255);
|
||||||
|
let mut condensed = IndexSet::with_capacity(palette.len());
|
||||||
|
let mut palette_map = [0; 256];
|
||||||
|
let mut did_change = false;
|
||||||
|
for (i, used) in used.iter().enumerate() {
|
||||||
|
if !used {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// There are invalid files that use pixel indices beyond palette size
|
||||||
|
let color = *palette.get(i).unwrap_or(&black);
|
||||||
|
palette_map[i] = add_color_to_set(color, &mut condensed, optimize_alpha);
|
||||||
|
if palette_map[i] as usize != i {
|
||||||
|
did_change = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = if did_change {
|
||||||
|
// Reassign data bytes to new indices
|
||||||
|
let byte_map = palette_map_to_byte_map(png.ihdr.bit_depth, &palette_map);
|
||||||
|
png.data.iter().map(|b| byte_map[*b as usize]).collect()
|
||||||
|
} else if condensed.len() < palette.len() {
|
||||||
|
// Data is unchanged but palette will be truncated
|
||||||
|
png.data.clone()
|
||||||
|
} else {
|
||||||
|
// Nothing has changed
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let palette: Vec<_> = condensed.into_iter().collect();
|
||||||
|
|
||||||
|
Some(PngImage {
|
||||||
|
ihdr: IhdrData {
|
||||||
|
color_type: ColorType::Indexed { palette },
|
||||||
|
..png.ihdr
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_color_to_set(mut color: RGBA8, set: &mut IndexSet<RGBA8>, optimize_alpha: bool) -> u8 {
|
||||||
|
// If there are multiple fully transparent entries, reduce them into one
|
||||||
|
if optimize_alpha && color.a == 0 {
|
||||||
|
color.r = 0;
|
||||||
|
color.g = 0;
|
||||||
|
color.b = 0;
|
||||||
|
}
|
||||||
|
let (idx, _) = set.insert_full(color);
|
||||||
|
idx as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_used_entries(png: &PngImage) -> [bool; 256] {
|
||||||
|
let mut used = [false; 256];
|
||||||
|
match png.ihdr.bit_depth {
|
||||||
|
BitDepth::Eight => {
|
||||||
|
for &byte in &png.data {
|
||||||
|
used[byte as usize] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BitDepth::Four => {
|
||||||
|
for &byte in &png.data {
|
||||||
|
used[(byte & 0x0F) as usize] = true;
|
||||||
|
used[(byte >> 4) as usize] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BitDepth::Two => {
|
||||||
|
for &byte in &png.data {
|
||||||
|
used[(byte & 0x03) as usize] = true;
|
||||||
|
used[((byte >> 2) & 0x03) as usize] = true;
|
||||||
|
used[((byte >> 4) & 0x03) as usize] = true;
|
||||||
|
used[(byte >> 6) as usize] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BitDepth::One => {
|
||||||
|
// Only two options, don't bother checking which are actually used
|
||||||
|
used[0] = true;
|
||||||
|
used[1] = true;
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
used
|
||||||
|
}
|
||||||
|
|
||||||
|
fn palette_map_to_byte_map(bit_depth: BitDepth, palette_map: &[u8; 256]) -> [u8; 256] {
|
||||||
|
// Low bit-depths can be pre-computed for every byte value
|
||||||
|
match bit_depth {
|
||||||
|
BitDepth::Eight => *palette_map,
|
||||||
|
BitDepth::Four => {
|
||||||
|
let mut byte_map = [0_u8; 256];
|
||||||
|
for byte in 0..256 {
|
||||||
|
byte_map[byte] = palette_map[byte & 0x0F] | (palette_map[byte >> 4] << 4);
|
||||||
|
}
|
||||||
|
byte_map
|
||||||
|
}
|
||||||
|
BitDepth::Two => {
|
||||||
|
let mut byte_map = [0_u8; 256];
|
||||||
|
for byte in 0..256 {
|
||||||
|
byte_map[byte] = palette_map[byte & 0x03]
|
||||||
|
| (palette_map[(byte >> 2) & 0x03] << 2)
|
||||||
|
| (palette_map[(byte >> 4) & 0x03] << 4)
|
||||||
|
| (palette_map[byte >> 6] << 6);
|
||||||
|
}
|
||||||
|
byte_map
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to sort the colors in the palette, returning the sorted image if successful
|
||||||
|
#[must_use]
|
||||||
|
pub fn sorted_palette(png: &PngImage) -> Option<PngImage> {
|
||||||
|
if png.ihdr.bit_depth == BitDepth::One {
|
||||||
|
// Don't bother trying to sort a 1-bit image
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let palette = match &png.ihdr.color_type {
|
||||||
|
ColorType::Indexed { palette } => palette,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut enumerated: Vec<_> = palette.iter().enumerate().collect();
|
||||||
|
|
||||||
|
// Sort the palette
|
||||||
|
enumerated.sort_by(|a, b| {
|
||||||
|
// Sort by ascending alpha and descending luma
|
||||||
|
let color_val = |color: &RGBA8| {
|
||||||
|
((color.a as i32) << 18)
|
||||||
|
// These are coefficients for standard sRGB to luma conversion
|
||||||
|
- i32::from(color.r) * 299
|
||||||
|
- i32::from(color.g) * 587
|
||||||
|
- i32::from(color.b) * 114
|
||||||
|
};
|
||||||
|
color_val(a.1).cmp(&color_val(b.1))
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract the new palette and determine if anything changed
|
||||||
|
let (old_map, palette): (Vec<_>, Vec<RGBA8>) = enumerated.into_iter().unzip();
|
||||||
|
if old_map.iter().enumerate().all(|(a, b)| a == *b) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the palette and byte maps and convert the data
|
||||||
|
let mut new_map = [0; 256];
|
||||||
|
for (i, &v) in old_map.iter().enumerate() {
|
||||||
|
new_map[v] = i as u8;
|
||||||
|
}
|
||||||
|
let byte_map = palette_map_to_byte_map(png.ihdr.bit_depth, &new_map);
|
||||||
|
let data = png.data.iter().map(|&b| byte_map[b as usize]).collect();
|
||||||
|
|
||||||
|
Some(PngImage {
|
||||||
|
ihdr: IhdrData {
|
||||||
|
color_type: ColorType::Indexed { palette },
|
||||||
|
..png.ihdr
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
47
src/sanity_checks.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
use image::{DynamicImage, GenericImageView, ImageFormat, Pixel};
|
||||||
|
use log::{error, warn};
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
/// Validate that the output png data still matches the original image
|
||||||
|
pub fn validate_output(output: &[u8], original_data: &[u8]) -> bool {
|
||||||
|
let (old_png, new_png) = rayon::join(
|
||||||
|
|| load_png_image_from_memory(original_data),
|
||||||
|
|| load_png_image_from_memory(output),
|
||||||
|
);
|
||||||
|
|
||||||
|
match (new_png, old_png) {
|
||||||
|
(Err(new_err), _) => {
|
||||||
|
error!("Failed to read output image for validation: {}", new_err);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
(_, Err(old_err)) => {
|
||||||
|
// The original image might be invalid if, for example, there is a CRC error,
|
||||||
|
// and we set fix_errors to true. In that case, all we can do is check that the
|
||||||
|
// new image is decodable.
|
||||||
|
warn!("Failed to read input image for validation: {}", old_err);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
(Ok(new_png), Ok(old_png)) => images_equal(&old_png, &new_png),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads a PNG image from memory to a [DynamicImage]
|
||||||
|
fn load_png_image_from_memory(png_data: &[u8]) -> Result<DynamicImage, image::ImageError> {
|
||||||
|
let mut reader = image::io::Reader::new(Cursor::new(png_data));
|
||||||
|
reader.set_format(ImageFormat::Png);
|
||||||
|
reader.no_limits();
|
||||||
|
reader.decode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares images pixel by pixel for equivalent content
|
||||||
|
fn images_equal(old_png: &DynamicImage, new_png: &DynamicImage) -> bool {
|
||||||
|
let a = old_png.pixels().filter(|x| {
|
||||||
|
let p = x.2.channels();
|
||||||
|
!(p.len() == 4 && p[3] == 0)
|
||||||
|
});
|
||||||
|
let b = new_png.pixels().filter(|x| {
|
||||||
|
let p = x.2.channels();
|
||||||
|
!(p.len() == 4 && p[3] == 0)
|
||||||
|
});
|
||||||
|
a.eq(b)
|
||||||
|
}
|
||||||
BIN
tests/files/grayscale_16_should_be_grayscale_1.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 66 KiB |
BIN
tests/files/grayscale_8_should_be_palette_8.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
tests/files/palette_8_should_be_rgb.png
Normal file
|
After Width: | Height: | Size: 195 B |
BIN
tests/files/palette_8_should_be_rgba.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
tests/files/raw_api.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
292
tests/filters.rs
|
|
@ -1,10 +1,16 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use oxipng::{internal_tests::*, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::*;
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
const GRAYSCALE: u8 = 0;
|
||||||
|
const RGB: u8 = 2;
|
||||||
|
const INDEXED: u8 = 3;
|
||||||
|
const GRAYSCALE_ALPHA: u8 = 4;
|
||||||
|
const RGBA: u8 = 6;
|
||||||
|
|
||||||
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
let mut options = oxipng::Options {
|
let mut options = oxipng::Options {
|
||||||
force: true,
|
force: true,
|
||||||
|
|
@ -23,18 +29,18 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
fn test_it_converts(
|
fn test_it_converts(
|
||||||
input: &str,
|
input: &str,
|
||||||
filter: RowFilter,
|
filter: RowFilter,
|
||||||
color_type_in: ColorType,
|
color_type_in: u8,
|
||||||
bit_depth_in: BitDepth,
|
bit_depth_in: BitDepth,
|
||||||
color_type_out: ColorType,
|
color_type_out: u8,
|
||||||
bit_depth_out: BitDepth,
|
bit_depth_out: BitDepth,
|
||||||
) {
|
) {
|
||||||
let input = PathBuf::from(input);
|
let input = PathBuf::from(input);
|
||||||
|
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
opts.filter = IndexSet::new();
|
opts.filter = IndexSet::new();
|
||||||
opts.filter.insert(filter);
|
opts.filter.insert(filter);
|
||||||
assert_eq!(png.raw.ihdr.color_type, color_type_in);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
||||||
|
|
||||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||||
|
|
@ -44,7 +50,7 @@ fn test_it_converts(
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -52,12 +58,10 @@ fn test_it_converts(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type, color_type_out);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_out);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
|
||||||
if let Some(palette) = png.raw.palette.as_ref() {
|
if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
|
||||||
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth.as_u8() as usize));
|
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth as u8));
|
||||||
} else {
|
|
||||||
assert_ne!(png.raw.ihdr.color_type, ColorType::Indexed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -68,9 +72,9 @@ fn filter_0_for_rgba_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_rgba_16.png",
|
"tests/files/filter_0_for_rgba_16.png",
|
||||||
RowFilter::None,
|
RowFilter::None,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -80,9 +84,9 @@ fn filter_1_for_rgba_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_rgba_16.png",
|
"tests/files/filter_1_for_rgba_16.png",
|
||||||
RowFilter::Sub,
|
RowFilter::Sub,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -92,9 +96,9 @@ fn filter_2_for_rgba_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_rgba_16.png",
|
"tests/files/filter_2_for_rgba_16.png",
|
||||||
RowFilter::Up,
|
RowFilter::Up,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -104,9 +108,9 @@ fn filter_3_for_rgba_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_rgba_16.png",
|
"tests/files/filter_3_for_rgba_16.png",
|
||||||
RowFilter::Average,
|
RowFilter::Average,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -116,9 +120,9 @@ fn filter_4_for_rgba_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_rgba_16.png",
|
"tests/files/filter_4_for_rgba_16.png",
|
||||||
RowFilter::Paeth,
|
RowFilter::Paeth,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -128,9 +132,9 @@ fn filter_5_for_rgba_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_rgba_16.png",
|
"tests/files/filter_5_for_rgba_16.png",
|
||||||
RowFilter::MinSum,
|
RowFilter::MinSum,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -140,9 +144,9 @@ fn filter_0_for_rgba_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_rgba_8.png",
|
"tests/files/filter_0_for_rgba_8.png",
|
||||||
RowFilter::None,
|
RowFilter::None,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -152,9 +156,9 @@ fn filter_1_for_rgba_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_rgba_8.png",
|
"tests/files/filter_1_for_rgba_8.png",
|
||||||
RowFilter::Sub,
|
RowFilter::Sub,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -164,9 +168,9 @@ fn filter_2_for_rgba_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_rgba_8.png",
|
"tests/files/filter_2_for_rgba_8.png",
|
||||||
RowFilter::Up,
|
RowFilter::Up,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -176,9 +180,9 @@ fn filter_3_for_rgba_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_rgba_8.png",
|
"tests/files/filter_3_for_rgba_8.png",
|
||||||
RowFilter::Average,
|
RowFilter::Average,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -188,9 +192,9 @@ fn filter_4_for_rgba_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_rgba_8.png",
|
"tests/files/filter_4_for_rgba_8.png",
|
||||||
RowFilter::Paeth,
|
RowFilter::Paeth,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -200,9 +204,9 @@ fn filter_5_for_rgba_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_rgba_8.png",
|
"tests/files/filter_5_for_rgba_8.png",
|
||||||
RowFilter::MinSum,
|
RowFilter::MinSum,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -212,9 +216,9 @@ fn filter_0_for_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_rgb_16.png",
|
"tests/files/filter_0_for_rgb_16.png",
|
||||||
RowFilter::None,
|
RowFilter::None,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -224,9 +228,9 @@ fn filter_1_for_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_rgb_16.png",
|
"tests/files/filter_1_for_rgb_16.png",
|
||||||
RowFilter::Sub,
|
RowFilter::Sub,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -236,9 +240,9 @@ fn filter_2_for_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_rgb_16.png",
|
"tests/files/filter_2_for_rgb_16.png",
|
||||||
RowFilter::Up,
|
RowFilter::Up,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -248,9 +252,9 @@ fn filter_3_for_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_rgb_16.png",
|
"tests/files/filter_3_for_rgb_16.png",
|
||||||
RowFilter::Average,
|
RowFilter::Average,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -260,9 +264,9 @@ fn filter_4_for_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_rgb_16.png",
|
"tests/files/filter_4_for_rgb_16.png",
|
||||||
RowFilter::Paeth,
|
RowFilter::Paeth,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -272,9 +276,9 @@ fn filter_5_for_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_rgb_16.png",
|
"tests/files/filter_5_for_rgb_16.png",
|
||||||
RowFilter::MinSum,
|
RowFilter::MinSum,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -284,9 +288,9 @@ fn filter_0_for_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_rgb_8.png",
|
"tests/files/filter_0_for_rgb_8.png",
|
||||||
RowFilter::None,
|
RowFilter::None,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -296,9 +300,9 @@ fn filter_1_for_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_rgb_8.png",
|
"tests/files/filter_1_for_rgb_8.png",
|
||||||
RowFilter::Sub,
|
RowFilter::Sub,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -308,9 +312,9 @@ fn filter_2_for_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_rgb_8.png",
|
"tests/files/filter_2_for_rgb_8.png",
|
||||||
RowFilter::Up,
|
RowFilter::Up,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -320,9 +324,9 @@ fn filter_3_for_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_rgb_8.png",
|
"tests/files/filter_3_for_rgb_8.png",
|
||||||
RowFilter::Average,
|
RowFilter::Average,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -332,9 +336,9 @@ fn filter_4_for_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_rgb_8.png",
|
"tests/files/filter_4_for_rgb_8.png",
|
||||||
RowFilter::Paeth,
|
RowFilter::Paeth,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -344,9 +348,9 @@ fn filter_5_for_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_rgb_8.png",
|
"tests/files/filter_5_for_rgb_8.png",
|
||||||
RowFilter::MinSum,
|
RowFilter::MinSum,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -356,9 +360,9 @@ fn filter_0_for_grayscale_alpha_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_grayscale_alpha_16.png",
|
"tests/files/filter_0_for_grayscale_alpha_16.png",
|
||||||
RowFilter::None,
|
RowFilter::None,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -368,9 +372,9 @@ fn filter_1_for_grayscale_alpha_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_grayscale_alpha_16.png",
|
"tests/files/filter_1_for_grayscale_alpha_16.png",
|
||||||
RowFilter::Sub,
|
RowFilter::Sub,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -380,9 +384,9 @@ fn filter_2_for_grayscale_alpha_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_grayscale_alpha_16.png",
|
"tests/files/filter_2_for_grayscale_alpha_16.png",
|
||||||
RowFilter::Up,
|
RowFilter::Up,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -392,9 +396,9 @@ fn filter_3_for_grayscale_alpha_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_grayscale_alpha_16.png",
|
"tests/files/filter_3_for_grayscale_alpha_16.png",
|
||||||
RowFilter::Average,
|
RowFilter::Average,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -404,9 +408,9 @@ fn filter_4_for_grayscale_alpha_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_grayscale_alpha_16.png",
|
"tests/files/filter_4_for_grayscale_alpha_16.png",
|
||||||
RowFilter::Paeth,
|
RowFilter::Paeth,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -416,9 +420,9 @@ fn filter_5_for_grayscale_alpha_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_grayscale_alpha_16.png",
|
"tests/files/filter_5_for_grayscale_alpha_16.png",
|
||||||
RowFilter::MinSum,
|
RowFilter::MinSum,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -428,9 +432,9 @@ fn filter_0_for_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_grayscale_alpha_8.png",
|
"tests/files/filter_0_for_grayscale_alpha_8.png",
|
||||||
RowFilter::None,
|
RowFilter::None,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -440,9 +444,9 @@ fn filter_1_for_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_grayscale_alpha_8.png",
|
"tests/files/filter_1_for_grayscale_alpha_8.png",
|
||||||
RowFilter::Sub,
|
RowFilter::Sub,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -452,9 +456,9 @@ fn filter_2_for_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_grayscale_alpha_8.png",
|
"tests/files/filter_2_for_grayscale_alpha_8.png",
|
||||||
RowFilter::Up,
|
RowFilter::Up,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -464,9 +468,9 @@ fn filter_3_for_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_grayscale_alpha_8.png",
|
"tests/files/filter_3_for_grayscale_alpha_8.png",
|
||||||
RowFilter::Average,
|
RowFilter::Average,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -476,9 +480,9 @@ fn filter_4_for_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_grayscale_alpha_8.png",
|
"tests/files/filter_4_for_grayscale_alpha_8.png",
|
||||||
RowFilter::Paeth,
|
RowFilter::Paeth,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -488,9 +492,9 @@ fn filter_5_for_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_grayscale_alpha_8.png",
|
"tests/files/filter_5_for_grayscale_alpha_8.png",
|
||||||
RowFilter::MinSum,
|
RowFilter::MinSum,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -500,9 +504,9 @@ fn filter_0_for_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_grayscale_16.png",
|
"tests/files/filter_0_for_grayscale_16.png",
|
||||||
RowFilter::None,
|
RowFilter::None,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -512,9 +516,9 @@ fn filter_1_for_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_grayscale_16.png",
|
"tests/files/filter_1_for_grayscale_16.png",
|
||||||
RowFilter::Sub,
|
RowFilter::Sub,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -524,9 +528,9 @@ fn filter_2_for_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_grayscale_16.png",
|
"tests/files/filter_2_for_grayscale_16.png",
|
||||||
RowFilter::Up,
|
RowFilter::Up,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -536,9 +540,9 @@ fn filter_3_for_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_grayscale_16.png",
|
"tests/files/filter_3_for_grayscale_16.png",
|
||||||
RowFilter::Average,
|
RowFilter::Average,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -548,9 +552,9 @@ fn filter_4_for_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_grayscale_16.png",
|
"tests/files/filter_4_for_grayscale_16.png",
|
||||||
RowFilter::Paeth,
|
RowFilter::Paeth,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -560,9 +564,9 @@ fn filter_5_for_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_grayscale_16.png",
|
"tests/files/filter_5_for_grayscale_16.png",
|
||||||
RowFilter::MinSum,
|
RowFilter::MinSum,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -572,9 +576,9 @@ fn filter_0_for_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_grayscale_8.png",
|
"tests/files/filter_0_for_grayscale_8.png",
|
||||||
RowFilter::None,
|
RowFilter::None,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -584,9 +588,9 @@ fn filter_1_for_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_grayscale_8.png",
|
"tests/files/filter_1_for_grayscale_8.png",
|
||||||
RowFilter::Sub,
|
RowFilter::Sub,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -596,9 +600,9 @@ fn filter_2_for_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_grayscale_8.png",
|
"tests/files/filter_2_for_grayscale_8.png",
|
||||||
RowFilter::Up,
|
RowFilter::Up,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -608,9 +612,9 @@ fn filter_3_for_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_grayscale_8.png",
|
"tests/files/filter_3_for_grayscale_8.png",
|
||||||
RowFilter::Average,
|
RowFilter::Average,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -620,9 +624,9 @@ fn filter_4_for_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_grayscale_8.png",
|
"tests/files/filter_4_for_grayscale_8.png",
|
||||||
RowFilter::Paeth,
|
RowFilter::Paeth,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -632,9 +636,9 @@ fn filter_5_for_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_grayscale_8.png",
|
"tests/files/filter_5_for_grayscale_8.png",
|
||||||
RowFilter::MinSum,
|
RowFilter::MinSum,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -644,9 +648,9 @@ fn filter_0_for_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_palette_4.png",
|
"tests/files/filter_0_for_palette_4.png",
|
||||||
RowFilter::None,
|
RowFilter::None,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -656,9 +660,9 @@ fn filter_1_for_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_palette_4.png",
|
"tests/files/filter_1_for_palette_4.png",
|
||||||
RowFilter::Sub,
|
RowFilter::Sub,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -668,9 +672,9 @@ fn filter_2_for_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_palette_4.png",
|
"tests/files/filter_2_for_palette_4.png",
|
||||||
RowFilter::Up,
|
RowFilter::Up,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -680,9 +684,9 @@ fn filter_3_for_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_palette_4.png",
|
"tests/files/filter_3_for_palette_4.png",
|
||||||
RowFilter::Average,
|
RowFilter::Average,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -692,9 +696,9 @@ fn filter_4_for_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_palette_4.png",
|
"tests/files/filter_4_for_palette_4.png",
|
||||||
RowFilter::Paeth,
|
RowFilter::Paeth,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -704,9 +708,9 @@ fn filter_5_for_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_palette_4.png",
|
"tests/files/filter_5_for_palette_4.png",
|
||||||
RowFilter::MinSum,
|
RowFilter::MinSum,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -716,9 +720,9 @@ fn filter_0_for_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_palette_2.png",
|
"tests/files/filter_0_for_palette_2.png",
|
||||||
RowFilter::None,
|
RowFilter::None,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -728,9 +732,9 @@ fn filter_1_for_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_palette_2.png",
|
"tests/files/filter_1_for_palette_2.png",
|
||||||
RowFilter::Sub,
|
RowFilter::Sub,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -740,9 +744,9 @@ fn filter_2_for_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_palette_2.png",
|
"tests/files/filter_2_for_palette_2.png",
|
||||||
RowFilter::Up,
|
RowFilter::Up,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -752,9 +756,9 @@ fn filter_3_for_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_palette_2.png",
|
"tests/files/filter_3_for_palette_2.png",
|
||||||
RowFilter::Average,
|
RowFilter::Average,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -764,9 +768,9 @@ fn filter_4_for_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_palette_2.png",
|
"tests/files/filter_4_for_palette_2.png",
|
||||||
RowFilter::Paeth,
|
RowFilter::Paeth,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -776,9 +780,9 @@ fn filter_5_for_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_palette_2.png",
|
"tests/files/filter_5_for_palette_2.png",
|
||||||
RowFilter::MinSum,
|
RowFilter::MinSum,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -788,9 +792,9 @@ fn filter_0_for_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_0_for_palette_1.png",
|
"tests/files/filter_0_for_palette_1.png",
|
||||||
RowFilter::None,
|
RowFilter::None,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -800,9 +804,9 @@ fn filter_1_for_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_1_for_palette_1.png",
|
"tests/files/filter_1_for_palette_1.png",
|
||||||
RowFilter::Sub,
|
RowFilter::Sub,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -812,9 +816,9 @@ fn filter_2_for_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_2_for_palette_1.png",
|
"tests/files/filter_2_for_palette_1.png",
|
||||||
RowFilter::Up,
|
RowFilter::Up,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -824,9 +828,9 @@ fn filter_3_for_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_3_for_palette_1.png",
|
"tests/files/filter_3_for_palette_1.png",
|
||||||
RowFilter::Average,
|
RowFilter::Average,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -836,9 +840,9 @@ fn filter_4_for_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_4_for_palette_1.png",
|
"tests/files/filter_4_for_palette_1.png",
|
||||||
RowFilter::Paeth,
|
RowFilter::Paeth,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -848,9 +852,9 @@ fn filter_5_for_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/filter_5_for_palette_1.png",
|
"tests/files/filter_5_for_palette_1.png",
|
||||||
RowFilter::MinSum,
|
RowFilter::MinSum,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
216
tests/flags.rs
|
|
@ -1,6 +1,6 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::{indexset, IndexSet};
|
||||||
use oxipng::{internal_tests::*, Interlacing, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::*;
|
||||||
#[cfg(feature = "filetime")]
|
#[cfg(feature = "filetime")]
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
|
|
@ -11,6 +11,11 @@ use std::ops::Deref;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
const GRAYSCALE: u8 = 0;
|
||||||
|
const RGB: u8 = 2;
|
||||||
|
const INDEXED: u8 = 3;
|
||||||
|
const RGBA: u8 = 6;
|
||||||
|
|
||||||
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
let mut options = oxipng::Options {
|
let mut options = oxipng::Options {
|
||||||
force: true,
|
force: true,
|
||||||
|
|
@ -27,37 +32,38 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add callback to allow checks before the output file is deleted again
|
/// Add callback to allow checks before the output file is deleted again
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn test_it_converts_callbacks<CBPRE, CBPOST>(
|
fn test_it_converts_callbacks<CBPRE, CBPOST>(
|
||||||
input: PathBuf,
|
input: PathBuf,
|
||||||
output: &OutFile,
|
output: &OutFile,
|
||||||
opts: &oxipng::Options,
|
opts: &oxipng::Options,
|
||||||
color_type_in: ColorType,
|
color_type_in: u8,
|
||||||
bit_depth_in: BitDepth,
|
bit_depth_in: BitDepth,
|
||||||
color_type_out: ColorType,
|
color_type_out: u8,
|
||||||
bit_depth_out: BitDepth,
|
bit_depth_out: BitDepth,
|
||||||
mut callback_pre: CBPRE,
|
mut callback_pre: CBPRE,
|
||||||
mut callback_post: CBPOST,
|
mut callback_post: CBPOST,
|
||||||
) where
|
) where
|
||||||
CBPOST: FnMut(&Path) -> (),
|
CBPOST: FnMut(&Path),
|
||||||
CBPRE: FnMut(&Path) -> (),
|
CBPRE: FnMut(&Path),
|
||||||
{
|
{
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type, color_type_in);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
||||||
|
|
||||||
callback_pre(&input);
|
callback_pre(&input);
|
||||||
|
|
||||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
match oxipng::optimize(&InFile::Path(input), output, opts) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(x) => panic!("{}", x),
|
Err(x) => panic!("{}", x),
|
||||||
};
|
};
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
callback_post(&output);
|
callback_post(output);
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -65,7 +71,7 @@ fn test_it_converts_callbacks<CBPRE, CBPOST>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type, color_type_out);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_out);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -76,9 +82,9 @@ fn test_it_converts(
|
||||||
input: PathBuf,
|
input: PathBuf,
|
||||||
output: &OutFile,
|
output: &OutFile,
|
||||||
opts: &oxipng::Options,
|
opts: &oxipng::Options,
|
||||||
color_type_in: ColorType,
|
color_type_in: u8,
|
||||||
bit_depth_in: BitDepth,
|
bit_depth_in: BitDepth,
|
||||||
color_type_out: ColorType,
|
color_type_out: u8,
|
||||||
bit_depth_out: BitDepth,
|
bit_depth_out: BitDepth,
|
||||||
) {
|
) {
|
||||||
test_it_converts_callbacks(
|
test_it_converts_callbacks(
|
||||||
|
|
@ -152,9 +158,9 @@ fn verbose_mode() {
|
||||||
input,
|
input,
|
||||||
&output,
|
&output,
|
||||||
&opts,
|
&opts,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -174,48 +180,47 @@ fn verbose_mode() {
|
||||||
});
|
});
|
||||||
|
|
||||||
let logs: Vec<_> = receiver.into_iter().collect();
|
let logs: Vec<_> = receiver.into_iter().collect();
|
||||||
println!("logs={:?}", logs);
|
let expected_prefixes = [
|
||||||
assert_eq!(logs.len(), 9);
|
|
||||||
let expected_logs = [
|
|
||||||
" 500x400 pixels, PNG format",
|
" 500x400 pixels, PNG format",
|
||||||
" 3x8 bits/pixel, RGB (non-interlaced)",
|
" 8-bit RGB, non-interlaced",
|
||||||
" IDAT size = 113794 bytes",
|
" IDAT size = 113794 bytes",
|
||||||
" File size = 114708 bytes",
|
" File size = 114708 bytes",
|
||||||
"Trying: 1 filters",
|
"Trying: 1 filters",
|
||||||
" zc = 11 f = None 149409 bytes",
|
|
||||||
"Found better combination:",
|
"Found better combination:",
|
||||||
" zc = 11 f = None 149409 bytes",
|
" zc = 11 f = None ",
|
||||||
" IDAT size = 149409 bytes",
|
" IDAT size = ",
|
||||||
];
|
];
|
||||||
for (idx, expected_log) in expected_logs.into_iter().enumerate() {
|
assert_eq!(logs.len(), expected_prefixes.len());
|
||||||
if let Some(log) = logs.get(idx) {
|
for (i, log) in logs.into_iter().enumerate() {
|
||||||
if !log.starts_with(expected_log) {
|
let expected_prefix = expected_prefixes[i];
|
||||||
panic!(
|
assert!(
|
||||||
|
log.starts_with(&expected_prefix),
|
||||||
"logs[{}] = {:?} doesn't start with {:?}",
|
"logs[{}] = {:?} doesn't start with {:?}",
|
||||||
idx, log, expected_log
|
i,
|
||||||
|
log,
|
||||||
|
expected_prefix
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
panic!(
|
|
||||||
"Expected to find {} log entries, but got {}",
|
fn count_chunk(png: &PngData, name: &[u8; 4]) -> usize {
|
||||||
expected_logs.len(),
|
png.aux_chunks
|
||||||
logs.len()
|
.iter()
|
||||||
);
|
.filter(|chunk| &chunk.name == name)
|
||||||
}
|
.count()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn strip_headers_list() {
|
fn strip_headers_list() {
|
||||||
let input = PathBuf::from("tests/files/strip_headers_list.png");
|
let input = PathBuf::from("tests/files/strip_headers_list.png");
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.strip = Headers::Strip(vec!["iCCP".to_owned(), "tEXt".to_owned()]);
|
opts.strip = StripChunks::Strip(indexset![*b"iCCP", *b"tEXt"]);
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
assert!(png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 3);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 1);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iCCP"));
|
assert_eq!(count_chunk(&png, b"iCCP"), 1);
|
||||||
|
|
||||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
|
|
@ -224,7 +229,7 @@ fn strip_headers_list() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -232,9 +237,9 @@ fn strip_headers_list() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(!png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 0);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 1);
|
||||||
assert!(!png.raw.aux_headers.contains_key(b"iCCP"));
|
assert_eq!(count_chunk(&png, b"iCCP"), 0);
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
}
|
}
|
||||||
|
|
@ -243,13 +248,13 @@ fn strip_headers_list() {
|
||||||
fn strip_headers_safe() {
|
fn strip_headers_safe() {
|
||||||
let input = PathBuf::from("tests/files/strip_headers_safe.png");
|
let input = PathBuf::from("tests/files/strip_headers_safe.png");
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.strip = Headers::Safe;
|
opts.strip = StripChunks::Safe;
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
assert!(png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 3);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 1);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iCCP"));
|
assert_eq!(count_chunk(&png, b"iCCP"), 1);
|
||||||
|
|
||||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
|
|
@ -258,7 +263,7 @@ fn strip_headers_safe() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -266,9 +271,9 @@ fn strip_headers_safe() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(!png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 0);
|
||||||
assert!(!png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 0);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"sRGB"));
|
assert_eq!(count_chunk(&png, b"sRGB"), 1);
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
}
|
}
|
||||||
|
|
@ -277,13 +282,13 @@ fn strip_headers_safe() {
|
||||||
fn strip_headers_all() {
|
fn strip_headers_all() {
|
||||||
let input = PathBuf::from("tests/files/strip_headers_all.png");
|
let input = PathBuf::from("tests/files/strip_headers_all.png");
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.strip = Headers::All;
|
opts.strip = StripChunks::All;
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
assert!(png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 3);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 1);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iCCP"));
|
assert_eq!(count_chunk(&png, b"iCCP"), 1);
|
||||||
|
|
||||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
|
|
@ -292,7 +297,7 @@ fn strip_headers_all() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -300,9 +305,9 @@ fn strip_headers_all() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(!png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 0);
|
||||||
assert!(!png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 0);
|
||||||
assert!(!png.raw.aux_headers.contains_key(b"iCCP"));
|
assert_eq!(count_chunk(&png, b"iCCP"), 0);
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
}
|
}
|
||||||
|
|
@ -311,13 +316,13 @@ fn strip_headers_all() {
|
||||||
fn strip_headers_none() {
|
fn strip_headers_none() {
|
||||||
let input = PathBuf::from("tests/files/strip_headers_none.png");
|
let input = PathBuf::from("tests/files/strip_headers_none.png");
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.strip = Headers::None;
|
opts.strip = StripChunks::None;
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &Options::default()).unwrap();
|
||||||
|
|
||||||
assert!(png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 3);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 1);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iCCP"));
|
assert_eq!(count_chunk(&png, b"iCCP"), 1);
|
||||||
|
|
||||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
|
|
@ -326,7 +331,7 @@ fn strip_headers_none() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -334,9 +339,9 @@ fn strip_headers_none() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(png.raw.aux_headers.contains_key(b"tEXt"));
|
assert_eq!(count_chunk(&png, b"tEXt"), 3);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iTXt"));
|
assert_eq!(count_chunk(&png, b"iTXt"), 1);
|
||||||
assert!(png.raw.aux_headers.contains_key(b"iCCP"));
|
assert_eq!(count_chunk(&png, b"iCCP"), 1);
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
}
|
}
|
||||||
|
|
@ -347,7 +352,7 @@ fn interlacing_0_to_1() {
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.interlace = Some(Interlacing::Adam7);
|
opts.interlace = Some(Interlacing::Adam7);
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
||||||
|
|
||||||
|
|
@ -358,7 +363,7 @@ fn interlacing_0_to_1() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -377,7 +382,7 @@ fn interlacing_1_to_0() {
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.interlace = Some(Interlacing::None);
|
opts.interlace = Some(Interlacing::None);
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
||||||
|
|
||||||
|
|
@ -388,7 +393,7 @@ fn interlacing_1_to_0() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -407,10 +412,10 @@ fn interlacing_0_to_1_small_files() {
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.interlace = Some(Interlacing::Adam7);
|
opts.interlace = Some(Interlacing::Adam7);
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
||||||
assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
||||||
|
|
||||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||||
|
|
@ -420,7 +425,7 @@ fn interlacing_0_to_1_small_files() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -429,8 +434,8 @@ fn interlacing_0_to_1_small_files() {
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
||||||
assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), RGB);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::One);
|
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
}
|
}
|
||||||
|
|
@ -441,10 +446,10 @@ fn interlacing_1_to_0_small_files() {
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.interlace = Some(Interlacing::None);
|
opts.interlace = Some(Interlacing::None);
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
||||||
assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
||||||
|
|
||||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||||
|
|
@ -454,7 +459,7 @@ fn interlacing_1_to_0_small_files() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -463,8 +468,8 @@ fn interlacing_1_to_0_small_files() {
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
||||||
assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), RGB);
|
||||||
// the depth can't be asserted reliably, because on such small file different zlib implementations pick different depth as the best
|
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
}
|
}
|
||||||
|
|
@ -478,7 +483,7 @@ fn interlaced_0_to_1_other_filter_mode() {
|
||||||
filter.insert(RowFilter::Paeth);
|
filter.insert(RowFilter::Paeth);
|
||||||
opts.filter = filter;
|
opts.filter = filter;
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
||||||
|
|
||||||
|
|
@ -489,7 +494,7 @@ fn interlaced_0_to_1_other_filter_mode() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -555,9 +560,9 @@ fn preserve_attrs() {
|
||||||
input,
|
input,
|
||||||
&output,
|
&output,
|
||||||
&opts,
|
&opts,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
callback_pre,
|
callback_pre,
|
||||||
callback_post,
|
callback_post,
|
||||||
|
|
@ -572,9 +577,9 @@ fn fix_errors() {
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.fix_errors = true;
|
opts.fix_errors = true;
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type, ColorType::RGBA);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), RGBA);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
||||||
|
|
||||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||||
|
|
@ -584,7 +589,7 @@ fn fix_errors() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, false) {
|
let png = match PngData::new(output, &Options::default()) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -592,13 +597,30 @@ fn fix_errors() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type, ColorType::Grayscale);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), GRAYSCALE);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
|
||||||
|
|
||||||
// Cannot check if pixels are equal because image crate cannot read corrupt (input) PNGs
|
// Cannot check if pixels are equal because image crate cannot read corrupt (input) PNGs
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scale_16() {
|
||||||
|
let input = PathBuf::from("tests/files/rgb_16_should_be_rgb_16.png");
|
||||||
|
let (output, mut opts) = get_opts(&input);
|
||||||
|
opts.scale_16 = true;
|
||||||
|
|
||||||
|
test_it_converts(
|
||||||
|
input,
|
||||||
|
&output,
|
||||||
|
&opts,
|
||||||
|
RGB,
|
||||||
|
BitDepth::Sixteen,
|
||||||
|
RGB,
|
||||||
|
BitDepth::Eight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "zopfli")]
|
#[cfg(feature = "zopfli")]
|
||||||
fn zopfli_mode() {
|
fn zopfli_mode() {
|
||||||
|
|
@ -612,9 +634,9 @@ fn zopfli_mode() {
|
||||||
input,
|
input,
|
||||||
&output,
|
&output,
|
||||||
&opts,
|
&opts,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use oxipng::{internal_tests::*, Interlacing, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::*;
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
const GRAYSCALE: u8 = 0;
|
||||||
|
const RGB: u8 = 2;
|
||||||
|
const INDEXED: u8 = 3;
|
||||||
|
const GRAYSCALE_ALPHA: u8 = 4;
|
||||||
|
const RGBA: u8 = 6;
|
||||||
|
|
||||||
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
let mut options = oxipng::Options {
|
let mut options = oxipng::Options {
|
||||||
force: true,
|
force: true,
|
||||||
|
|
@ -22,16 +28,16 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
|
|
||||||
fn test_it_converts(
|
fn test_it_converts(
|
||||||
input: &str,
|
input: &str,
|
||||||
color_type_in: ColorType,
|
color_type_in: u8,
|
||||||
bit_depth_in: BitDepth,
|
bit_depth_in: BitDepth,
|
||||||
color_type_out: ColorType,
|
color_type_out: u8,
|
||||||
bit_depth_out: BitDepth,
|
bit_depth_out: BitDepth,
|
||||||
) {
|
) {
|
||||||
let input = PathBuf::from(input);
|
let input = PathBuf::from(input);
|
||||||
let (output, opts) = get_opts(&input);
|
let (output, opts) = get_opts(&input);
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type, color_type_in);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
||||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
|
||||||
|
|
||||||
|
|
@ -42,7 +48,7 @@ fn test_it_converts(
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -50,7 +56,7 @@ fn test_it_converts(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type, color_type_out);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_out);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -60,9 +66,9 @@ fn test_it_converts(
|
||||||
fn interlaced_rgba_16_should_be_rgba_16() {
|
fn interlaced_rgba_16_should_be_rgba_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_16_should_be_rgba_16.png",
|
"tests/files/interlaced_rgba_16_should_be_rgba_16.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -71,9 +77,9 @@ fn interlaced_rgba_16_should_be_rgba_16() {
|
||||||
fn interlaced_rgba_16_should_be_rgba_8() {
|
fn interlaced_rgba_16_should_be_rgba_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_16_should_be_rgba_8.png",
|
"tests/files/interlaced_rgba_16_should_be_rgba_8.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -82,9 +88,9 @@ fn interlaced_rgba_16_should_be_rgba_8() {
|
||||||
fn interlaced_rgba_8_should_be_rgba_8() {
|
fn interlaced_rgba_8_should_be_rgba_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_8_should_be_rgba_8.png",
|
"tests/files/interlaced_rgba_8_should_be_rgba_8.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -93,9 +99,9 @@ fn interlaced_rgba_8_should_be_rgba_8() {
|
||||||
fn interlaced_rgba_16_should_be_rgb_16() {
|
fn interlaced_rgba_16_should_be_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_16_should_be_rgb_16.png",
|
"tests/files/interlaced_rgba_16_should_be_rgb_16.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -104,9 +110,9 @@ fn interlaced_rgba_16_should_be_rgb_16() {
|
||||||
fn interlaced_rgba_16_should_be_rgb_8() {
|
fn interlaced_rgba_16_should_be_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_16_should_be_rgb_8.png",
|
"tests/files/interlaced_rgba_16_should_be_rgb_8.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -115,9 +121,9 @@ fn interlaced_rgba_16_should_be_rgb_8() {
|
||||||
fn interlaced_rgba_8_should_be_rgb_8() {
|
fn interlaced_rgba_8_should_be_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_8_should_be_rgb_8.png",
|
"tests/files/interlaced_rgba_8_should_be_rgb_8.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -126,9 +132,9 @@ fn interlaced_rgba_8_should_be_rgb_8() {
|
||||||
fn interlaced_rgba_16_should_be_palette_8() {
|
fn interlaced_rgba_16_should_be_palette_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_16_should_be_palette_8.png",
|
"tests/files/interlaced_rgba_16_should_be_palette_8.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -137,9 +143,9 @@ fn interlaced_rgba_16_should_be_palette_8() {
|
||||||
fn interlaced_rgba_8_should_be_palette_8() {
|
fn interlaced_rgba_8_should_be_palette_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_8_should_be_palette_8.png",
|
"tests/files/interlaced_rgba_8_should_be_palette_8.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -148,9 +154,9 @@ fn interlaced_rgba_8_should_be_palette_8() {
|
||||||
fn interlaced_rgba_16_should_be_palette_4() {
|
fn interlaced_rgba_16_should_be_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_16_should_be_palette_4.png",
|
"tests/files/interlaced_rgba_16_should_be_palette_4.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -159,9 +165,9 @@ fn interlaced_rgba_16_should_be_palette_4() {
|
||||||
fn interlaced_rgba_8_should_be_palette_4() {
|
fn interlaced_rgba_8_should_be_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_8_should_be_palette_4.png",
|
"tests/files/interlaced_rgba_8_should_be_palette_4.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -170,9 +176,9 @@ fn interlaced_rgba_8_should_be_palette_4() {
|
||||||
fn interlaced_rgba_16_should_be_palette_2() {
|
fn interlaced_rgba_16_should_be_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_16_should_be_palette_2.png",
|
"tests/files/interlaced_rgba_16_should_be_palette_2.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -181,9 +187,9 @@ fn interlaced_rgba_16_should_be_palette_2() {
|
||||||
fn interlaced_rgba_8_should_be_palette_2() {
|
fn interlaced_rgba_8_should_be_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_8_should_be_palette_2.png",
|
"tests/files/interlaced_rgba_8_should_be_palette_2.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -192,9 +198,9 @@ fn interlaced_rgba_8_should_be_palette_2() {
|
||||||
fn interlaced_rgba_16_should_be_palette_1() {
|
fn interlaced_rgba_16_should_be_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_16_should_be_palette_1.png",
|
"tests/files/interlaced_rgba_16_should_be_palette_1.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -203,9 +209,9 @@ fn interlaced_rgba_16_should_be_palette_1() {
|
||||||
fn interlaced_rgba_8_should_be_palette_1() {
|
fn interlaced_rgba_8_should_be_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_8_should_be_palette_1.png",
|
"tests/files/interlaced_rgba_8_should_be_palette_1.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -214,9 +220,9 @@ fn interlaced_rgba_8_should_be_palette_1() {
|
||||||
fn interlaced_rgba_16_should_be_grayscale_alpha_16() {
|
fn interlaced_rgba_16_should_be_grayscale_alpha_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_16_should_be_grayscale_alpha_16.png",
|
"tests/files/interlaced_rgba_16_should_be_grayscale_alpha_16.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -225,9 +231,9 @@ fn interlaced_rgba_16_should_be_grayscale_alpha_16() {
|
||||||
fn interlaced_rgba_16_should_be_grayscale_alpha_8() {
|
fn interlaced_rgba_16_should_be_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_16_should_be_grayscale_alpha_8.png",
|
"tests/files/interlaced_rgba_16_should_be_grayscale_alpha_8.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -236,9 +242,9 @@ fn interlaced_rgba_16_should_be_grayscale_alpha_8() {
|
||||||
fn interlaced_rgba_8_should_be_grayscale_alpha_8() {
|
fn interlaced_rgba_8_should_be_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_8_should_be_grayscale_alpha_8.png",
|
"tests/files/interlaced_rgba_8_should_be_grayscale_alpha_8.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -247,9 +253,9 @@ fn interlaced_rgba_8_should_be_grayscale_alpha_8() {
|
||||||
fn interlaced_rgba_16_should_be_grayscale_16() {
|
fn interlaced_rgba_16_should_be_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_16_should_be_grayscale_16.png",
|
"tests/files/interlaced_rgba_16_should_be_grayscale_16.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -258,9 +264,9 @@ fn interlaced_rgba_16_should_be_grayscale_16() {
|
||||||
fn interlaced_rgba_16_should_be_grayscale_8() {
|
fn interlaced_rgba_16_should_be_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_16_should_be_grayscale_8.png",
|
"tests/files/interlaced_rgba_16_should_be_grayscale_8.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -269,9 +275,9 @@ fn interlaced_rgba_16_should_be_grayscale_8() {
|
||||||
fn interlaced_rgba_8_should_be_grayscale_8() {
|
fn interlaced_rgba_8_should_be_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgba_8_should_be_grayscale_8.png",
|
"tests/files/interlaced_rgba_8_should_be_grayscale_8.png",
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -280,9 +286,9 @@ fn interlaced_rgba_8_should_be_grayscale_8() {
|
||||||
fn interlaced_rgb_16_should_be_rgb_16() {
|
fn interlaced_rgb_16_should_be_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_16_should_be_rgb_16.png",
|
"tests/files/interlaced_rgb_16_should_be_rgb_16.png",
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -291,9 +297,9 @@ fn interlaced_rgb_16_should_be_rgb_16() {
|
||||||
fn interlaced_rgb_16_should_be_rgb_8() {
|
fn interlaced_rgb_16_should_be_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_16_should_be_rgb_8.png",
|
"tests/files/interlaced_rgb_16_should_be_rgb_8.png",
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -302,9 +308,9 @@ fn interlaced_rgb_16_should_be_rgb_8() {
|
||||||
fn interlaced_rgb_8_should_be_rgb_8() {
|
fn interlaced_rgb_8_should_be_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_8_should_be_rgb_8.png",
|
"tests/files/interlaced_rgb_8_should_be_rgb_8.png",
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -313,9 +319,9 @@ fn interlaced_rgb_8_should_be_rgb_8() {
|
||||||
fn interlaced_rgb_16_should_be_palette_8() {
|
fn interlaced_rgb_16_should_be_palette_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_16_should_be_palette_8.png",
|
"tests/files/interlaced_rgb_16_should_be_palette_8.png",
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -324,9 +330,9 @@ fn interlaced_rgb_16_should_be_palette_8() {
|
||||||
fn interlaced_rgb_8_should_be_palette_8() {
|
fn interlaced_rgb_8_should_be_palette_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_8_should_be_palette_8.png",
|
"tests/files/interlaced_rgb_8_should_be_palette_8.png",
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -335,9 +341,9 @@ fn interlaced_rgb_8_should_be_palette_8() {
|
||||||
fn interlaced_rgb_16_should_be_palette_4() {
|
fn interlaced_rgb_16_should_be_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_16_should_be_palette_4.png",
|
"tests/files/interlaced_rgb_16_should_be_palette_4.png",
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -346,9 +352,9 @@ fn interlaced_rgb_16_should_be_palette_4() {
|
||||||
fn interlaced_rgb_8_should_be_palette_4() {
|
fn interlaced_rgb_8_should_be_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_8_should_be_palette_4.png",
|
"tests/files/interlaced_rgb_8_should_be_palette_4.png",
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -357,9 +363,9 @@ fn interlaced_rgb_8_should_be_palette_4() {
|
||||||
fn interlaced_rgb_16_should_be_palette_2() {
|
fn interlaced_rgb_16_should_be_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_16_should_be_palette_2.png",
|
"tests/files/interlaced_rgb_16_should_be_palette_2.png",
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -368,9 +374,9 @@ fn interlaced_rgb_16_should_be_palette_2() {
|
||||||
fn interlaced_rgb_8_should_be_palette_2() {
|
fn interlaced_rgb_8_should_be_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_8_should_be_palette_2.png",
|
"tests/files/interlaced_rgb_8_should_be_palette_2.png",
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -379,9 +385,9 @@ fn interlaced_rgb_8_should_be_palette_2() {
|
||||||
fn interlaced_rgb_16_should_be_palette_1() {
|
fn interlaced_rgb_16_should_be_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_16_should_be_palette_1.png",
|
"tests/files/interlaced_rgb_16_should_be_palette_1.png",
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -390,9 +396,9 @@ fn interlaced_rgb_16_should_be_palette_1() {
|
||||||
fn interlaced_rgb_8_should_be_palette_1() {
|
fn interlaced_rgb_8_should_be_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_8_should_be_palette_1.png",
|
"tests/files/interlaced_rgb_8_should_be_palette_1.png",
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -401,9 +407,9 @@ fn interlaced_rgb_8_should_be_palette_1() {
|
||||||
fn interlaced_rgb_16_should_be_grayscale_16() {
|
fn interlaced_rgb_16_should_be_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_16_should_be_grayscale_16.png",
|
"tests/files/interlaced_rgb_16_should_be_grayscale_16.png",
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -412,9 +418,9 @@ fn interlaced_rgb_16_should_be_grayscale_16() {
|
||||||
fn interlaced_rgb_16_should_be_grayscale_8() {
|
fn interlaced_rgb_16_should_be_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_16_should_be_grayscale_8.png",
|
"tests/files/interlaced_rgb_16_should_be_grayscale_8.png",
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -423,9 +429,9 @@ fn interlaced_rgb_16_should_be_grayscale_8() {
|
||||||
fn interlaced_rgb_8_should_be_grayscale_8() {
|
fn interlaced_rgb_8_should_be_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_8_should_be_grayscale_8.png",
|
"tests/files/interlaced_rgb_8_should_be_grayscale_8.png",
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -434,9 +440,9 @@ fn interlaced_rgb_8_should_be_grayscale_8() {
|
||||||
fn interlaced_palette_8_should_be_palette_8() {
|
fn interlaced_palette_8_should_be_palette_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_palette_8_should_be_palette_8.png",
|
"tests/files/interlaced_palette_8_should_be_palette_8.png",
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -445,9 +451,9 @@ fn interlaced_palette_8_should_be_palette_8() {
|
||||||
fn interlaced_palette_8_should_be_palette_4() {
|
fn interlaced_palette_8_should_be_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_palette_8_should_be_palette_4.png",
|
"tests/files/interlaced_palette_8_should_be_palette_4.png",
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -456,9 +462,9 @@ fn interlaced_palette_8_should_be_palette_4() {
|
||||||
fn interlaced_palette_4_should_be_palette_4() {
|
fn interlaced_palette_4_should_be_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_palette_4_should_be_palette_4.png",
|
"tests/files/interlaced_palette_4_should_be_palette_4.png",
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -467,9 +473,9 @@ fn interlaced_palette_4_should_be_palette_4() {
|
||||||
fn interlaced_palette_8_should_be_palette_2() {
|
fn interlaced_palette_8_should_be_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_palette_8_should_be_palette_2.png",
|
"tests/files/interlaced_palette_8_should_be_palette_2.png",
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -478,9 +484,9 @@ fn interlaced_palette_8_should_be_palette_2() {
|
||||||
fn interlaced_palette_4_should_be_palette_2() {
|
fn interlaced_palette_4_should_be_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_palette_4_should_be_palette_2.png",
|
"tests/files/interlaced_palette_4_should_be_palette_2.png",
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -489,9 +495,9 @@ fn interlaced_palette_4_should_be_palette_2() {
|
||||||
fn interlaced_palette_2_should_be_palette_2() {
|
fn interlaced_palette_2_should_be_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_palette_2_should_be_palette_2.png",
|
"tests/files/interlaced_palette_2_should_be_palette_2.png",
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -500,9 +506,9 @@ fn interlaced_palette_2_should_be_palette_2() {
|
||||||
fn interlaced_palette_8_should_be_palette_1() {
|
fn interlaced_palette_8_should_be_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_palette_8_should_be_palette_1.png",
|
"tests/files/interlaced_palette_8_should_be_palette_1.png",
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -511,9 +517,9 @@ fn interlaced_palette_8_should_be_palette_1() {
|
||||||
fn interlaced_palette_4_should_be_palette_1() {
|
fn interlaced_palette_4_should_be_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_palette_4_should_be_palette_1.png",
|
"tests/files/interlaced_palette_4_should_be_palette_1.png",
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -522,9 +528,9 @@ fn interlaced_palette_4_should_be_palette_1() {
|
||||||
fn interlaced_palette_2_should_be_palette_1() {
|
fn interlaced_palette_2_should_be_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_palette_2_should_be_palette_1.png",
|
"tests/files/interlaced_palette_2_should_be_palette_1.png",
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -533,20 +539,31 @@ fn interlaced_palette_2_should_be_palette_1() {
|
||||||
fn interlaced_palette_1_should_be_palette_1() {
|
fn interlaced_palette_1_should_be_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_palette_1_should_be_palette_1.png",
|
"tests/files/interlaced_palette_1_should_be_palette_1.png",
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn interlaced_palette_8_should_be_grayscale_8() {
|
||||||
|
test_it_converts(
|
||||||
|
"tests/files/interlaced_palette_8_should_be_grayscale_8.png",
|
||||||
|
INDEXED,
|
||||||
|
BitDepth::Eight,
|
||||||
|
GRAYSCALE,
|
||||||
|
BitDepth::Eight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn interlaced_grayscale_alpha_16_should_be_grayscale_alpha_16() {
|
fn interlaced_grayscale_alpha_16_should_be_grayscale_alpha_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_grayscale_alpha_16_should_be_grayscale_alpha_16.png",
|
"tests/files/interlaced_grayscale_alpha_16_should_be_grayscale_alpha_16.png",
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -555,9 +572,9 @@ fn interlaced_grayscale_alpha_16_should_be_grayscale_alpha_16() {
|
||||||
fn interlaced_grayscale_alpha_16_should_be_grayscale_alpha_8() {
|
fn interlaced_grayscale_alpha_16_should_be_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_grayscale_alpha_16_should_be_grayscale_alpha_8.png",
|
"tests/files/interlaced_grayscale_alpha_16_should_be_grayscale_alpha_8.png",
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -566,9 +583,9 @@ fn interlaced_grayscale_alpha_16_should_be_grayscale_alpha_8() {
|
||||||
fn interlaced_grayscale_alpha_8_should_be_grayscale_alpha_8() {
|
fn interlaced_grayscale_alpha_8_should_be_grayscale_alpha_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_grayscale_alpha_8_should_be_grayscale_alpha_8.png",
|
"tests/files/interlaced_grayscale_alpha_8_should_be_grayscale_alpha_8.png",
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -577,9 +594,9 @@ fn interlaced_grayscale_alpha_8_should_be_grayscale_alpha_8() {
|
||||||
fn interlaced_grayscale_alpha_16_should_be_grayscale_16() {
|
fn interlaced_grayscale_alpha_16_should_be_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_grayscale_alpha_16_should_be_grayscale_16.png",
|
"tests/files/interlaced_grayscale_alpha_16_should_be_grayscale_16.png",
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -588,9 +605,9 @@ fn interlaced_grayscale_alpha_16_should_be_grayscale_16() {
|
||||||
fn interlaced_grayscale_alpha_16_should_be_grayscale_8() {
|
fn interlaced_grayscale_alpha_16_should_be_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_grayscale_alpha_16_should_be_grayscale_8.png",
|
"tests/files/interlaced_grayscale_alpha_16_should_be_grayscale_8.png",
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -599,9 +616,9 @@ fn interlaced_grayscale_alpha_16_should_be_grayscale_8() {
|
||||||
fn interlaced_grayscale_alpha_8_should_be_grayscale_8() {
|
fn interlaced_grayscale_alpha_8_should_be_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_grayscale_alpha_8_should_be_grayscale_8.png",
|
"tests/files/interlaced_grayscale_alpha_8_should_be_grayscale_8.png",
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -610,9 +627,9 @@ fn interlaced_grayscale_alpha_8_should_be_grayscale_8() {
|
||||||
fn interlaced_grayscale_16_should_be_grayscale_16() {
|
fn interlaced_grayscale_16_should_be_grayscale_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_grayscale_16_should_be_grayscale_16.png",
|
"tests/files/interlaced_grayscale_16_should_be_grayscale_16.png",
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -621,9 +638,9 @@ fn interlaced_grayscale_16_should_be_grayscale_16() {
|
||||||
fn interlaced_grayscale_16_should_be_grayscale_8() {
|
fn interlaced_grayscale_16_should_be_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_grayscale_16_should_be_grayscale_8.png",
|
"tests/files/interlaced_grayscale_16_should_be_grayscale_8.png",
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -632,9 +649,9 @@ fn interlaced_grayscale_16_should_be_grayscale_8() {
|
||||||
fn interlaced_grayscale_8_should_be_grayscale_8() {
|
fn interlaced_grayscale_8_should_be_grayscale_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_grayscale_8_should_be_grayscale_8.png",
|
"tests/files/interlaced_grayscale_8_should_be_grayscale_8.png",
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -643,10 +660,10 @@ fn interlaced_grayscale_8_should_be_grayscale_8() {
|
||||||
fn interlaced_small_files() {
|
fn interlaced_small_files() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_small_files.png",
|
"tests/files/interlaced_small_files.png",
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
|
BitDepth::Eight,
|
||||||
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
|
||||||
BitDepth::One,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -654,9 +671,9 @@ fn interlaced_small_files() {
|
||||||
fn interlaced_odd_width() {
|
fn interlaced_odd_width() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_odd_width.png",
|
"tests/files/interlaced_odd_width.png",
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use oxipng::{internal_tests::*, Interlacing, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::*;
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
const RGB: u8 = 2;
|
||||||
|
const INDEXED: u8 = 3;
|
||||||
|
|
||||||
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
let mut options = oxipng::Options {
|
let mut options = oxipng::Options {
|
||||||
force: true,
|
force: true,
|
||||||
|
|
@ -23,16 +26,16 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
fn test_it_converts(
|
fn test_it_converts(
|
||||||
input: &str,
|
input: &str,
|
||||||
interlace: Interlacing,
|
interlace: Interlacing,
|
||||||
color_type_in: ColorType,
|
color_type_in: u8,
|
||||||
bit_depth_in: BitDepth,
|
bit_depth_in: BitDepth,
|
||||||
color_type_out: ColorType,
|
color_type_out: u8,
|
||||||
bit_depth_out: BitDepth,
|
bit_depth_out: BitDepth,
|
||||||
) {
|
) {
|
||||||
let input = PathBuf::from(input);
|
let input = PathBuf::from(input);
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
opts.interlace = Some(interlace);
|
opts.interlace = Some(interlace);
|
||||||
assert_eq!(png.raw.ihdr.color_type, color_type_in);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
png.raw.ihdr.interlaced,
|
png.raw.ihdr.interlaced,
|
||||||
|
|
@ -50,7 +53,7 @@ fn test_it_converts(
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -58,7 +61,7 @@ fn test_it_converts(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type, color_type_out);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_out);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -69,9 +72,9 @@ fn deinterlace_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_16_should_be_rgb_16.png",
|
"tests/files/interlaced_rgb_16_should_be_rgb_16.png",
|
||||||
Interlacing::None,
|
Interlacing::None,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -81,9 +84,9 @@ fn deinterlace_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_rgb_8_should_be_rgb_8.png",
|
"tests/files/interlaced_rgb_8_should_be_rgb_8.png",
|
||||||
Interlacing::None,
|
Interlacing::None,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -93,9 +96,9 @@ fn deinterlace_palette_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_palette_8_should_be_palette_8.png",
|
"tests/files/interlaced_palette_8_should_be_palette_8.png",
|
||||||
Interlacing::None,
|
Interlacing::None,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -105,9 +108,9 @@ fn deinterlace_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_palette_4_should_be_palette_4.png",
|
"tests/files/interlaced_palette_4_should_be_palette_4.png",
|
||||||
Interlacing::None,
|
Interlacing::None,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -117,9 +120,9 @@ fn deinterlace_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_palette_2_should_be_palette_2.png",
|
"tests/files/interlaced_palette_2_should_be_palette_2.png",
|
||||||
Interlacing::None,
|
Interlacing::None,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -129,9 +132,9 @@ fn deinterlace_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/interlaced_palette_1_should_be_palette_1.png",
|
"tests/files/interlaced_palette_1_should_be_palette_1.png",
|
||||||
Interlacing::None,
|
Interlacing::None,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -141,9 +144,9 @@ fn interlace_rgb_16() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/rgb_16_should_be_rgb_16.png",
|
"tests/files/rgb_16_should_be_rgb_16.png",
|
||||||
Interlacing::Adam7,
|
Interlacing::Adam7,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -153,9 +156,9 @@ fn interlace_rgb_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/rgb_8_should_be_rgb_8.png",
|
"tests/files/rgb_8_should_be_rgb_8.png",
|
||||||
Interlacing::Adam7,
|
Interlacing::Adam7,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -165,9 +168,9 @@ fn interlace_palette_8() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/palette_8_should_be_palette_8.png",
|
"tests/files/palette_8_should_be_palette_8.png",
|
||||||
Interlacing::Adam7,
|
Interlacing::Adam7,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -177,9 +180,9 @@ fn interlace_palette_4() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/palette_4_should_be_palette_4.png",
|
"tests/files/palette_4_should_be_palette_4.png",
|
||||||
Interlacing::Adam7,
|
Interlacing::Adam7,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -189,9 +192,9 @@ fn interlace_palette_2() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/palette_2_should_be_palette_2.png",
|
"tests/files/palette_2_should_be_palette_2.png",
|
||||||
Interlacing::Adam7,
|
Interlacing::Adam7,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -201,9 +204,9 @@ fn interlace_palette_1() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/palette_1_should_be_palette_1.png",
|
"tests/files/palette_1_should_be_palette_1.png",
|
||||||
Interlacing::Adam7,
|
Interlacing::Adam7,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
tests/lib.rs
|
|
@ -1,6 +1,4 @@
|
||||||
use oxipng::Headers;
|
use oxipng::*;
|
||||||
use oxipng::OutFile;
|
|
||||||
use std::default::Default;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
|
@ -11,9 +9,7 @@ fn optimize_from_memory() {
|
||||||
let mut in_file_buf: Vec<u8> = Vec::new();
|
let mut in_file_buf: Vec<u8> = Vec::new();
|
||||||
in_file.read_to_end(&mut in_file_buf).unwrap();
|
in_file.read_to_end(&mut in_file_buf).unwrap();
|
||||||
|
|
||||||
let opts: oxipng::Options = Default::default();
|
let result = oxipng::optimize_from_memory(&in_file_buf, &Options::default());
|
||||||
|
|
||||||
let result = oxipng::optimize_from_memory(&in_file_buf, &opts);
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,9 +19,7 @@ fn optimize_from_memory_corrupted() {
|
||||||
let mut in_file_buf: Vec<u8> = Vec::new();
|
let mut in_file_buf: Vec<u8> = Vec::new();
|
||||||
in_file.read_to_end(&mut in_file_buf).unwrap();
|
in_file.read_to_end(&mut in_file_buf).unwrap();
|
||||||
|
|
||||||
let opts: oxipng::Options = Default::default();
|
let result = oxipng::optimize_from_memory(&in_file_buf, &Options::default());
|
||||||
|
|
||||||
let result = oxipng::optimize_from_memory(&in_file_buf, &opts);
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,44 +29,36 @@ fn optimize_from_memory_apng() {
|
||||||
let mut in_file_buf: Vec<u8> = Vec::new();
|
let mut in_file_buf: Vec<u8> = Vec::new();
|
||||||
in_file.read_to_end(&mut in_file_buf).unwrap();
|
in_file.read_to_end(&mut in_file_buf).unwrap();
|
||||||
|
|
||||||
let opts: oxipng::Options = Default::default();
|
let result = oxipng::optimize_from_memory(&in_file_buf, &Options::default());
|
||||||
|
|
||||||
let result = oxipng::optimize_from_memory(&in_file_buf, &opts);
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn optimize() {
|
fn optimize() {
|
||||||
let opts: oxipng::Options = Default::default();
|
|
||||||
|
|
||||||
let result = oxipng::optimize(
|
let result = oxipng::optimize(
|
||||||
&"tests/files/fully_optimized.png".into(),
|
&"tests/files/fully_optimized.png".into(),
|
||||||
&OutFile::Path(None),
|
&OutFile::Path(None),
|
||||||
&opts,
|
&Options::default(),
|
||||||
);
|
);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn optimize_corrupted() {
|
fn optimize_corrupted() {
|
||||||
let opts: oxipng::Options = Default::default();
|
|
||||||
|
|
||||||
let result = oxipng::optimize(
|
let result = oxipng::optimize(
|
||||||
&"tests/files/corrupted_header.png".into(),
|
&"tests/files/corrupted_header.png".into(),
|
||||||
&OutFile::Path(None),
|
&OutFile::Path(None),
|
||||||
&opts,
|
&Options::default(),
|
||||||
);
|
);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn optimize_apng() {
|
fn optimize_apng() {
|
||||||
let opts: oxipng::Options = Default::default();
|
|
||||||
|
|
||||||
let result = oxipng::optimize(
|
let result = oxipng::optimize(
|
||||||
&"tests/files/apng_file.png".into(),
|
&"tests/files/apng_file.png".into(),
|
||||||
&OutFile::Path(None),
|
&OutFile::Path(None),
|
||||||
&opts,
|
&Options::default(),
|
||||||
);
|
);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
@ -80,12 +66,12 @@ fn optimize_apng() {
|
||||||
#[test]
|
#[test]
|
||||||
fn optimize_srgb_icc() {
|
fn optimize_srgb_icc() {
|
||||||
let file = fs::read("tests/files/badsrgb.png").unwrap();
|
let file = fs::read("tests/files/badsrgb.png").unwrap();
|
||||||
let mut opts: oxipng::Options = Default::default();
|
let mut opts = Options::default();
|
||||||
|
|
||||||
let result = oxipng::optimize_from_memory(&file, &opts);
|
let result = oxipng::optimize_from_memory(&file, &opts);
|
||||||
assert!(result.unwrap().len() > 1000);
|
assert!(result.unwrap().len() > 1000);
|
||||||
|
|
||||||
opts.strip = Headers::Safe;
|
opts.strip = StripChunks::Safe;
|
||||||
let result = oxipng::optimize_from_memory(&file, &opts);
|
let result = oxipng::optimize_from_memory(&file, &opts);
|
||||||
assert!(result.unwrap().len() < 1000);
|
assert!(result.unwrap().len() < 1000);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
97
tests/raw.rs
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
use oxipng::internal_tests::*;
|
||||||
|
use oxipng::*;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
fn get_opts() -> Options {
|
||||||
|
Options {
|
||||||
|
force: true,
|
||||||
|
filter: indexset! { RowFilter::None },
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_it_converts(input: &str) {
|
||||||
|
let input = PathBuf::from(input);
|
||||||
|
let opts = get_opts();
|
||||||
|
|
||||||
|
let original_data = PngData::read_file(&PathBuf::from(input)).unwrap();
|
||||||
|
let image = PngData::from_slice(&original_data, &opts).unwrap();
|
||||||
|
let png = Arc::try_unwrap(image.raw).unwrap();
|
||||||
|
|
||||||
|
let num_chunks = image.aux_chunks.len();
|
||||||
|
assert!(num_chunks > 0);
|
||||||
|
|
||||||
|
let mut raw = RawImage::new(
|
||||||
|
png.ihdr.width,
|
||||||
|
png.ihdr.height,
|
||||||
|
png.ihdr.color_type,
|
||||||
|
png.ihdr.bit_depth,
|
||||||
|
png.data,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for chunk in image.aux_chunks {
|
||||||
|
raw.add_png_chunk(chunk.name, chunk.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = raw.create_optimized_png(&opts).unwrap();
|
||||||
|
|
||||||
|
let new = PngData::from_slice(&output, &opts).unwrap();
|
||||||
|
assert!(new.aux_chunks.len() == num_chunks);
|
||||||
|
|
||||||
|
#[cfg(feature = "sanity-checks")]
|
||||||
|
assert!(validate_output(&output, &original_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_file() {
|
||||||
|
test_it_converts("tests/files/raw_api.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_indexed() {
|
||||||
|
let opts = get_opts();
|
||||||
|
|
||||||
|
let raw = RawImage::new(
|
||||||
|
4,
|
||||||
|
4,
|
||||||
|
ColorType::Indexed {
|
||||||
|
palette: vec![
|
||||||
|
RGBA8::new(255, 255, 255, 255),
|
||||||
|
RGBA8::new(255, 0, 0, 255),
|
||||||
|
RGBA8::new(0, 255, 0, 255),
|
||||||
|
RGBA8::new(0, 0, 255, 255),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
BitDepth::Eight,
|
||||||
|
vec![0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 3, 3, 2, 2, 3, 3],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
raw.create_optimized_png(&opts).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_depth() {
|
||||||
|
RawImage::new(
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
ColorType::RGBA,
|
||||||
|
BitDepth::Four,
|
||||||
|
vec![0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 3, 3, 2, 2, 3, 3],
|
||||||
|
)
|
||||||
|
.expect_err("Expected invalid depth for color type");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn incorrect_length() {
|
||||||
|
RawImage::new(
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
ColorType::RGBA,
|
||||||
|
BitDepth::Eight,
|
||||||
|
vec![0, 0, 1, 1, 0, 0, 1, 1],
|
||||||
|
)
|
||||||
|
.expect_err("Expected incorrect data length");
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use oxipng::{internal_tests::*, Interlacing, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::*;
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
const GRAYSCALE: u8 = 0;
|
||||||
|
const RGB: u8 = 2;
|
||||||
|
const INDEXED: u8 = 3;
|
||||||
|
const GRAYSCALE_ALPHA: u8 = 4;
|
||||||
|
const RGBA: u8 = 6;
|
||||||
|
|
||||||
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
let mut options = oxipng::Options {
|
let mut options = oxipng::Options {
|
||||||
force: true,
|
force: true,
|
||||||
|
|
@ -23,17 +29,18 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
fn test_it_converts(
|
fn test_it_converts(
|
||||||
input: &str,
|
input: &str,
|
||||||
custom: Option<(OutFile, oxipng::Options)>,
|
custom: Option<(OutFile, oxipng::Options)>,
|
||||||
color_type_in: ColorType,
|
color_type_in: u8,
|
||||||
bit_depth_in: BitDepth,
|
bit_depth_in: BitDepth,
|
||||||
color_type_out: ColorType,
|
color_type_out: u8,
|
||||||
bit_depth_out: BitDepth,
|
bit_depth_out: BitDepth,
|
||||||
) {
|
) {
|
||||||
let input = PathBuf::from(input);
|
let input = PathBuf::from(input);
|
||||||
let (output, opts) = custom.unwrap_or_else(|| get_opts(&input));
|
let (output, opts) = custom.unwrap_or_else(|| get_opts(&input));
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
png.raw.ihdr.color_type, color_type_in,
|
png.raw.ihdr.color_type.png_header_code(),
|
||||||
|
color_type_in,
|
||||||
"test file is broken"
|
"test file is broken"
|
||||||
);
|
);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in, "test file is broken");
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in, "test file is broken");
|
||||||
|
|
@ -45,7 +52,7 @@ fn test_it_converts(
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -54,23 +61,16 @@ fn test_it_converts(
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
png.raw.ihdr.color_type, color_type_out,
|
png.raw.ihdr.color_type.png_header_code(),
|
||||||
|
color_type_out,
|
||||||
"optimized to wrong color type"
|
"optimized to wrong color type"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
png.raw.ihdr.bit_depth, bit_depth_out,
|
png.raw.ihdr.bit_depth, bit_depth_out,
|
||||||
"optimized to wrong bit depth"
|
"optimized to wrong bit depth"
|
||||||
);
|
);
|
||||||
if let Some(palette) = png.raw.palette.as_ref() {
|
if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
|
||||||
let mut max_palette_size = 1 << (png.raw.ihdr.bit_depth.as_u8() as usize);
|
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth as u8));
|
||||||
// Ensure bKGD color is valid
|
|
||||||
if let Some(&idx) = png.raw.aux_headers.get(b"bKGD").and_then(|b| b.first()) {
|
|
||||||
assert!(palette.len() > idx as usize);
|
|
||||||
max_palette_size = max_palette_size.max(idx as usize + 1);
|
|
||||||
}
|
|
||||||
assert!(palette.len() <= max_palette_size);
|
|
||||||
} else {
|
|
||||||
assert_ne!(png.raw.ihdr.color_type, ColorType::Indexed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -81,9 +81,9 @@ fn issue_29() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-29.png",
|
"tests/files/issue-29.png",
|
||||||
None,
|
None,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +94,7 @@ fn issue_42() {
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
opts.interlace = Some(Interlacing::Adam7);
|
opts.interlace = Some(Interlacing::Adam7);
|
||||||
|
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
|
||||||
assert_eq!(png.raw.ihdr.color_type, ColorType::GrayscaleAlpha);
|
assert_eq!(png.raw.ihdr.color_type, ColorType::GrayscaleAlpha);
|
||||||
|
|
@ -107,7 +107,7 @@ fn issue_42() {
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -127,9 +127,9 @@ fn issue_52_01() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-52-01.png",
|
"tests/files/issue-52-01.png",
|
||||||
None,
|
None,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -139,9 +139,9 @@ fn issue_52_02() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-52-02.png",
|
"tests/files/issue-52-02.png",
|
||||||
None,
|
None,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -151,9 +151,9 @@ fn issue_52_03() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-52-03.png",
|
"tests/files/issue-52-03.png",
|
||||||
None,
|
None,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -163,10 +163,10 @@ fn issue_52_04() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-52-04.png",
|
"tests/files/issue-52-04.png",
|
||||||
None,
|
None,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
|
BitDepth::Eight,
|
||||||
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
|
||||||
BitDepth::One,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,9 +175,9 @@ fn issue_52_05() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-52-05.png",
|
"tests/files/issue-52-05.png",
|
||||||
None,
|
None,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -187,10 +187,10 @@ fn issue_52_06() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-52-06.png",
|
"tests/files/issue-52-06.png",
|
||||||
None,
|
None,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
|
BitDepth::Eight,
|
||||||
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
|
||||||
BitDepth::Two,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,9 +199,9 @@ fn issue_56() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-56.png",
|
"tests/files/issue-56.png",
|
||||||
None,
|
None,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -211,9 +211,9 @@ fn issue_58() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-58.png",
|
"tests/files/issue-58.png",
|
||||||
None,
|
None,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -223,9 +223,9 @@ fn issue_59() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-59.png",
|
"tests/files/issue-59.png",
|
||||||
None,
|
None,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -235,9 +235,9 @@ fn issue_60() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-60.png",
|
"tests/files/issue-60.png",
|
||||||
None,
|
None,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -247,9 +247,9 @@ fn issue_80() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-80.png",
|
"tests/files/issue-80.png",
|
||||||
None,
|
None,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -259,9 +259,9 @@ fn issue_82() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-82.png",
|
"tests/files/issue-82.png",
|
||||||
None,
|
None,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Four,
|
BitDepth::Four,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -271,9 +271,9 @@ fn issue_89() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-89.png",
|
"tests/files/issue-89.png",
|
||||||
None,
|
None,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -283,9 +283,9 @@ fn issue_92_filter_0() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-92.png",
|
"tests/files/issue-92.png",
|
||||||
None,
|
None,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -300,9 +300,9 @@ fn issue_92_filter_5() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -316,9 +316,9 @@ fn issue_113() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::GrayscaleAlpha,
|
GRAYSCALE_ALPHA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -326,14 +326,7 @@ fn issue_113() {
|
||||||
#[test]
|
#[test]
|
||||||
fn issue_129() {
|
fn issue_129() {
|
||||||
let input = "tests/files/issue-129.png";
|
let input = "tests/files/issue-129.png";
|
||||||
test_it_converts(
|
test_it_converts(input, None, RGB, BitDepth::Eight, INDEXED, BitDepth::Eight);
|
||||||
input,
|
|
||||||
None,
|
|
||||||
ColorType::RGB,
|
|
||||||
BitDepth::Eight,
|
|
||||||
ColorType::Indexed,
|
|
||||||
BitDepth::Eight,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -344,9 +337,9 @@ fn issue_133() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -356,9 +349,9 @@ fn issue_140() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-140.png",
|
"tests/files/issue-140.png",
|
||||||
None,
|
None,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Two,
|
BitDepth::Two,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -368,9 +361,9 @@ fn issue_141() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-141.png",
|
"tests/files/issue-141.png",
|
||||||
None,
|
None,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -380,9 +373,9 @@ fn issue_153() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-153.png",
|
"tests/files/issue-153.png",
|
||||||
None,
|
None,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -392,9 +385,9 @@ fn issue_159() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-159.png",
|
"tests/files/issue-159.png",
|
||||||
None,
|
None,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -404,9 +397,9 @@ fn issue_171() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-171.png",
|
"tests/files/issue-171.png",
|
||||||
None,
|
None,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -416,9 +409,9 @@ fn issue_175() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-175.png",
|
"tests/files/issue-175.png",
|
||||||
None,
|
None,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -432,9 +425,9 @@ fn issue_182() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
Some((output, opts)),
|
Some((output, opts)),
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -444,9 +437,9 @@ fn issue_195() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-195.png",
|
"tests/files/issue-195.png",
|
||||||
None,
|
None,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -456,9 +449,9 @@ fn issue_426_01() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-426-01.png",
|
"tests/files/issue-426-01.png",
|
||||||
None,
|
None,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -468,9 +461,9 @@ fn issue_426_02() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/issue-426-02.png",
|
"tests/files/issue-426-02.png",
|
||||||
None,
|
None,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::One,
|
BitDepth::One,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use oxipng::{internal_tests::*, RowFilter};
|
use oxipng::internal_tests::*;
|
||||||
use oxipng::{InFile, OutFile};
|
use oxipng::*;
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
const GRAYSCALE: u8 = 0;
|
||||||
|
const RGB: u8 = 2;
|
||||||
|
const INDEXED: u8 = 3;
|
||||||
|
const RGBA: u8 = 6;
|
||||||
|
|
||||||
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
let mut options = oxipng::Options {
|
let mut options = oxipng::Options {
|
||||||
force: true,
|
force: true,
|
||||||
|
|
@ -23,18 +28,18 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
fn test_it_converts(
|
fn test_it_converts(
|
||||||
input: &str,
|
input: &str,
|
||||||
filter: RowFilter,
|
filter: RowFilter,
|
||||||
color_type_in: ColorType,
|
color_type_in: u8,
|
||||||
bit_depth_in: BitDepth,
|
bit_depth_in: BitDepth,
|
||||||
color_type_out: ColorType,
|
color_type_out: u8,
|
||||||
bit_depth_out: BitDepth,
|
bit_depth_out: BitDepth,
|
||||||
) {
|
) {
|
||||||
let input = PathBuf::from(input);
|
let input = PathBuf::from(input);
|
||||||
|
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (output, mut opts) = get_opts(&input);
|
||||||
let png = PngData::new(&input, opts.fix_errors).unwrap();
|
let png = PngData::new(&input, &opts).unwrap();
|
||||||
opts.filter = IndexSet::new();
|
opts.filter = IndexSet::new();
|
||||||
opts.filter.insert(filter);
|
opts.filter.insert(filter);
|
||||||
assert_eq!(png.raw.ihdr.color_type, color_type_in);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
|
||||||
|
|
||||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||||
|
|
@ -44,7 +49,7 @@ fn test_it_converts(
|
||||||
let output = output.path().unwrap();
|
let output = output.path().unwrap();
|
||||||
assert!(output.exists());
|
assert!(output.exists());
|
||||||
|
|
||||||
let png = match PngData::new(output, opts.fix_errors) {
|
let png = match PngData::new(output, &opts) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(x) => {
|
Err(x) => {
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -52,12 +57,10 @@ fn test_it_converts(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(png.raw.ihdr.color_type, color_type_out);
|
assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_out);
|
||||||
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
|
assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
|
||||||
if let Some(palette) = png.raw.palette.as_ref() {
|
if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
|
||||||
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth.as_u8() as usize));
|
assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth as u8));
|
||||||
} else {
|
|
||||||
assert_ne!(png.raw.ihdr.color_type, ColorType::Indexed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_file(output).ok();
|
remove_file(output).ok();
|
||||||
|
|
@ -68,9 +71,9 @@ fn filter_minsum() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/rgb_16_should_be_rgb_16.png",
|
"tests/files/rgb_16_should_be_rgb_16.png",
|
||||||
RowFilter::MinSum,
|
RowFilter::MinSum,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Sixteen,
|
BitDepth::Sixteen,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -80,9 +83,9 @@ fn filter_entropy() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/rgb_8_should_be_rgb_8.png",
|
"tests/files/rgb_8_should_be_rgb_8.png",
|
||||||
RowFilter::Entropy,
|
RowFilter::Entropy,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGB,
|
RGB,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -92,9 +95,9 @@ fn filter_bigrams() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/rgba_8_should_be_rgba_8.png",
|
"tests/files/rgba_8_should_be_rgba_8.png",
|
||||||
RowFilter::Bigrams,
|
RowFilter::Bigrams,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::RGBA,
|
RGBA,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -104,9 +107,9 @@ fn filter_bigent() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/grayscale_8_should_be_grayscale_8.png",
|
"tests/files/grayscale_8_should_be_grayscale_8.png",
|
||||||
RowFilter::BigEnt,
|
RowFilter::BigEnt,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Grayscale,
|
GRAYSCALE,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -116,9 +119,9 @@ fn filter_brute() {
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
"tests/files/palette_8_should_be_palette_8.png",
|
"tests/files/palette_8_should_be_palette_8.png",
|
||||||
RowFilter::Brute,
|
RowFilter::Brute,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
ColorType::Indexed,
|
INDEXED,
|
||||||
BitDepth::Eight,
|
BitDepth::Eight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||