├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-ZLIB ├── README.md ├── benches ├── decode.rs └── encode.rs ├── comparison ├── Cargo.toml ├── benches │ ├── aci_png.rs │ ├── imagefmt.rs │ ├── imagine.rs │ ├── libpng.rs │ ├── lodepng.rs │ └── png.rs └── src │ └── lib.rs ├── examples ├── chunks.rs ├── decode_raster.rs └── encode_raster.rs ├── list.rs ├── res └── icon.png ├── src ├── adam7.rs ├── bitstream.rs ├── chunk.rs ├── chunk │ ├── bkgd.rs │ ├── idat.rs │ ├── iend.rs │ ├── ihdr.rs │ ├── itxt.rs │ ├── phys.rs │ ├── plte.rs │ ├── text.rs │ ├── time.rs │ ├── trns.rs │ ├── unknown.rs │ └── ztxt.rs ├── consts.rs ├── decode.rs ├── decode │ ├── chunks.rs │ ├── error.rs │ ├── steps.rs │ └── steps │ │ └── unfilter.rs ├── decoder.rs ├── encode.rs ├── encode │ ├── chunk_enc.rs │ ├── error.rs │ ├── filter.rs │ └── step_enc.rs ├── encoder.rs ├── lib.rs ├── parsing.rs ├── raster.rs ├── step.rs └── zlib.rs └── tests ├── PngSuite.LICENSE ├── PngSuite.README ├── apng ├── APNG-Fadeout.png ├── APNG-Glass.png ├── APNG-IC1696_by_Jukka_Metsavainio.png ├── APNG-Icos4D.png ├── APNG-Saturn.png ├── APNG-StarV838.png ├── APNG-from-GIF-LostWorld.png ├── APNG-from-GIF-Mouse.png ├── APNG.png ├── Gold.png ├── Newton.png ├── clock.png ├── colors-apng.png ├── diamond-apng.png ├── graham11.png └── o_sample.png ├── png ├── 0.png ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── PngSuite.png ├── bad │ ├── README.md │ ├── bad_iCCP.png │ ├── badadler.png │ ├── badcrc.png │ ├── empty_ancillary_chunks.png │ ├── huge_IDAT.png │ ├── huge_bKGD_chunk.png │ ├── huge_cHRM_chunk.png │ ├── huge_eXIf_chunk.png │ ├── huge_gAMA_chunk.png │ ├── huge_hIST_chunk.png │ ├── huge_iCCP_chunk.png │ ├── huge_iTXt_chunk.png │ ├── huge_juNK_unsafe_to_copy.png │ ├── huge_juNk_safe_to_copy.png │ ├── huge_pCAL_chunk.png │ ├── huge_pHYs_chunk.png │ ├── huge_sCAL_chunk.png │ ├── huge_sPLT_chunk.png │ ├── huge_sRGB_chunk.png │ ├── huge_sTER_chunk.png │ ├── huge_tEXt_chunk.png │ ├── huge_tIME_chunk.png │ └── huge_zTXt_chunk.png ├── fry.png ├── gray.png ├── icon.png ├── noise.png ├── plopgrizzly.png ├── profile.png ├── res.png └── test.png ├── pngsuite-ancillary ├── ccwn2c08.png ├── ccwn3p08.png ├── cdfn2c08.png ├── cdhn2c08.png ├── cdsn2c08.png ├── cdun2c08.png ├── ch1n3p04.png ├── ch2n3p08.png ├── cm0n0g04.png ├── cm7n0g04.png ├── cm9n0g04.png ├── cs3n2c16.png ├── cs3n3p08.png ├── cs5n2c08.png ├── cs5n3p08.png ├── cs8n2c08.png ├── cs8n3p08.png ├── ct0n0g04.png ├── ct1n0g04.png ├── cten0g04.png ├── ctfn0g04.png ├── ctgn0g04.png ├── cthn0g04.png ├── ctjn0g04.png ├── ctzn0g04.png └── exif2c08.png ├── pngsuite-background ├── bgai4a08.png ├── bgai4a16.png ├── bgan6a08.png ├── bgan6a16.png ├── bgbn4a08.png ├── bggn4a16.png ├── bgwn6a08.png └── bgyn6a16.png ├── pngsuite-basic ├── basn0g01.png ├── basn0g02.png ├── basn0g04.png ├── basn0g08.png ├── basn0g16.png ├── basn2c08.png ├── basn2c16.png ├── basn3p01.png ├── basn3p02.png ├── basn3p04.png ├── basn3p08.png ├── basn4a08.png ├── basn4a16.png ├── basn6a08.png └── basn6a16.png ├── pngsuite-chunkorder ├── oi1n0g16.png ├── oi1n2c16.png ├── oi2n0g16.png ├── oi2n2c16.png ├── oi4n0g16.png ├── oi4n2c16.png ├── oi9n0g16.png └── oi9n2c16.png ├── pngsuite-corrupt ├── xc1n0g08.png ├── xc9n2c08.png ├── xcrn0g04.png ├── xcsn0g01.png ├── xd0n2c08.png ├── xd3n2c08.png ├── xd9n2c08.png ├── xdtn0g01.png ├── xhdn0g08.png ├── xlfn0g04.png ├── xs1n0g01.png ├── xs2n0g01.png ├── xs4n0g01.png └── xs7n0g01.png ├── pngsuite-filtering ├── f00n0g08.png ├── f00n2c08.png ├── f01n0g08.png ├── f01n2c08.png ├── f02n0g08.png ├── f02n2c08.png ├── f03n0g08.png ├── f03n2c08.png ├── f04n0g08.png ├── f04n2c08.png └── f99n0g04.png ├── pngsuite-gamma ├── g03n0g16.png ├── g03n2c08.png ├── g03n3p04.png ├── g04n0g16.png ├── g04n2c08.png ├── g04n3p04.png ├── g05n0g16.png ├── g05n2c08.png ├── g05n3p04.png ├── g07n0g16.png ├── g07n2c08.png ├── g07n3p04.png ├── g10n0g16.png ├── g10n2c08.png ├── g10n3p04.png ├── g25n0g16.png ├── g25n2c08.png └── g25n3p04.png ├── pngsuite-interlaced ├── basi0g01.png ├── basi0g02.png ├── basi0g04.png ├── basi0g08.png ├── basi0g16.png ├── basi2c08.png ├── basi2c16.png ├── basi3p01.png ├── basi3p02.png ├── basi3p04.png ├── basi3p08.png ├── basi4a08.png ├── basi4a16.png ├── basi6a08.png └── basi6a16.png ├── pngsuite-oddsizes ├── s01i3p01.png ├── s01n3p01.png ├── s02i3p01.png ├── s02n3p01.png ├── s03i3p01.png ├── s03n3p01.png ├── s04i3p01.png ├── s04n3p01.png ├── s05i3p02.png ├── s05n3p02.png ├── s06i3p02.png ├── s06n3p02.png ├── s07i3p02.png ├── s07n3p02.png ├── s08i3p02.png ├── s08n3p02.png ├── s09i3p02.png ├── s09n3p02.png ├── s32i3p04.png ├── s32n3p04.png ├── s33i3p04.png ├── s33n3p04.png ├── s34i3p04.png ├── s34n3p04.png ├── s35i3p04.png ├── s35n3p04.png ├── s36i3p04.png ├── s36n3p04.png ├── s37i3p04.png ├── s37n3p04.png ├── s38i3p04.png ├── s38n3p04.png ├── s39i3p04.png ├── s39n3p04.png ├── s40i3p04.png └── s40n3p04.png ├── pngsuite-palette ├── pp0n2c16.png ├── pp0n6a08.png ├── ps1n0g08.png ├── ps1n2c16.png ├── ps2n0g08.png └── ps2n2c16.png ├── pngsuite-transparency ├── tbbn0g04.png ├── tbbn2c16.png ├── tbbn3p08.png ├── tbgn2c16.png ├── tbgn3p08.png ├── tbrn2c08.png ├── tbwn0g16.png ├── tbwn3p08.png ├── tbyn3p08.png ├── tm3n3p02.png ├── tp0n0g08.png ├── tp0n2c08.png ├── tp0n3p08.png └── tp1n3p08.png ├── pngsuite-zlib ├── z00n2c08.png ├── z03n2c08.png ├── z06n2c08.png └── z09n2c08.png └── roundtrip.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "11:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: tests 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, macos-latest, windows-latest] 11 | tc: [1.70.0, stable, beta, nightly] 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | components: rust-std 18 | toolchain: ${{ matrix.tc }} 19 | override: true 20 | - uses: actions-rs/cargo@v1 21 | with: 22 | command: test 23 | args: --all --all-features 24 | cross-compile: 25 | runs-on: ${{ matrix.os }} 26 | strategy: 27 | matrix: 28 | os: [ubuntu-latest] 29 | tc: [1.70.0, stable, beta, nightly] 30 | cc: 31 | - aarch64-linux-android 32 | - i686-pc-windows-gnu 33 | - i686-unknown-freebsd 34 | - i686-unknown-linux-gnu 35 | - wasm32-unknown-unknown 36 | - x86_64-apple-darwin 37 | - x86_64-unknown-redox 38 | steps: 39 | - uses: actions/checkout@v2 40 | - uses: actions-rs/toolchain@v1 41 | with: 42 | profile: minimal 43 | components: rust-std 44 | toolchain: ${{ matrix.tc }} 45 | target: ${{ matrix.cc }} 46 | override: true 47 | - uses: actions-rs/cargo@v1 48 | with: 49 | command: build 50 | args: --all-features --target=${{ matrix.cc }} 51 | cross-compile-ios: 52 | runs-on: ${{ matrix.os }} 53 | strategy: 54 | matrix: 55 | os: [macos-latest] 56 | tc: [1.70.0, stable, beta, nightly] 57 | cc: [aarch64-apple-ios] 58 | steps: 59 | - uses: actions/checkout@v2 60 | - uses: actions-rs/toolchain@v1 61 | with: 62 | profile: minimal 63 | components: rust-std 64 | toolchain: ${{ matrix.tc }} 65 | target: ${{ matrix.cc }} 66 | override: true 67 | - uses: actions-rs/cargo@v1 68 | with: 69 | command: build 70 | args: --all-features --target=${{ matrix.cc }} 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /**/*.rs.bk 2 | /**/target/ 3 | /.cargo-ok 4 | /graphic.png 5 | /out.png 6 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Don't deviate too much, just reduce columns and stricter enforcement 2 | edition = "2021" 3 | unstable_features = true 4 | max_width = 80 5 | reorder_impl_items = true 6 | group_imports = "StdExternalCrate" 7 | imports_granularity = "Crate" 8 | newline_style = "Unix" 9 | normalize_doc_attributes = true 10 | wrap_comments = true 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to PNG Pong will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog], and this project adheres to 5 | [Semantic Versioning]. 6 | 7 | ## 0.9.3 - 2024-11-05 8 | ### Changed 9 | - Use the parsenic crate for chunk decoding 10 | 11 | ## 0.9.2 - 2024-08-10 12 | ### Changed 13 | - Updated miniz\_oxide to v0.8 14 | 15 | ## 0.9.1 - 2024-04-03 16 | ### Changed 17 | - Lowered MSRV to 1.70 18 | 19 | ## 0.9.0 - 2024-04-01 20 | ### Changed 21 | - Updated dependencies 22 | - Result type aliases are now more flexible 23 | - Upgraded to the 2021 edition 24 | - Depend on `simd-adler32` crate for speed improvements 25 | - Rename `TextSize` variant to `KeySize` on `encode::Error` and `decode::Error` 26 | 27 | ### Fixed 28 | - Warnings, clippy lints 29 | - Lifted 79 byte restriction on tEXt chunk payload 30 | - Incorrect length of iTXt chunk 31 | 32 | ## 0.8.2 - 2021-05-18 33 | ### Fixed 34 | - Palette PNGs returning empty palette when compiled in release mode. 35 | 36 | ## 0.8.1 - 2021-05-16 37 | ### Fixed 38 | - Not being able to load palette PNGs without a transparency chunk. 39 | 40 | ## 0.8.0 - 2021-02-14 41 | Thanks to [wezm](https://github.com/wezm) for adding these features! 42 | 43 | ### Added 44 | - `chunk::Unknown` 45 | - Implemented `std::error::Error` on `encode::Error` 46 | - Implemented `std::error::Error` on `decode::Error` 47 | 48 | ## 0.7.0 - 2020-09-19 49 | ### Added 50 | - Sealed trait: `AsRaster` 51 | 52 | ### Changed 53 | - `StepEnc.still()` now takes either a reference to a PngRaster or a Raster 54 | 55 | ## 0.6.0 - 2020-07-26 56 | ### Added 57 | - `chunk::ColorType` and `PngRaster` for reading PNGs without conversion 58 | - Lots of chunks to the Chunk API 59 | - `CompressedText` 60 | - `ImageData` 61 | - `ImageEnd` 62 | - `ImageHeader` 63 | - `Palette` 64 | - `Physical` 65 | - `Time` 66 | - `Background` 67 | - `Transparency` 68 | - `encode::Result` 69 | - `decode::Result` 70 | - `Decoder` - A builder for decoder types 71 | - `Encoder` - A builder for encoder types 72 | 73 | ### Changed 74 | - Renamed `EncodeError` -> `encode::Error` 75 | - Renamed `DecodeError` -> `decode::Error` 76 | - Renamed `chunk::TextChunk` -> `chunk::Text` 77 | - Renamed `chunk::ITextChunk` -> `InternationalText` 78 | - Renamed `Frame` -> `Step` 79 | - Renamed `ChunkDecoder` -> `decode::Chunks` 80 | - Renamed `ChunkEncoder` -> `encode::ChunkEnc` 81 | - Renamed `FrameDecoder` -> `decode::Steps` 82 | - Renamed `FrameEncoder` -> `encode::StepEnc` 83 | 84 | ### Removed 85 | - `Format` trait 86 | - `ParseError`, the very lame integer error. 87 | 88 | ### Fixed 89 | - Chunk APIs not working 90 | 91 | ## 0.5.0 - 2020-05-05 92 | ### Changed 93 | - Update to pix 0.13 94 | 95 | ## 0.4.0 - 2020-04-24 96 | ### Changed 97 | - Update to pix 0.12 98 | 99 | ## 0.3.0 - 2020-04-11 100 | ### Changed 101 | - Update to pix 0.11 102 | 103 | ## 0.2.2 - 2020-03-29 104 | ### Fixed 105 | - Docs not building at all 106 | 107 | ## 0.2.1 - 2020-03-29 108 | ### Fixed 109 | - Not all docs showing up on docs.rs 110 | 111 | ## 0.2.0 - 2020-03-29 112 | ### Changed 113 | - Updated pix to 0.10 114 | - Made `ColorType` a public item in the crate 115 | 116 | ## 0.1.0 - 2020-01-01 117 | ### Added 118 | - `Frame` struct 119 | - `Format` trait for pixel formats that can be saved as PNG 120 | 121 | ### Changed 122 | - Replace `RasterDecoder` and `RasterEncoder` with `FrameEncoder` and 123 | FrameDecoder 124 | 125 | ### Removed 126 | - Prelude module 127 | - Re-exports from pix crate 128 | 129 | ## 0.0.2 - 2019-08-03 130 | ### Changed 131 | - Use miniz\_oxide instead of deflate & inflate crates. 132 | 133 | ## 0.0.1 - 2019-07-24 134 | ### Added 135 | - Support for reading writing PNGs. 136 | 137 | [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ 138 | [Semantic Versioning]: https://github.com/AldaronLau/semver/blob/stable/README.md 139 | 140 | 141 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at jeronlau@plopgrizzly.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | This repository accepts contributions. Ideas, questions, feature requests and 3 | bug reports can be filed through GitHub issues. 4 | 5 | Pull Requests are welcome on GitHub. By committing pull requests, you accept 6 | that your code might be modified and reformatted to fit the project coding style 7 | in order to improve the implementation. Contributed code is considered licensed 8 | under the same license as the rest of the project unless explicitly agreed 9 | otherwise. See the `LICENSE-ZLIB` and `LICENSE-APACHE` files. 10 | 11 | Please discuss what you want to see modified before filing a pull request if you 12 | don't want to be doing work that might be rejected. 13 | 14 | ## git workflow 15 | 16 | Please file PR's against the `v0` branch (it's the default, so super easy!). 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "png_pong" 3 | version = "0.9.3" 4 | license = "Apache-2.0 OR Zlib" 5 | description = "A pure Rust PNG/APNG encoder & decoder" 6 | repository = "https://github.com/AldaronLau/png_pong" 7 | documentation = "https://docs.rs/png_pong" 8 | homepage = "https://github.com/AldaronLau/png_pong/releases" 9 | include = [ 10 | "Cargo.toml", 11 | "README.md", 12 | "LICENSE-ZLIB", 13 | "LICENSE-APACHE", 14 | "src/*", 15 | "benches/*", 16 | ] 17 | categories = ["multimedia::encoding", "multimedia::images"] 18 | keywords = ["png", "encoder", "decoder", "apng", "image"] 19 | readme = "README.md" 20 | edition = "2021" 21 | rust-version = "1.70" 22 | 23 | [dependencies.pix] 24 | version = "0.13" 25 | 26 | [dependencies.simd-adler32] 27 | version = "0.3" 28 | 29 | [dependencies.miniz_oxide] 30 | version = "0.8" 31 | features = ["simd"] 32 | 33 | [dependencies.parsenic] 34 | version = "0.2" 35 | 36 | [dependencies.traitful] 37 | version = "0.3" 38 | 39 | [dev-dependencies.criterion] 40 | version = "0.5" 41 | 42 | [package.metadata.docs.rs] 43 | all-features = true 44 | default-target = "x86_64-unknown-linux-gnu" 45 | 46 | [[bench]] 47 | name = "encode" 48 | harness = false 49 | 50 | [[bench]] 51 | name = "decode" 52 | harness = false 53 | 54 | [profile.test] 55 | opt-level = 2 56 | 57 | [profile.dev] 58 | opt-level = 2 59 | 60 | [features] 61 | default = [] 62 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-ZLIB: -------------------------------------------------------------------------------- 1 | Zlib License 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 16 | 2. Altered source versions must be plainly marked as such, and must not be 17 | misrepresented as being the original software. 18 | 19 | 3. This notice may not be removed or altered from any source 20 | distribution. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![PNG Pong] 2 | 3 | #### A pure Rust PNG/APNG encoder & decoder 4 | 5 | [![tests](https://github.com/AldaronLau/png_pong/actions/workflows/ci.yml/badge.svg)](https://github.com/AldaronLau/png_pong/actions/workflows/ci.yml) 6 | [![GitHub commit activity](https://img.shields.io/github/commit-activity/y/AldaronLau/png_pong)](https://github.com/AldaronLau/png_pong/) 7 | [![GitHub contributors](https://img.shields.io/github/contributors/AldaronLau/png_pong)](https://github.com/AldaronLau/png_pong/graphs/contributors) 8 | [![Crates.io](https://img.shields.io/crates/v/png_pong)](https://crates.io/crates/png_pong) 9 | [![Crates.io](https://img.shields.io/crates/d/png_pong)](https://crates.io/crates/png_pong) 10 | [![Crates.io (recent)](https://img.shields.io/crates/dr/png_pong)](https://crates.io/crates/png_pong) 11 | [![Crates.io](https://img.shields.io/crates/l/png_pong)](https://github.com/AldaronLau/png_pong/search?l=Text&q=license) 12 | [![Docs.rs](https://docs.rs/png_pong/badge.svg)](https://docs.rs/png_pong/) 13 | 14 | This is a pure Rust PNG image decoder and encoder taking inspiration from 15 | lodepng. This crate allows easy reading and writing of PNG files without any 16 | system dependencies. 17 | 18 | ### Why another PNG crate? 19 | 20 | These are the 4 Rust PNG encoder/decoder crates I know of: 21 | - [png] - The one everyone uses (used to be able to load less pngs than 22 | png\_pong and slower, but has caught up). 23 | - [lodepng] - Loads all the PNGs, code is ported from C, therefore code is hard 24 | read & maintain, also uses slow implementation of deflate/inflate algorithm. 25 | - [imagefmt] - Abandoned, and limited in what files it can open, but with a lot 26 | less lines of code. 27 | - [imagine] - PNG decoding only. 28 | 29 | Originally I made the [aci\_png] based on imagefmt, and intended to add more 30 | features. At the time, I didn't want to write a PNG encoder/decoder from 31 | scratch so I decided to take `lodepng` which has more features (and more low 32 | level features) and clean up the code, upgrade to 2018 edition of Rust, depend 33 | on the miniz\_oxide crate (because it can decompress faster than lodepng's 34 | INFLATE implementation) and get rid of the libc dependency so it *actually* 35 | becomes pure Rust (lodepng claimed to be, but called C's malloc and free). 36 | Then, I rewrote the entire library, based on [gift] and [pix]. 37 | 38 | ### Goals 39 | 40 | - Forbid unsafe. 41 | - APNG support as iterator. 42 | - Fast. 43 | - Compatible with pix / gift-style API. 44 | - Load all PNG files crushed with pngcrush. 45 | - Save crushed PNG files. 46 | - Clean, well-documented, concise code. 47 | - Implement all completed, non-deprecated chunks in the 48 | [PNG 1.2 Specification], including the [PNG 1.2 Extensions] and the 49 | [APNG Specification] 50 | 51 | ### TODO 52 | 53 | - Implement APNG reading. 54 | - Implement Chunk reading (with all the different chunk structs). 55 | - StepDecoder should wrap StepDecoder, RasterEncoder should wrap ChunkEncoder 56 | - More test cases to test against. 57 | 58 | ### Benchmarks And Comparisons 59 | 60 | TODO: Update, ran on older png\_pong and Rust 1.52.1 61 | 62 | Using Rust 1.52.1, criterion and 4 different PNG sizes with PNGs from 63 | "./tests/png/" (units are: us / microseconds). I stopped anything that was 64 | predicted to take longer than a half hour with criterion with the message 65 | "TIMEOUT". 66 | 67 | - sRGB 1x1: Uses `tests/png/profile.png` 68 | - sRGBA 1x1: Uses `tests/png/test.png` 69 | - sRGB 64x64: Uses `test/png/4.png` 70 | - sRGBA 64x64: Uses `tests/png/res.png` 71 | - sRGB 256x256: `tests/png/PngSuite.png` 72 | - sRGBA 256x256: Uses `tests/png/icon.png` 73 | - sRGB 4096x4096: `tests/png/plopgrizzly.png` 74 | - sRGBA 4096x4096: Uses `tests/png/noise.png` 75 | 76 | #### Encoder 77 | 78 | | Library | sRGB 1x1 | sRGBA 1x1 | sRGB 64x64 | sRGBA 64x64 | sRGB 256x256 | sRGBA 256x256 | sRGB 4096x4096 | sRGBA 4096x4096 | 79 | |------------|----------|-----------|------------|-------------|--------------|---------------|----------------|-----------------| 80 | | png_pong | 41.956 | 8.2661 | 1\_025.7 | 700.80 | 2\_646.1 | 5\_061.5 | 587\_320 | 3\_587\_100 | 81 | | png | 29.538 | 9.4122 | 213.49 | 203.02 | 944.99 | 1\_534.3 | 201\_680 | 1\_535\_300 | 82 | | lodepng | 59.989 | 10.700 | 1\_399.3 | 982.63 | 3\_391.3 | 6\_664.7 | 831\_190 | 3\_394\_900 | 83 | | imagefmt | 8.7942 | 8.7399 | 211.01 | 177.82 | 901.22 | 1\_569.4 | 218\_550 | 1\_285\_700 | 84 | | imagine | --- | --- | --- | --- | --- | --- | --- | --- | 85 | | aci_png | FAILS | 30.987 | FAILS | 289.24 | FAILS | 2\_298.1 | FAILS | 2\_135\_400 | 86 | | libpng-sys | 6.8443 | 2.9461 | 1\_613.5 | 769.70 | 2\_261.1 | 4\_745.2 | 520\_770 | 2\_926\_900 | 87 | 88 | #### Decoder 89 | 90 | | Library | sRGB 1x1 | sRGBA 1x1 | sRGB 64x64 | sRGBA 64x64 | sRGB 256x256 | sRGBA 256x256 | sRGB 4096x4096 | sRGBA 4096x4096 | 91 | |------------|----------|-----------|------------|-------------|--------------|---------------|----------------|-----------------| 92 | | png_pong | 7.7520 | 3.9459 | 77.981 | 99.384 | 752.95 | 901.98 | 178\_880 | 570\_200 | 93 | | png | 8.1195 | 6.6107 | 54.834 | 71.873 | 643.09 | 686.29 | 128\_000 | 355\_080 | 94 | | lodepng | 5.8958 | 5.4527 | 77.050 | 97.762 | 882.83 | 982.76 | 230\_570 | 563\_210 | 95 | | imagefmt | 4.2864 | 4.8706 | 74.715 | 82.026 | 566.86 | 758.27 | 69\_465 | 545\_060 | 96 | | imagine | 2.8809 | 0.44822 | 3\_202.3 | 2\_266.4 | 2\_056.1 | 10\_753 | 442\_750 | 27\_944\_000 | 97 | | aci_png | 5.0243 | 4.3516 | 201.29 | 174.30 | 1\_500.6 | 1\_689.8 | 398\_340 | 1\_323\_600 | 98 | | libpng-sys | 3.6011 | 0.48747 | 1.8175 | 0.67344 | 25.809 | 4.4175 | 19\_400 | 18\_262 | 99 | 100 | ## Table of Contents 101 | 102 | - [API] 103 | - [Features] 104 | - [Upgrade] 105 | - [License] 106 | - [Contribution] 107 | 108 | ## API 109 | 110 | API documentation can be found on [docs.rs]. 111 | 112 | ## Features 113 | 114 | There are no optional features. 115 | 116 | ## Upgrade 117 | 118 | You can use the [changelog] to facilitate upgrading this crate as a dependency. 119 | 120 | ## MSRV 121 | 122 | The current MSRV is Rust 1.70. 123 | 124 | MSRV is updated according to the [Ardaku MSRV guidelines]. 125 | 126 | ## License 127 | 128 | Copyright © 2019-2024 The PNG Pong Crate Contributor(s) 129 | 130 | Licensed under either of 131 | - Apache License, Version 2.0, ([LICENSE-APACHE] or 132 | ) 133 | - Zlib License, ([LICENSE-ZLIB] or ) 134 | 135 | at your option. 136 | 137 | ### Contribution 138 | 139 | Unless you explicitly state otherwise, any contribution intentionally submitted 140 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 141 | dual licensed as above, without any additional terms or conditions. 142 | 143 | Before contributing, check out the [contribution guidelines], and, as always, 144 | make sure to always follow the [code of conduct]. 145 | 146 | [Ardaku MSRV guidelines]: https://github.com/ardaku/.github/blob/v1/profile/MSRV.md 147 | [PNG Pong]: https://raw.githubusercontent.com/AldaronLau/png_pong/v0/res/icon.png 148 | [code of conduct]: https://github.com/AldaronLau/png_pong/blob/v0/CODE_OF_CONDUCT.md 149 | [contribution guidelines]: https://github.com/AldaronLau/png_pong/blob/v0/CONTRIBUTING.md 150 | [LICENSE-APACHE]: https://github.com/AldaronLau/png_pong/blob/v0/LICENSE-APACHE 151 | [LICENSE-ZLIB]: https://github.com/AldaronLau/png_pong/blob/v0/LICENSE-ZLIB 152 | [changelog]: https://github.com/AldaronLau/png_pong/blob/v0/CHANGELOG.md 153 | [docs.rs]: https://docs.rs/png_pong 154 | [API]: #api 155 | [Features]: #features 156 | [Upgrade]: #upgrade 157 | [License]: #license 158 | [Contribution]: #contribution 159 | [PNG 1.2 Specification]: http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html 160 | [PNG 1.2 Extensions]: https://pmt.sourceforge.io/specs/pngext-1.2.0-pdg-h20.html 161 | [APNG Specification]: https://wiki.mozilla.org/APNG_Specification 162 | [gift]: https://crates.io/crates/gift 163 | [pix]: https://crates.io/crates/pix 164 | [aci\_png]: https://crates.io/crates/aci_png 165 | [png]: https://crates.io/crates/png 166 | [lodepng]: https://crates.io/crates/lodepng 167 | [imagefmt]: https://crates.io/crates/imagefmt 168 | [imagine]: https://crates.io/crates/imagine 169 | -------------------------------------------------------------------------------- /benches/decode.rs: -------------------------------------------------------------------------------- 1 | include!("../list.rs"); 2 | 3 | #[macro_use] 4 | extern crate criterion; 5 | 6 | fn decode(c: &mut criterion::Criterion) { 7 | let mut group = c.benchmark_group("decode"); 8 | group.sample_size(10); 9 | 10 | for file in FILE_PATHS.iter().copied() { 11 | let data = std::fs::read(file).expect("Failed to open PNG"); 12 | 13 | group.bench_function(file, |b| { 14 | b.iter(|| { 15 | let data = std::io::Cursor::new(data.as_slice()); 16 | let decoder = 17 | png_pong::Decoder::new(data).expect("Not PNG").into_steps(); 18 | let png_pong::Step { raster, delay: _ } = decoder 19 | .last() 20 | .expect("No frames in PNG") 21 | .expect("PNG parsing error"); 22 | let _ = raster; 23 | }) 24 | }); 25 | } 26 | } 27 | 28 | criterion_group!(benches, decode); 29 | criterion_main!(benches); 30 | -------------------------------------------------------------------------------- /benches/encode.rs: -------------------------------------------------------------------------------- 1 | include!("../list.rs"); 2 | 3 | #[macro_use] 4 | extern crate criterion; 5 | 6 | fn encode(c: &mut criterion::Criterion) { 7 | let mut group = c.benchmark_group("encode"); 8 | group.sample_size(10); 9 | 10 | for file in FILE_PATHS.iter().copied() { 11 | let data = std::fs::read(file).expect("Failed to open PNG"); 12 | let data = std::io::Cursor::new(data); 13 | let decoder = 14 | png_pong::Decoder::new(data).expect("Not PNG").into_steps(); 15 | let step = decoder 16 | .last() 17 | .expect("No frames in PNG") 18 | .expect("PNG parsing error"); 19 | 20 | group.bench_function(file, |b| { 21 | b.iter(|| { 22 | let mut out_data = Vec::new(); 23 | let mut encoder = 24 | png_pong::Encoder::new(&mut out_data).into_step_enc(); 25 | encoder.encode(&step).expect("Failed to add frame"); 26 | }) 27 | }); 28 | } 29 | } 30 | 31 | criterion_group!(benches, encode); 32 | criterion_main!(benches); 33 | -------------------------------------------------------------------------------- /comparison/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "comparison" 3 | version = "0.1.0" 4 | authors = ["Jeron Aldaron Lau "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | png_pong = { path = "../" } 11 | png = "=0.16.8" 12 | imagine = "=0.0.5" 13 | lodepng = "=3.4.5" 14 | imagefmt = "=4.0.0" 15 | aci_png = "=0.8.0-pre0" 16 | libpng-sys = "=1.1.8" 17 | pix = "=0.13.1" 18 | afi = "=0.8.0-pre0" 19 | 20 | [dev-dependencies.criterion] 21 | version = "0.3" 22 | 23 | [[bench]] 24 | name = "png" 25 | harness = false 26 | 27 | [[bench]] 28 | name = "lodepng" 29 | harness = false 30 | 31 | [[bench]] 32 | name = "imagefmt" 33 | harness = false 34 | 35 | [[bench]] 36 | name = "imagine" 37 | harness = false 38 | 39 | [[bench]] 40 | name = "aci_png" 41 | harness = false 42 | 43 | [[bench]] 44 | name = "libpng" 45 | harness = false 46 | -------------------------------------------------------------------------------- /comparison/benches/aci_png.rs: -------------------------------------------------------------------------------- 1 | use comparison::FILE_PATHS; 2 | 3 | use afi::EncoderV; 4 | 5 | #[macro_use] 6 | extern crate criterion; 7 | 8 | fn decode(c: &mut criterion::Criterion) { 9 | let mut group = c.benchmark_group("decode_aci"); 10 | group.sample_size(10); 11 | 12 | for (i, file) in FILE_PATHS.iter().copied().enumerate() { 13 | let data = std::fs::read(file).expect("Failed to open PNG"); 14 | let data = data.as_slice(); 15 | 16 | if i % 2 == 0 { 17 | group.bench_function(file, |b| { 18 | b.iter(|| { 19 | let video = aci_png::decode(&data, afi::ColorChannels::Srgb) 20 | .expect("Failed to load PNG"); 21 | let _ = video; 22 | }) 23 | }); 24 | } else { 25 | group.bench_function(file, |b| { 26 | b.iter(|| { 27 | let video = aci_png::decode(&data, afi::ColorChannels::Srgba) 28 | .expect("Failed to load PNG"); 29 | let _ = video; 30 | }) 31 | }); 32 | } 33 | } 34 | } 35 | 36 | fn encode(c: &mut criterion::Criterion) { 37 | let mut group = c.benchmark_group("encode_aci"); 38 | group.sample_size(10); 39 | 40 | for (i, file) in FILE_PATHS.iter().copied().enumerate() { 41 | let data = std::fs::read(file).expect("Failed to open PNG"); 42 | let data = std::io::Cursor::new(data); 43 | let decoder = 44 | png_pong::Decoder::new(data).expect("Not PNG").into_steps(); 45 | let png_pong::Step { raster, .. } = decoder 46 | .last() 47 | .expect("No frames in PNG") 48 | .expect("PNG parsing error"); 49 | if i % 2 == 0 { 50 | let _raster = match raster { 51 | png_pong::PngRaster::Rgb8(raster) => raster, 52 | _ => unreachable!(), 53 | }; 54 | /* 55 | group.bench_function(file, |b| { 56 | b.iter(|| { 57 | let raster = match step.raster { 58 | png_pong::PngRaster::Rgb8(ok) => ok, 59 | _ => unreachable!(), 60 | }; 61 | c.bench_function(file, |b| { 62 | b.iter(|| { 63 | let mut encoder = aci_png::PngEncoder::new(&afi::Video::new(afi::ColorChannels::Srgb, (raster.width() as u16, raster.height() as u16), 1)); 64 | encoder.run(&afi::VFrame(raster.as_u8_slice().to_vec())); 65 | let out_data = encoder.end(); 66 | let _ = out_data; 67 | }) 68 | }); 69 | }) 70 | });*/ 71 | } else { 72 | let raster = match raster { 73 | png_pong::PngRaster::Rgba8(raster) => raster, 74 | _ => unreachable!(), 75 | }; 76 | 77 | group.bench_function(file, |b| { 78 | b.iter(|| { 79 | let mut encoder = aci_png::PngEncoder::new(&afi::Video::new( 80 | afi::ColorChannels::Srgba, 81 | (raster.width() as u16, raster.height() as u16), 82 | 1, 83 | )); 84 | encoder.run(&afi::VFrame(raster.as_u8_slice().to_vec())); 85 | let out_data = encoder.end(); 86 | let _ = out_data; 87 | }) 88 | }); 89 | }; 90 | } 91 | } 92 | 93 | criterion_group!(benches, encode, decode); 94 | criterion_main!(benches); 95 | -------------------------------------------------------------------------------- /comparison/benches/imagefmt.rs: -------------------------------------------------------------------------------- 1 | use comparison::FILE_PATHS; 2 | 3 | #[macro_use] 4 | extern crate criterion; 5 | 6 | fn decode(c: &mut criterion::Criterion) { 7 | let mut group = c.benchmark_group("decode_imagefmt"); 8 | group.sample_size(10); 9 | 10 | for file in FILE_PATHS.iter().copied() { 11 | let data = std::fs::read(file).expect("Failed to open PNG"); 12 | let data = data.as_slice(); 13 | 14 | group.bench_function(file, |b| { 15 | b.iter(|| { 16 | let mut data = std::io::Cursor::new(data); 17 | let image = imagefmt::read_from(&mut data, imagefmt::ColFmt::Auto) 18 | .expect("Failed to decode"); 19 | let _ = image; 20 | }) 21 | }); 22 | } 23 | } 24 | 25 | fn encode(c: &mut criterion::Criterion) { 26 | let mut group = c.benchmark_group("encode_imagefmt"); 27 | group.sample_size(10); 28 | 29 | for (i, file) in FILE_PATHS.iter().copied().enumerate() { 30 | let data = std::fs::read(file).expect("Failed to open PNG"); 31 | let data = std::io::Cursor::new(data); 32 | let decoder = 33 | png_pong::Decoder::new(data).expect("Not PNG").into_steps(); 34 | let png_pong::Step { raster, .. } = decoder 35 | .last() 36 | .expect("No frames in PNG") 37 | .expect("PNG parsing error"); 38 | if i % 2 == 0 { 39 | let raster = match raster { 40 | png_pong::PngRaster::Rgb8(raster) => raster, 41 | _ => unreachable!(), 42 | }; 43 | 44 | group.bench_function(file, |b| { 45 | b.iter(|| { 46 | // There's no writer API, so this'll have to do. 47 | imagefmt::write( 48 | "/tmp/imagefmt.png", 49 | raster.width() as usize, 50 | raster.height() as usize, 51 | imagefmt::ColFmt::RGB, 52 | raster.as_u8_slice(), 53 | imagefmt::ColType::Auto, 54 | ) 55 | .unwrap(); 56 | }) 57 | }); 58 | } else { 59 | let raster = match raster { 60 | png_pong::PngRaster::Rgba8(raster) => raster, 61 | _ => unreachable!(), 62 | }; 63 | 64 | group.bench_function(file, |b| { 65 | b.iter(|| { 66 | // There's no writer API, so this'll have to do. 67 | imagefmt::write( 68 | "/tmp/imagefmt.png", 69 | raster.width() as usize, 70 | raster.height() as usize, 71 | imagefmt::ColFmt::RGBA, 72 | raster.as_u8_slice(), 73 | imagefmt::ColType::Auto, 74 | ) 75 | .unwrap(); 76 | }) 77 | }); 78 | }; 79 | } 80 | } 81 | 82 | criterion_group!(benches, encode, decode); 83 | criterion_main!(benches); 84 | -------------------------------------------------------------------------------- /comparison/benches/imagine.rs: -------------------------------------------------------------------------------- 1 | use comparison::FILE_PATHS; 2 | 3 | #[macro_use] 4 | extern crate criterion; 5 | 6 | fn decode(c: &mut criterion::Criterion) { 7 | let mut group = c.benchmark_group("decode_imagine"); 8 | group.sample_size(10); 9 | 10 | for file in FILE_PATHS.iter().copied() { 11 | let data = std::fs::read(file).expect("Failed to open PNG"); 12 | let data = data.as_slice(); 13 | 14 | group.bench_function(file, |b| { 15 | b.iter(|| { 16 | let image = imagine::png::parse_png_rgba8(&data) 17 | .expect("Failed to decode with imagine"); 18 | let _ = image; 19 | }) 20 | }); 21 | } 22 | } 23 | 24 | criterion_group!(benches, decode); 25 | criterion_main!(benches); 26 | -------------------------------------------------------------------------------- /comparison/benches/libpng.rs: -------------------------------------------------------------------------------- 1 | use comparison::FILE_PATHS; 2 | 3 | #[macro_use] 4 | extern crate criterion; 5 | 6 | fn encode_sys(raster: &png_pong::PngRaster, alpha: bool) { 7 | if alpha { 8 | let raster = match raster { 9 | png_pong::PngRaster::Rgba8(ok) => ok, 10 | _ => unreachable!(), 11 | }; 12 | 13 | // 1. Declare png_image struct, 2. Set members to describe image 14 | let mut png_image = libpng_sys::ffi::png_image { 15 | opaque: std::ptr::null_mut(), 16 | version: libpng_sys::ffi::PNG_IMAGE_VERSION as u32, 17 | width: raster.width(), 18 | height: raster.height(), 19 | format: libpng_sys::ffi::PNG_FORMAT_RGBA as u32, 20 | flags: 0, 21 | colormap_entries: 0, 22 | warning_or_error: 0, 23 | message: [0; 64], 24 | }; 25 | // 3. Call png_image_write... 26 | let memory: *mut std::ffi::c_void = std::ptr::null_mut(); 27 | let mut memory_bytes = 0; 28 | let _r = unsafe { libpng_sys::ffi::png_image_write_to_memory( 29 | &mut png_image, 30 | memory, 31 | &mut memory_bytes, 32 | 0, 33 | raster.as_u8_slice().as_ptr().cast(), 34 | 0, // raster.width() as i32 * 4, // 0 probably has the same effect. 35 | std::ptr::null(), 36 | ) }; 37 | } else { 38 | let raster = match raster { 39 | png_pong::PngRaster::Rgb8(ok) => ok, 40 | _ => unreachable!(), 41 | }; 42 | 43 | // 1. Declare png_image struct, 2. Set members to describe image 44 | let mut png_image = libpng_sys::ffi::png_image { 45 | opaque: std::ptr::null_mut(), 46 | version: libpng_sys::ffi::PNG_IMAGE_VERSION as u32, 47 | width: raster.width(), 48 | height: raster.height(), 49 | format: libpng_sys::ffi::PNG_FORMAT_RGB as u32, 50 | flags: 0, 51 | colormap_entries: 0, 52 | warning_or_error: 0, 53 | message: [0; 64], 54 | }; 55 | // 3. Call png_image_write... 56 | let mut memory = Vec::with_capacity(100_000_000); 57 | let mut memory_bytes = 100_000_000; 58 | let _r = unsafe { libpng_sys::ffi::png_image_write_to_memory( 59 | &mut png_image, 60 | memory.as_mut_ptr(), 61 | &mut memory_bytes, 62 | 0, 63 | raster.as_u8_slice().as_ptr().cast(), 64 | 0, // raster.width() as i32 * 3, // 0 probably has the same effect. 65 | std::ptr::null(), 66 | ) }; 67 | } 68 | } 69 | 70 | fn decode_sys(data: &[u8], alpha: bool) { 71 | // 1. Declare png_image struct 72 | let mut png_image = libpng_sys::ffi::png_image { 73 | opaque: std::ptr::null_mut(), 74 | version: libpng_sys::ffi::PNG_IMAGE_VERSION as u32, 75 | width: 0, 76 | height: 0, 77 | format: 0, 78 | flags: 0, 79 | colormap_entries: 0, 80 | warning_or_error: 0, 81 | message: [0; 64], 82 | }; 83 | // 2. Begin read 84 | let _r = unsafe {libpng_sys::ffi::png_image_begin_read_from_memory( 85 | &mut png_image, 86 | data.as_ptr().cast(), 87 | data.len(), 88 | ) }; 89 | if alpha { 90 | // 3. Set required sample format 91 | png_image.format = libpng_sys::ffi::PNG_FORMAT_RGBA as u32; 92 | // 4. Allocate buffer for image 93 | let mut raster = pix::Raster::::with_clear( 94 | png_image.width, 95 | png_image.height, 96 | ); 97 | // 5. Call png_image_finish_read 98 | let row_stride = png_image.width as i32; 99 | let bg = libpng_sys::ffi::png_color { 100 | red: 0, 101 | green: 0, 102 | blue: 0, 103 | }; 104 | let _r = unsafe { libpng_sys::ffi::png_image_finish_read( 105 | &mut png_image, 106 | &bg, 107 | raster.as_u8_slice_mut().as_mut_ptr().cast(), 108 | row_stride, 109 | std::ptr::null_mut(), 110 | ) }; 111 | let _ = raster; 112 | } else { 113 | // 3. Set required sample format 114 | png_image.format = libpng_sys::ffi::PNG_FORMAT_RGB as u32; 115 | // 4. Allocate buffer for image 116 | let mut raster = pix::Raster::::with_clear( 117 | png_image.width, 118 | png_image.height, 119 | ); 120 | // 5. Call png_image_finish_read 121 | let row_stride = png_image.width as i32; 122 | let bg = libpng_sys::ffi::png_color { 123 | red: 0, 124 | green: 0, 125 | blue: 0, 126 | }; 127 | let _r = unsafe { libpng_sys::ffi::png_image_finish_read( 128 | &mut png_image, 129 | &bg, 130 | raster.as_u8_slice_mut().as_mut_ptr().cast(), 131 | row_stride, 132 | std::ptr::null_mut(), 133 | ) }; 134 | let _ = raster; 135 | } 136 | } 137 | 138 | fn decode(c: &mut criterion::Criterion) { 139 | let mut group = c.benchmark_group("decode_libpng"); 140 | group.sample_size(10); 141 | 142 | for (i, file) in FILE_PATHS.iter().copied().enumerate() { 143 | let data = std::fs::read(file).expect("Failed to open PNG"); 144 | let data = data.as_slice(); 145 | 146 | if i % 2 == 0 { 147 | group.bench_function(file, |b| { 148 | b.iter(|| { 149 | decode_sys(data, false /* rgb */ ) 150 | }) 151 | }); 152 | } else { 153 | group.bench_function(file, |b| { 154 | b.iter(|| { 155 | decode_sys(data, true /* rbga */ ) 156 | }) 157 | }); 158 | } 159 | } 160 | } 161 | 162 | fn encode(c: &mut criterion::Criterion) { 163 | let mut group = c.benchmark_group("encode_libpng"); 164 | group.sample_size(10); 165 | 166 | for (i, file) in FILE_PATHS.iter().copied().enumerate() { 167 | let data = std::fs::read(file).expect("Failed to open PNG"); 168 | let data = std::io::Cursor::new(data); 169 | let decoder = 170 | png_pong::Decoder::new(data).expect("Not PNG").into_steps(); 171 | let png_pong::Step { raster, .. } = decoder 172 | .last() 173 | .expect("No frames in PNG") 174 | .expect("PNG parsing error"); 175 | if i % 2 == 0 { 176 | group.bench_function(file, |b| { 177 | b.iter(|| { 178 | encode_sys(&raster, false) 179 | }) 180 | }); 181 | } else { 182 | group.bench_function(file, |b| { 183 | b.iter(|| { 184 | encode_sys(&raster, true) 185 | }) 186 | }); 187 | }; 188 | } 189 | } 190 | 191 | criterion_group!(benches, encode, decode); 192 | criterion_main!(benches); 193 | -------------------------------------------------------------------------------- /comparison/benches/lodepng.rs: -------------------------------------------------------------------------------- 1 | use comparison::FILE_PATHS; 2 | 3 | #[macro_use] 4 | extern crate criterion; 5 | 6 | fn decode(c: &mut criterion::Criterion) { 7 | let mut group = c.benchmark_group("decode_lodepng"); 8 | group.sample_size(10); 9 | 10 | for (i, file) in FILE_PATHS.iter().copied().enumerate() { 11 | let data = std::fs::read(file).expect("Failed to open PNG"); 12 | let data = data.as_slice(); 13 | 14 | if i % 2 == 0 { 15 | group.bench_function(file, |b| { 16 | b.iter(|| { 17 | let image = 18 | lodepng::decode_memory(data, lodepng::ColorType::RGBA, 8) 19 | .expect("Failed to decode with lodepng"); 20 | let _ = image; 21 | }) 22 | }); 23 | } else { 24 | group.bench_function(file, |b| { 25 | b.iter(|| { 26 | let image = 27 | lodepng::decode_memory(data, lodepng::ColorType::RGB, 8) 28 | .expect("Failed to decode with lodepng"); 29 | let _ = image; 30 | }) 31 | }); 32 | } 33 | } 34 | } 35 | 36 | fn encode(c: &mut criterion::Criterion) { 37 | let mut group = c.benchmark_group("encode_lodepng"); 38 | group.sample_size(10); 39 | 40 | for (i, file) in FILE_PATHS.iter().copied().enumerate() { 41 | let data = std::fs::read(file).expect("Failed to open PNG"); 42 | let data = std::io::Cursor::new(data); 43 | let decoder = 44 | png_pong::Decoder::new(data).expect("Not PNG").into_steps(); 45 | let png_pong::Step { raster, .. } = decoder 46 | .last() 47 | .expect("No frames in PNG") 48 | .expect("PNG parsing error"); 49 | if i % 2 == 0 { 50 | let raster = match raster { 51 | png_pong::PngRaster::Rgb8(raster) => raster, 52 | _ => unreachable!(), 53 | }; 54 | 55 | group.bench_function(file, |b| { 56 | b.iter(|| { 57 | let out_data = lodepng::encode_memory( 58 | raster.as_u8_slice(), 59 | raster.width() as usize, 60 | raster.height() as usize, 61 | lodepng::ColorType::RGB, 62 | 8, 63 | ) 64 | .expect("Failed to encode with lodepng"); 65 | let _ = out_data; 66 | }) 67 | }); 68 | } else { 69 | let raster = match raster { 70 | png_pong::PngRaster::Rgba8(raster) => raster, 71 | _ => unreachable!(), 72 | }; 73 | 74 | group.bench_function(file, |b| { 75 | b.iter(|| { 76 | let out_data = lodepng::encode_memory( 77 | raster.as_u8_slice(), 78 | raster.width() as usize, 79 | raster.height() as usize, 80 | lodepng::ColorType::RGBA, 81 | 8, 82 | ) 83 | .expect("Failed to encode with lodepng"); 84 | let _ = out_data; 85 | }) 86 | }); 87 | }; 88 | } 89 | } 90 | 91 | criterion_group!(benches, encode, decode); 92 | criterion_main!(benches); 93 | -------------------------------------------------------------------------------- /comparison/benches/png.rs: -------------------------------------------------------------------------------- 1 | use comparison::FILE_PATHS; 2 | 3 | #[macro_use] 4 | extern crate criterion; 5 | 6 | fn decode(c: &mut criterion::Criterion) { 7 | let mut group = c.benchmark_group("decode_png"); 8 | group.sample_size(10); 9 | 10 | for file in FILE_PATHS.iter().copied() { 11 | let data = std::fs::read(file).expect("Failed to open PNG"); 12 | let data = data.as_slice(); 13 | 14 | group.bench_function(file, |b| { 15 | b.iter(|| { 16 | let data = std::io::Cursor::new(data); 17 | let decoder = png::Decoder::new(data); 18 | let (info, mut reader) = decoder.read_info().unwrap(); 19 | let mut buf = vec![0; info.buffer_size()]; 20 | reader.next_frame(&mut buf).unwrap(); 21 | let _ = buf; 22 | }) 23 | }); 24 | } 25 | } 26 | 27 | fn encode(c: &mut criterion::Criterion) { 28 | let mut group = c.benchmark_group("encode_png"); 29 | group.sample_size(10); 30 | 31 | for (i, file) in FILE_PATHS.iter().copied().enumerate() { 32 | let data = std::fs::read(file).expect("Failed to open PNG"); 33 | let data = std::io::Cursor::new(data); 34 | let decoder = 35 | png_pong::Decoder::new(data).expect("Not PNG").into_steps(); 36 | let png_pong::Step { raster, .. } = decoder 37 | .last() 38 | .expect("No frames in PNG") 39 | .expect("PNG parsing error"); 40 | if i % 2 == 0 { 41 | let raster = match raster { 42 | png_pong::PngRaster::Rgb8(raster) => raster, 43 | _ => unreachable!(), 44 | }; 45 | 46 | group.bench_function(file, |b| { 47 | b.iter(|| { 48 | let mut out_data = Vec::new(); 49 | let mut encoder = png::Encoder::new( 50 | &mut out_data, 51 | raster.width(), 52 | raster.height(), 53 | ); 54 | encoder.set_color(png::ColorType::RGB); 55 | encoder.set_depth(png::BitDepth::Eight); 56 | let mut writer = encoder.write_header().unwrap(); 57 | writer.write_image_data(&raster.as_u8_slice()).unwrap(); 58 | }) 59 | }); 60 | } else { 61 | let raster = match raster { 62 | png_pong::PngRaster::Rgba8(raster) => raster, 63 | _ => unreachable!(), 64 | }; 65 | 66 | group.bench_function(file, |b| { 67 | b.iter(|| { 68 | let mut out_data = Vec::new(); 69 | let mut encoder = png::Encoder::new( 70 | &mut out_data, 71 | raster.width(), 72 | raster.height(), 73 | ); 74 | encoder.set_color(png::ColorType::RGBA); 75 | encoder.set_depth(png::BitDepth::Eight); 76 | let mut writer = encoder.write_header().unwrap(); 77 | writer.write_image_data(&raster.as_u8_slice()).unwrap(); 78 | }) 79 | }); 80 | }; 81 | } 82 | } 83 | 84 | criterion_group!(benches, encode, decode); 85 | criterion_main!(benches); 86 | -------------------------------------------------------------------------------- /comparison/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub const FILE_PATHS: &[&str] = &[ 2 | "../tests/png/profile.png", 3 | "../tests/png/test.png", 4 | "../tests/png/4.png", 5 | "../tests/png/res.png", 6 | "../tests/png/PngSuite.png", 7 | "../tests/png/icon.png", 8 | "../tests/png/plopgrizzly.png", 9 | "../tests/png/noise.png", 10 | ]; 11 | -------------------------------------------------------------------------------- /examples/chunks.rs: -------------------------------------------------------------------------------- 1 | use std::{fs::File, io::BufReader}; 2 | 3 | use png_pong::{decode::Error, Decoder}; 4 | 5 | fn main() { 6 | let reader = BufReader::new(File::open("res/icon.png").unwrap()); 7 | for chunk in Decoder::new(reader).expect("Not a PNG file").into_chunks() { 8 | match chunk { 9 | Ok(c) => println!("Chunk {:?}", c), 10 | Err(e) => match e { 11 | Error::UnknownChunkType(bytes) => println!( 12 | "Unknown Chunk: {:?}", 13 | String::from_utf8_lossy(&bytes) 14 | ), 15 | e => panic!("Other Error: {:?}", e), 16 | }, 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/decode_raster.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let data: &[u8] = 3 | &std::fs::read("res/icon.png").expect("Failed to open PNG"); 4 | let decoder = png_pong::Decoder::new(data).expect("Not PNG").into_steps(); 5 | let png_pong::Step { raster: _, .. } = decoder 6 | .last() 7 | .expect("No frames in PNG") 8 | .expect("PNG parsing error"); 9 | } 10 | -------------------------------------------------------------------------------- /examples/encode_raster.rs: -------------------------------------------------------------------------------- 1 | use std::{fs::File, io::BufWriter}; 2 | 3 | use pix::{hwb::SHwb8, rgb::SRgb8, Raster}; 4 | use png_pong::Encoder; 5 | 6 | fn main() { 7 | let mut r = Raster::with_clear(256, 256); 8 | for (y, row) in r.rows_mut(()).enumerate() { 9 | for (x, p) in row.iter_mut().enumerate() { 10 | let h = ((x + y) >> 1) as u8; 11 | let w = y.saturating_sub(x) as u8; 12 | let b = x.saturating_sub(y) as u8; 13 | *p = SHwb8::new(h, w, b); 14 | } 15 | } 16 | // Convert to SRgb8 pixel format 17 | let raster = Raster::::with_raster(&r); 18 | 19 | // Save PNG File Out 20 | let writer = BufWriter::new(File::create("out.png").unwrap()); 21 | let mut encoder = Encoder::new(writer).into_step_enc(); 22 | encoder.still(&raster).expect("Failed to write PNG"); 23 | } 24 | -------------------------------------------------------------------------------- /list.rs: -------------------------------------------------------------------------------- 1 | pub const FILE_PATHS: &[&str] = &[ 2 | "./tests/png/profile.png", 3 | "./tests/png/test.png", 4 | "./tests/png/4.png", 5 | "./tests/png/res.png", 6 | "./tests/png/PngSuite.png", 7 | "./tests/png/icon.png", 8 | "./tests/png/plopgrizzly.png", 9 | "./tests/png/noise.png", 10 | ]; 11 | -------------------------------------------------------------------------------- /res/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/res/icon.png -------------------------------------------------------------------------------- /src/adam7.rs: -------------------------------------------------------------------------------- 1 | // FIXME: API to get Adam7 image before fully loaded (the reason it exists). 2 | // And refactor code to depend on that to get the final image. 3 | 4 | use crate::bitstream::BitstreamReader; 5 | 6 | /// x start values 7 | const IX: [u32; 7] = [0, 4, 0, 2, 0, 1, 0]; 8 | /// y start values 9 | const IY: [u32; 7] = [0, 0, 4, 0, 2, 0, 1]; 10 | 11 | /// x delta values 12 | const DX: [u32; 7] = [8, 8, 4, 4, 2, 2, 1]; 13 | /// y delta values 14 | const DY: [u32; 7] = [8, 8, 8, 4, 4, 2, 2]; 15 | 16 | type PassW = [u32; 7]; 17 | type PassH = [u32; 7]; 18 | type FilterPassStart = [u32; 8]; 19 | type PaddedPassStart = [u32; 8]; 20 | type PassStart = [u32; 8]; 21 | 22 | pub(crate) fn get_pass_values( 23 | w: u32, 24 | h: u32, 25 | bpp: u8, 26 | ) -> (PassW, PassH, FilterPassStart, PaddedPassStart, PassStart) { 27 | let bpp = bpp as u32; 28 | let mut passw: [u32; 7] = [0; 7]; 29 | let mut passh: [u32; 7] = [0; 7]; 30 | let mut filter_passstart: [u32; 8] = [0; 8]; 31 | let mut padded_passstart: [u32; 8] = [0; 8]; 32 | let mut passstart: [u32; 8] = [0; 8]; 33 | 34 | // The passstart values have 8 values: the 8th one indicates the byte after 35 | // the end of the 7th (= last) pass 36 | for i in 0..7 { 37 | // calculate width and height in pixels of each pass 38 | passw[i] = (w + DX[i] - IX[i] - 1) / DX[i]; 39 | passh[i] = (h + DY[i] - IY[i] - 1) / DY[i]; 40 | // if passw[i] is 0, it's 0 bytes, not 1 (no filter_type-byte) 41 | if passw[i] == 0 { 42 | passh[i] = 0; // only padded at end of reduced image 43 | } 44 | // bits padded if needed to fill full byte at end of each scanline 45 | if passh[i] == 0 { 46 | passw[i] = 0; 47 | }; 48 | } 49 | filter_passstart[0] = 0; 50 | padded_passstart[0] = 0; 51 | passstart[0] = 0; 52 | for i in 0..7 { 53 | filter_passstart[i + 1] = filter_passstart[i] 54 | + if passw[i] != 0 && passh[i] != 0 { 55 | passh[i] * (1 + (passw[i] * bpp + 7) / 8) 56 | } else { 57 | 0 58 | }; 59 | padded_passstart[i + 1] = 60 | padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7) / 8); 61 | passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7) / 8; 62 | } 63 | (passw, passh, filter_passstart, padded_passstart, passstart) 64 | } 65 | 66 | /// in: Adam7 interlaced image, with no padding bits between scanlines, but 67 | /// between reduced images so that each reduced image starts at a byte. 68 | /// out: the same pixels, but re-ordered so that they're now a non-interlaced 69 | /// image with size w * h bpp: bits per pixel out has the following size in 70 | /// bits: w * h * bpp. in is possibly bigger due to padding bits between 71 | /// reduced images. out must be big enough AND must be 0 everywhere if bpp < 8 72 | /// in the current implementation (because that's likely a little bit faster) 73 | /// 74 | /// NOTE: comments about padding bits are only relevant if bpp < 8 75 | pub(crate) fn deinterlace(out: &mut [u8], inp: &[u8], w: u32, h: u32, bpp: u8) { 76 | let (passw, passh, _, _, passstart) = get_pass_values(w, h, bpp); 77 | let bpp = bpp as u32; 78 | if bpp >= 8 { 79 | for i in 0..7 { 80 | let bytewidth = bpp / 8; 81 | for y in 0..passh[i] { 82 | for x in 0..passw[i] { 83 | let pixelinstart = (passstart[i] 84 | + (y * passw[i] + x) * bytewidth) 85 | as usize; 86 | let bytewidth = bytewidth as usize; 87 | let pixeloutstart = 88 | ((IY[i] + y * DY[i]) * w + IX[i] + x * DX[i]) as usize 89 | * bytewidth; 90 | 91 | out[pixeloutstart..(bytewidth + pixeloutstart)] 92 | .clone_from_slice( 93 | &inp[pixelinstart..(bytewidth + pixelinstart)], 94 | ) 95 | } 96 | } 97 | } 98 | } else { 99 | for i in 0..7 { 100 | let ilinebits = bpp * passw[i]; 101 | let olinebits = bpp * w; 102 | for y in 0..passh[i] { 103 | for x in 0..passw[i] { 104 | let mut obp = ((IY[i] + y * DY[i]) * olinebits 105 | + (IX[i] + x * DX[i]) * bpp) 106 | as usize; 107 | let mut in_stream = BitstreamReader::with_bitpointer( 108 | std::io::Cursor::new(inp), 109 | ((8 * passstart[i]) + (y * ilinebits + x * bpp)) 110 | as usize, 111 | ) 112 | .unwrap(); 113 | for _ in 0..bpp { 114 | let bit = in_stream.read().unwrap().unwrap(); 115 | // note that this function assumes the out buffer is 116 | // completely 0, use set_bit_of_reversed_stream 117 | // otherwise 118 | set_bit_of_reversed_stream0(&mut obp, out, bit); 119 | } 120 | } 121 | } 122 | } 123 | }; 124 | } 125 | 126 | /// in: non-interlaced image with size w*h 127 | /// out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, 128 | /// with no padding bits between scanlines, but between reduced images so that 129 | /// each reduced image starts at a byte. 130 | /// bpp: bits per pixel there are no padding bits, not between scanlines, not 131 | /// between reduced images. in has the following size in bits: w * h * bpp. 132 | /// out is possibly bigger due to padding bits between reduced images 133 | /// 134 | /// NOTE: comments about padding bits are only relevant if bpp < 8 135 | pub(crate) fn interlace(out: &mut [u8], inp: &[u8], w: u32, h: u32, bpp: u8) { 136 | let (passw, passh, _, _, passstart) = get_pass_values(w, h, bpp); 137 | let bpp = bpp as usize; 138 | if bpp >= 8 { 139 | for i in 0..7 { 140 | let bytewidth = bpp / 8; 141 | for y in 0..passh[i] as usize { 142 | for x in 0..passw[i] as usize { 143 | let pixelinstart = ((IY[i] as usize + y * DY[i] as usize) 144 | * w as usize 145 | + IX[i] as usize 146 | + x * DX[i] as usize) 147 | * bytewidth; 148 | let pixeloutstart = passstart[i] as usize 149 | + (y * passw[i] as usize + x) * bytewidth; 150 | out[pixeloutstart..(bytewidth + pixeloutstart)] 151 | .clone_from_slice( 152 | &inp[pixelinstart..(bytewidth + pixelinstart)], 153 | ); 154 | } 155 | } 156 | } 157 | } else { 158 | for i in 0..7 { 159 | let ilinebits = bpp * passw[i] as usize; 160 | let olinebits = bpp * w as usize; 161 | for y in 0..passh[i] as usize { 162 | for x in 0..passw[i] as usize { 163 | let mut obp = 164 | (8 * passstart[i] as usize) + (y * ilinebits + x * bpp); 165 | let mut in_stream = BitstreamReader::with_bitpointer( 166 | std::io::Cursor::new(inp), 167 | (IY[i] as usize + y * DY[i] as usize) * olinebits 168 | + (IX[i] as usize + x * DX[i] as usize) * bpp, 169 | ) 170 | .unwrap(); 171 | for _ in 0..bpp { 172 | let bit = in_stream.read().unwrap().unwrap(); 173 | set_bit_of_reversed_stream(&mut obp, out, bit); 174 | } 175 | } 176 | } 177 | } 178 | }; 179 | } 180 | 181 | /// Like `set_bit_of_reversed_stream()`, except assumes the current value of the 182 | /// bit is `false`. 183 | #[inline(always)] 184 | pub(crate) fn set_bit_of_reversed_stream0( 185 | bitpointer: &mut usize, 186 | bitstream: &mut [u8], 187 | bit: bool, 188 | ) { 189 | /* the current bit in bitstream must be 0 for this to work */ 190 | if bit { 191 | /* earlier bit of huffman code is in a lesser significant bit of an 192 | * earlier byte */ 193 | bitstream[(*bitpointer) >> 3] |= 1 << (7 - ((*bitpointer) & 7)); 194 | } 195 | *bitpointer += 1; 196 | } 197 | 198 | #[inline(always)] 199 | pub(crate) fn set_bit_of_reversed_stream( 200 | bitpointer: &mut usize, 201 | bitstream: &mut [u8], 202 | bit: bool, 203 | ) { 204 | if bit { 205 | bitstream[(*bitpointer) >> 3] |= 1 << (7 - ((*bitpointer) & 7)); 206 | } else { 207 | bitstream[(*bitpointer) >> 3] &= !(1 << (7 - ((*bitpointer) & 7))); 208 | } 209 | 210 | *bitpointer += 1; 211 | } 212 | -------------------------------------------------------------------------------- /src/bitstream.rs: -------------------------------------------------------------------------------- 1 | //! Read and write from a reversed bit stream 2 | 3 | use std::io::{Bytes, Read, Result, Write}; 4 | 5 | /// A reversed bit stream writer. 6 | pub(super) struct BitstreamWriter { 7 | /// Pointer within the stream. 8 | bitpointer: usize, 9 | /// Writer 10 | stream: W, 11 | /// Current byte 12 | byte: u8, 13 | } 14 | 15 | impl BitstreamWriter { 16 | /// Create a new `BitstreamReader` from a type that implements `Read`. 17 | #[inline(always)] 18 | pub(super) fn new(stream: W) -> Self { 19 | BitstreamWriter { 20 | bitpointer: 0, 21 | stream, 22 | byte: 0, 23 | } 24 | } 25 | 26 | pub(super) fn write(&mut self, bit: bool) -> Result<()> { 27 | /* the current bit in bitstream must be 0 for this to work */ 28 | if bit { 29 | /* earlier bit of huffman code is in a lesser significant bit of 30 | * an earlier byte */ 31 | self.byte |= 1 << (7 - self.bitpointer); 32 | } 33 | self.bitpointer += 1; 34 | if self.bitpointer == 8 { 35 | self.bitpointer = 0; 36 | self.stream.write_all(&[self.byte])?; 37 | self.byte = 0; 38 | } 39 | Ok(()) 40 | } 41 | } 42 | 43 | /// A reversed bit stream reader. 44 | pub(super) struct BitstreamReader { 45 | /// Pointer within the stream. 46 | bitpointer: usize, 47 | /// Reader 48 | stream: Bytes, 49 | /// Current byte 50 | byte: Option, 51 | } 52 | 53 | impl BitstreamReader { 54 | /// Create a new `BitstreamReader` from a type that implements `Read`. 55 | #[inline(always)] 56 | pub(super) fn new(stream: R) -> Result { 57 | let mut stream = stream.bytes(); 58 | Ok(BitstreamReader { 59 | bitpointer: 0, 60 | byte: match stream.next() { 61 | Some(Ok(v)) => Ok(Some(v)), 62 | Some(Err(e)) => Err(e), 63 | None => Ok(None), 64 | }?, 65 | stream, 66 | }) 67 | } 68 | 69 | /// Create a new `BitstreamReader` from a type that implements `Read`. 70 | #[inline(always)] 71 | pub(super) fn with_bitpointer( 72 | stream: R, 73 | bitpointer: usize, 74 | ) -> Result { 75 | let mut stream = stream.bytes(); 76 | for _ in 0..bitpointer / 8 { 77 | stream.next().unwrap().unwrap(); 78 | } 79 | Ok(BitstreamReader { 80 | bitpointer: bitpointer % 8, 81 | byte: match stream.next() { 82 | Some(Ok(v)) => Ok(Some(v)), 83 | Some(Err(e)) => Err(e), 84 | None => Ok(None), 85 | }?, 86 | stream, 87 | }) 88 | } 89 | 90 | /// Read the next bit from the stream. 91 | #[inline(always)] 92 | pub(super) fn read(&mut self) -> Result> { 93 | let byte = match self.byte { 94 | Some(b) => b, 95 | None => return Ok(None), 96 | }; 97 | let bitpointer = self.bitpointer; 98 | 99 | // Advance bit pointer 100 | self.bitpointer += 1; 101 | if self.bitpointer == 8 { 102 | self.bitpointer = 0; 103 | self.byte = match self.stream.next() { 104 | Some(Ok(v)) => Ok(Some(v)), 105 | Some(Err(e)) => Err(e), 106 | None => Ok(None), 107 | }?; 108 | } 109 | 110 | Ok(Some(((byte >> (7 - bitpointer)) & 1) != 0)) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/chunk.rs: -------------------------------------------------------------------------------- 1 | //! Low-level PNG API 2 | //! 3 | //! A PNG file consists of a sequence of [`Chunk`](enum.Chunk.html)s in a 4 | //! specific order. 5 | //! 6 | //! # PNG Chunk Order 7 | //! ## Key 8 | //! - **Required** - Count must be exactly one. 9 | //! - **Optional** - Count must be exactly one or zero. 10 | //! - **Multiple** - Count can be any number, including zero. 11 | //! 12 | //! ## Order 13 | //! The PNG/APNG chunk order must be as follows: 14 | //! 15 | //! - **Required** `ImageHeader` "IHDR" 16 | //! - In any order: 17 | //! - **Optional** `Chromaticities` 18 | //! - **Optional** `Gamma` "gAMA" 19 | //! - **Optional** `ColorProfile` "iCCP" 20 | //! - **Optional** `SignificantBits` "sBIT" 21 | //! - **Optional** `SRgb` "sRGB" 22 | //! - **Optional** `Physical` "pHYs" 23 | //! - **Multiple** `SuggestedPalette` "sPLT" 24 | //! - **Optional** `Time` "tIME" (If didn't appear earlier) 25 | //! - **Multiple** `InternationalText` "iTXt" 26 | //! - **Multiple** `Text` "tEXt" 27 | //! - **Multiple** `CompressedText` "zTXt" 28 | //! - **Optional** `AnimationControl` "acTL" (APNG) 29 | //! - **Optional** `FrameControl` "fcTL" (APNG) 30 | //! - **Optional** `ImageOffset` "oFFs" (*Extension*) 31 | //! - **Optional** `PixelCalibration` "pCAL" (*Extension*) 32 | //! - **Optional** `SubjectPhysical` "sCAL" (*Extension*) 33 | //! - **Multiple** `GifGraphicControlExt` "gIFg" (*Extension*) 34 | //! - **Multiple** `GifApplicationExt` "gIFx" (*Extension*) 35 | //! - **Optional** `Palette` "PLTE" 36 | //! - In any order: 37 | //! - **Optional** `Background` "bKGD" 38 | //! - **Optional** `PaletteHistogram` "hIST" 39 | //! - **Optional** `Transparency` "tRNS" 40 | //! - **Optional** `Physical` "pHYs" (If didn't appear before PLTE) 41 | //! - **Multiple** `SuggestedPalette` "sPLT" 42 | //! - **Optional** `Time` "tIME" (If didn't appear earlier) 43 | //! - **Multiple** `InternationalText` "iTXt" 44 | //! - **Multiple** `Text` "tEXt" 45 | //! - **Multiple** `CompressedText` "zTXt" 46 | //! - **Optional** `AnimationControl` "acTL" (APNG, If didn't appear earlier) 47 | //! - **Optional** `FrameControl` "fcTL" (APNG, If didn't appear earlier) 48 | //! - **Optional** `ImageOffset` "oFFs" (*Extension*, If didn't appear 49 | //! earlier) 50 | //! - **Optional** `PixelCalibration` "pCAL" (*Extension*, If didn't appear 51 | //! earlier) 52 | //! - **Optional** `SubjectPhysical` "sCAL" (*Extension*, If didn't appear 53 | //! earlier) 54 | //! - **Multiple** `GifGraphicControlExt` "gIFg" (*Extension*) 55 | //! - **Multiple** `GifApplicationExt` "gIFx" (*Extension*) 56 | //! - **Multiple** `ImageData` "IDAT" 57 | //! - In any order: 58 | //! - **Optional** `Time` "tIME" (If didn't appear earlier) 59 | //! - **Multiple** `InternationalText` "iTXt" 60 | //! - **Multiple** `Text` "tEXt" 61 | //! - **Multiple** `CompressedText` "zTXt" 62 | //! - **Multiple** `FrameControl` "fcTL" (APNG) 63 | //! - **Multiple** `FrameData` "fdAT" (APNG, must be somewhere after "fcTL") 64 | //! - **Multiple** `GifGraphicControlExt` "gIFg" (*Extension*) 65 | //! - **Multiple** `GifApplicationExt` "gIFx" (*Extension*) 66 | //! - **Required** `ImageEnd` "IEND" 67 | 68 | use crate::{ 69 | decode::{Error as DecoderError, Result as DecoderResult}, 70 | encode::{Error as EncoderError, Result as EncoderResult}, 71 | }; 72 | 73 | mod bkgd; 74 | mod idat; 75 | mod iend; 76 | mod ihdr; 77 | mod itxt; 78 | mod phys; 79 | mod plte; 80 | mod text; 81 | mod time; 82 | mod trns; 83 | mod unknown; 84 | mod ztxt; 85 | 86 | pub use self::{ 87 | // Optional 88 | bkgd::Background, 89 | // Required 90 | idat::ImageData, 91 | // Required 92 | iend::ImageEnd, 93 | // Required 94 | ihdr::{ColorType, ImageHeader}, 95 | // Optional 96 | itxt::InternationalText, 97 | // Optional 98 | phys::Physical, 99 | // Required 100 | plte::Palette, 101 | // Optional 102 | text::Text, 103 | // Optional 104 | time::Time, 105 | // Optional 106 | trns::Transparency, 107 | // Optional 108 | unknown::Unknown, 109 | // Optional 110 | ztxt::CompressedText, 111 | }; 112 | 113 | /// A chunk within a PNG file. 114 | #[derive(Debug)] 115 | pub enum Chunk { 116 | /// Required: Image Header 117 | ImageHeader(ImageHeader), 118 | /// Required: Image Data 119 | ImageData(ImageData), 120 | /// Required: Image End 121 | ImageEnd(ImageEnd), 122 | 123 | /// Maybe Required: Palette chunk. 124 | Palette(Palette), 125 | 126 | /// Optional: Background color chunk. 127 | Background(Background), 128 | /// Optional: International text chunk. 129 | InternationalText(InternationalText), 130 | /// Optional: Physical dimensions chunk 131 | Physical(Physical), 132 | /// Optional: Non-International text chunk. 133 | Text(Text), 134 | /// Optional: Time chunk. 135 | Time(Time), 136 | /// Optional: Alpha palette chunk. 137 | Transparency(Transparency), 138 | /// Optional: Z text chunk. 139 | CompressedText(CompressedText), 140 | /// Unknown chunk 141 | Unknown(Unknown), 142 | } 143 | 144 | impl Chunk { 145 | pub(super) fn is_idat(&self) -> bool { 146 | matches!(self, Chunk::ImageData(_)) 147 | } 148 | 149 | pub(super) fn is_iend(&self) -> bool { 150 | matches!(self, Chunk::ImageEnd(_)) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/chunk/bkgd.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write}; 2 | 3 | use parsenic::{be::Read as _, Read as _, Reader}; 4 | 5 | use super::{Chunk, DecoderError, EncoderError}; 6 | use crate::{consts, decoder::Parser, encoder::Enc}; 7 | 8 | /// Suggested background color chunk (bKGD) 9 | #[derive(Copy, Clone, Debug)] 10 | pub enum Background { 11 | /// 8-bit palette background index 12 | Palette(u8), 13 | /// 1-16 bit gray background value 14 | Gray(u16), 15 | /// 1-16 bits for each of Red, Green and Blue 16 | Rgb(u16, u16, u16), 17 | } 18 | 19 | impl Background { 20 | pub(crate) fn parse( 21 | parse: &mut Parser, 22 | ) -> Result { 23 | match parse.len() { 24 | 1 => { 25 | let buffer: [u8; 1] = parse.bytes()?; 26 | let mut reader = Reader::new(&buffer); 27 | let index = reader.u8()?; 28 | 29 | reader.end().unwrap(); 30 | Ok(Chunk::Background(Background::Palette(index))) 31 | } 32 | 2 => { 33 | let buffer: [u8; 2] = parse.bytes()?; 34 | let mut reader = Reader::new(&buffer); 35 | let value = reader.u16()?; 36 | 37 | reader.end().unwrap(); 38 | Ok(Chunk::Background(Background::Gray(value))) 39 | } 40 | 6 => { 41 | let buffer: [u8; 6] = parse.bytes()?; 42 | let mut reader = Reader::new(&buffer); 43 | let [r, g, b] = [reader.u16()?, reader.u16()?, reader.u16()?]; 44 | 45 | reader.end().unwrap(); 46 | Ok(Chunk::Background(Background::Rgb(r, g, b))) 47 | } 48 | _ => Err(DecoderError::ChunkLength(consts::BACKGROUND)), 49 | } 50 | } 51 | 52 | pub(crate) fn write( 53 | &self, 54 | enc: &mut Enc, 55 | ) -> Result<(), EncoderError> { 56 | use Background::*; 57 | match *self { 58 | Palette(v) => { 59 | enc.prepare(1, consts::BACKGROUND)?; 60 | enc.u8(v)?; 61 | } 62 | Gray(v) => { 63 | enc.prepare(2, consts::BACKGROUND)?; 64 | enc.u16(v)? 65 | } 66 | Rgb(r, g, b) => { 67 | enc.prepare(6, consts::BACKGROUND)?; 68 | enc.u16(r)?; 69 | enc.u16(g)?; 70 | enc.u16(b)?; 71 | } 72 | } 73 | enc.write_crc() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/chunk/idat.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write}; 2 | 3 | use crate::{ 4 | chunk::Chunk, consts, decode::Result as DecoderResult, decoder::Parser, 5 | encode::Error as EncoderError, encoder::Enc, zlib, 6 | }; 7 | 8 | /// Image Data Chunk Data (IDAT) 9 | #[derive(Debug)] 10 | pub struct ImageData { 11 | /// Part of a compressed ZLIB stream 12 | pub data: Vec, 13 | } 14 | 15 | impl ImageData { 16 | pub(crate) fn parse( 17 | parse: &mut Parser, 18 | ) -> DecoderResult { 19 | let data = parse.raw()?; 20 | Ok(Chunk::ImageData(ImageData { data })) 21 | } 22 | 23 | pub(crate) fn write( 24 | &self, 25 | enc: &mut Enc, 26 | ) -> Result<(), EncoderError> { 27 | // FIXME: Should already be compressed. 28 | let mut zlib = Vec::new(); 29 | zlib::compress(&mut zlib, self.data.as_slice(), enc.level()); 30 | 31 | // 32 | enc.prepare(zlib.len(), consts::IMAGE_DATA)?; 33 | enc.raw(&zlib)?; 34 | enc.write_crc() 35 | } 36 | 37 | /// Construct from raw uncompressed image data. 38 | pub fn with_data(data: Vec) -> ImageData { 39 | ImageData { data } 40 | } 41 | 42 | /// Get the image data 43 | pub fn data(&self) -> &[u8] { 44 | &self.data[..] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/chunk/iend.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use super::{Chunk, EncoderError}; 4 | use crate::{consts, encoder::Enc}; 5 | 6 | /// Image End Chunk Data (IEND) 7 | #[derive(Copy, Clone, Debug)] 8 | pub struct ImageEnd; 9 | 10 | impl ImageEnd { 11 | pub(crate) fn parse() -> Chunk { 12 | Chunk::ImageEnd(ImageEnd) 13 | } 14 | 15 | pub(crate) fn write( 16 | &self, 17 | enc: &mut Enc, 18 | ) -> Result<(), EncoderError> { 19 | enc.prepare(0, consts::IMAGE_END)?; 20 | enc.write_crc() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/chunk/ihdr.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{Read, Write}, 3 | num::NonZeroU32, 4 | }; 5 | 6 | use parsenic::{be::Read as _, Read as _, Reader}; 7 | 8 | use crate::{ 9 | chunk::Chunk, consts, decode::Error as DecoderError, decoder::Parser, 10 | encode::Error as EncoderError, encoder::Enc, 11 | }; 12 | 13 | /// Standard PNG color types. 14 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 15 | #[repr(u8)] 16 | pub enum ColorType { 17 | /// greyscale: 1, 2, 4, 8, 16 bit 18 | Grey = 0u8, 19 | /// RGB: 8, 16 bit 20 | Rgb = 2, 21 | /// palette: 1, 2, 4, 8 bit 22 | Palette = 3, 23 | /// greyscale with alpha: 8, 16 bit 24 | GreyAlpha = 4, 25 | /// RGB with alpha: 8, 16 bit 26 | Rgba = 6, 27 | } 28 | 29 | impl ColorType { 30 | /// channels * bytes per channel = bytes per pixel 31 | pub(crate) fn channels(self) -> u8 { 32 | match self { 33 | ColorType::Grey | ColorType::Palette => 1, 34 | ColorType::GreyAlpha => 2, 35 | ColorType::Rgb => 3, 36 | ColorType::Rgba => 4, 37 | } 38 | } 39 | 40 | /// get the total amount of bits per pixel, based on colortype and bitdepth 41 | /// in the struct 42 | pub(crate) fn bpp(self, bit_depth: u8) -> u8 { 43 | assert!((1..=16).contains(&bit_depth)); 44 | /* bits per pixel is amount of channels * bits per channel */ 45 | let ch = self.channels(); 46 | ch * if ch > 1 { 47 | if bit_depth == 8 { 48 | 8 49 | } else { 50 | 16 51 | } 52 | } else { 53 | bit_depth 54 | } 55 | } 56 | 57 | /// Error if invalid color type / bit depth combination for PNG. 58 | pub(crate) fn check_png_color_validity( 59 | self, 60 | bd: u8, 61 | ) -> Result<(), DecoderError> { 62 | match self { 63 | ColorType::Grey => { 64 | if !(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16) { 65 | return Err(DecoderError::ColorMode(self, bd)); 66 | } 67 | } 68 | ColorType::Palette => { 69 | if !(bd == 1 || bd == 2 || bd == 4 || bd == 8) { 70 | return Err(DecoderError::ColorMode(self, bd)); 71 | } 72 | } 73 | ColorType::Rgb | ColorType::GreyAlpha | ColorType::Rgba => { 74 | if !(bd == 8 || bd == 16) { 75 | return Err(DecoderError::ColorMode(self, bd)); 76 | } 77 | } 78 | } 79 | Ok(()) 80 | } 81 | } 82 | 83 | /// Image Header Chunk Data (IHDR) 84 | #[derive(Copy, Clone, Debug)] 85 | pub struct ImageHeader { 86 | /// Width of the image 87 | pub width: u32, 88 | /// Height of the image 89 | pub height: u32, 90 | /// The colortype of the image 91 | pub color_type: ColorType, 92 | /// How many bits per channel 93 | pub bit_depth: u8, 94 | /// True for adam7 interlacing, false for no interlacing. 95 | pub interlace: bool, 96 | } 97 | 98 | impl ImageHeader { 99 | pub(crate) fn write( 100 | &self, 101 | enc: &mut Enc, 102 | ) -> Result<(), EncoderError> { 103 | enc.prepare(13, consts::IMAGE_HEADER)?; 104 | enc.u32(self.width)?; 105 | enc.u32(self.height)?; 106 | enc.u8(self.bit_depth)?; 107 | enc.u8(self.color_type as u8)?; 108 | enc.u8(0)?; 109 | enc.u8(0)?; 110 | enc.u8(self.interlace as u8)?; 111 | enc.write_crc() 112 | } 113 | 114 | pub(crate) fn parse( 115 | parse: &mut Parser, 116 | ) -> Result { 117 | let buffer: [u8; 13] = parse.bytes()?; 118 | let mut reader = Reader::new(&buffer); 119 | let width = NonZeroU32::new(reader.u32()?) 120 | .ok_or(DecoderError::ImageDimensions)? 121 | .get(); 122 | let height = NonZeroU32::new(reader.u32()?) 123 | .ok_or(DecoderError::ImageDimensions)? 124 | .get(); 125 | let bit_depth = { 126 | let bit_depth = reader.u8()?; 127 | 128 | (1..=16) 129 | .contains(&bit_depth) 130 | .then_some(bit_depth) 131 | .ok_or(DecoderError::BitDepth(bit_depth))? 132 | }; 133 | let color_type = { 134 | let color_type = match reader.u8()? { 135 | 0 => ColorType::Grey, 136 | 2 => ColorType::Rgb, 137 | 3 => ColorType::Palette, 138 | 4 => ColorType::GreyAlpha, 139 | 6 => ColorType::Rgba, 140 | c => return Err(DecoderError::ColorType(c)), 141 | }; 142 | 143 | color_type.check_png_color_validity(bit_depth)?; 144 | color_type 145 | }; 146 | let _compression_method = { 147 | let compression_method = reader.u8()?; 148 | 149 | // error: only compression method 0 is allowed in the specification 150 | (compression_method == 0) 151 | .then_some(compression_method) 152 | .ok_or(DecoderError::CompressionMethod)? 153 | }; 154 | let _filter_method = { 155 | let filter_method = reader.u8()?; 156 | 157 | // error: only filter method 0 is allowed in the specification 158 | (filter_method == 0) 159 | .then_some(filter_method) 160 | .ok_or(DecoderError::FilterMethod)? 161 | }; 162 | let interlace = match reader.u8()? { 163 | 0 => false, 164 | 1 => true, 165 | _ => return Err(DecoderError::InterlaceMethod), 166 | }; 167 | 168 | reader.end().unwrap(); 169 | Ok(Chunk::ImageHeader(Self { 170 | width, 171 | height, 172 | color_type, 173 | bit_depth, 174 | interlace, 175 | })) 176 | } 177 | 178 | /// get the total amount of bits per pixel, based on colortype and bitdepth 179 | /// in the struct 180 | pub(crate) fn bpp(&self) -> u8 { 181 | self.color_type.bpp(self.bit_depth) /* 4 or 6 */ 182 | } 183 | 184 | /// Returns the byte size of a raw image buffer with given width, height and 185 | /// color mode 186 | pub(crate) fn raw_size(&self) -> usize { 187 | /* will not overflow for any color type if roughly w * h < 268435455 */ 188 | let bpp = self.bpp() as usize; 189 | let n = self.width as usize * self.height as usize; 190 | ((n / 8) * bpp) + ((n & 7) * bpp + 7) / 8 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/chunk/itxt.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write}; 2 | 3 | use parsenic::{Read as _, Reader}; 4 | 5 | use super::Chunk; 6 | use crate::{ 7 | consts, decode::Error as DecoderError, decoder::Parser, 8 | encode::Error as EncoderError, encoder::Enc, parsing::Read as _, zlib, 9 | }; 10 | 11 | /// International Text Chunk Data (iTXt) 12 | #[derive(Clone, Debug)] 13 | pub struct InternationalText { 14 | /// A keyword that gives a short description of what the text in `val` 15 | /// represents, e.g. Title, Author, Description, or anything else. Minimum 16 | /// of 1 character, and maximum 79 characters long. 17 | pub key: String, 18 | /// Additional string "langtag" 19 | pub langtag: String, 20 | /// Additional string "transkey" 21 | pub transkey: String, 22 | /// The actual message. It's discouraged to use a single line length 23 | /// longer than 79 characters 24 | pub val: String, 25 | /// If the text should be compressed 26 | pub compressed: bool, 27 | } 28 | 29 | impl InternationalText { 30 | /* international text chunk (iTXt) */ 31 | pub(crate) fn parse( 32 | parse: &mut Parser, 33 | ) -> Result { 34 | let buffer = parse.raw()?; 35 | let mut reader = Reader::new(&buffer); 36 | let key = { 37 | let key = reader.strz()?; 38 | let key_len = key.len(); 39 | 40 | (1..=79) 41 | .contains(&key_len) 42 | .then_some(key) 43 | .ok_or(DecoderError::KeySize(key_len))? 44 | }; 45 | let compressed = match reader.u8()? { 46 | 0 => false, 47 | 1 => true, 48 | // FIXME: More specific error 49 | _ => return Err(DecoderError::CompressionMethod), 50 | }; 51 | let _compression_method = { 52 | let compression_method = reader.u8()?; 53 | 54 | (compression_method == 0) 55 | .then_some(compression_method) 56 | .ok_or(DecoderError::CompressionMethod)? 57 | }; 58 | let langtag = reader.strz()?; 59 | let transkey = reader.strz()?; 60 | let data = reader 61 | .slice( 62 | parse.len() - (key.len() + langtag.len() + transkey.len() + 5), 63 | )? 64 | .to_vec(); 65 | let val = if compressed { 66 | String::from_utf8_lossy(&zlib::decompress(&data)?).to_string() 67 | } else { 68 | String::from_utf8_lossy(&data).to_string() 69 | }; 70 | 71 | reader.end().unwrap(); 72 | Ok(Chunk::InternationalText(InternationalText { 73 | key, 74 | langtag, 75 | transkey, 76 | val, 77 | compressed, 78 | })) 79 | } 80 | 81 | pub(crate) fn write( 82 | &self, 83 | enc: &mut Enc, 84 | ) -> Result<(), EncoderError> { 85 | // Checks 86 | let k_len = self.key.len(); 87 | if !(1..=79).contains(&k_len) { 88 | return Err(EncoderError::KeySize(k_len)); 89 | } 90 | 91 | // Maybe compress 92 | let zdata = if self.compressed { 93 | let mut data = Vec::new(); 94 | zlib::compress(&mut data, self.val.as_bytes(), enc.level()); 95 | Some(data) 96 | } else { 97 | None 98 | }; 99 | 100 | // Encode 101 | let len = if let Some(ref zdata) = zdata { 102 | zdata.len() 103 | } else { 104 | self.val.len() 105 | }; 106 | enc.prepare( 107 | self.key.len() + self.langtag.len() + self.transkey.len() + len + 5, 108 | consts::ITEXT, 109 | )?; 110 | enc.str(&self.key)?; 111 | enc.u8(self.compressed as u8)?; 112 | enc.u8(0)?; 113 | enc.str(&self.langtag)?; 114 | enc.str(&self.transkey)?; 115 | if let Some(zdata) = zdata { 116 | enc.raw(&zdata)?; 117 | } else { 118 | enc.raw(self.val.as_bytes())?; 119 | } 120 | enc.write_crc() 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/chunk/phys.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write}; 2 | 3 | use parsenic::{be::Read as _, Read as _, Reader}; 4 | 5 | use super::{Chunk, DecoderError, EncoderError}; 6 | use crate::{consts, decoder::Parser, encoder::Enc}; 7 | 8 | /// Physical dimensions chunk (pHYs) 9 | #[derive(Copy, Clone, Debug)] 10 | pub struct Physical { 11 | /// Pixels per unit: X dimension 12 | pub ppu_x: u32, 13 | /// Pixels per unit: Y dimension 14 | pub ppu_y: u32, 15 | /// Unit is `meter` if true, `unknown` if false. 16 | pub is_meter: bool, 17 | } 18 | 19 | impl Physical { 20 | pub(crate) fn write( 21 | &self, 22 | enc: &mut Enc, 23 | ) -> Result<(), EncoderError> { 24 | enc.prepare(9, consts::PHYSICAL)?; 25 | enc.u32(self.ppu_x)?; 26 | enc.u32(self.ppu_y)?; 27 | enc.u8(if self.is_meter { 1 } else { 0 })?; 28 | enc.write_crc() 29 | } 30 | 31 | pub(crate) fn parse( 32 | parse: &mut Parser, 33 | ) -> Result { 34 | let buffer: [u8; 9] = parse.bytes()?; 35 | let mut reader = Reader::new(&buffer); 36 | let ppu_x = reader.u32()?; 37 | let ppu_y = reader.u32()?; 38 | let is_meter = match reader.u8()? { 39 | 0 => false, 40 | 1 => true, 41 | _ => return Err(DecoderError::PhysUnits), 42 | }; 43 | 44 | reader.end().unwrap(); 45 | Ok(Chunk::Physical(Physical { 46 | ppu_x, 47 | ppu_y, 48 | is_meter, 49 | })) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/chunk/plte.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write}; 2 | 3 | use parsenic::{Read as _, Reader}; 4 | use pix::rgb::{Rgb, SRgb8}; 5 | 6 | use super::{Chunk, DecoderError, EncoderError}; 7 | use crate::{consts, decoder::Parser, encoder::Enc}; 8 | 9 | /// Palette Chunk Data (PLTE) 10 | #[derive(Clone, Debug)] 11 | #[must_use] 12 | pub struct Palette { 13 | /// List of colors in the palette. 14 | pub palette: Vec, 15 | } 16 | 17 | impl Palette { 18 | pub(crate) fn parse( 19 | parse: &mut Parser, 20 | ) -> Result { 21 | parse.set_palette(); 22 | 23 | let buffer = parse.raw()?; 24 | let mut reader = Reader::new(&buffer); 25 | let palette = (0..(parse.len() / 3)) 26 | .map(|_| -> Result<_, DecoderError> { 27 | let [r, g, b] = [reader.u8()?, reader.u8()?, reader.u8()?]; 28 | 29 | Ok(SRgb8::new(r, g, b)) 30 | }) 31 | .collect::>()?; 32 | 33 | reader.end().unwrap(); 34 | Ok(Chunk::Palette(Palette { palette })) 35 | } 36 | 37 | pub(crate) fn write( 38 | &self, 39 | enc: &mut Enc, 40 | ) -> Result<(), EncoderError> { 41 | enc.prepare(self.palette.len() * 3, consts::PALETTE)?; 42 | for p in self.palette.iter().cloned() { 43 | enc.u8(Rgb::red(p).into())?; 44 | enc.u8(Rgb::green(p).into())?; 45 | enc.u8(Rgb::blue(p).into())?; 46 | } 47 | enc.write_crc() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/chunk/text.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write}; 2 | 3 | use parsenic::{Read as _, Reader}; 4 | 5 | use super::{Chunk, DecoderError, EncoderError}; 6 | use crate::{consts, decoder::Parser, encoder::Enc, parsing::Read as _}; 7 | 8 | /// Non-International Text Chunk Data (tEXt and zTXt) 9 | #[derive(Clone, Debug)] 10 | pub struct Text { 11 | /// A keyword that gives a short description of what the text in `val` 12 | /// represents, e.g. Title, Author, Description, or anything else. Minimum 13 | /// of 1 character, and maximum 79 characters long. 14 | pub key: String, 15 | /// The actual message. It's discouraged to use a single line length 16 | /// longer than 79 characters 17 | pub val: String, 18 | } 19 | 20 | impl Text { 21 | pub(crate) fn parse( 22 | parse: &mut Parser, 23 | ) -> Result { 24 | let buffer = parse.raw()?; 25 | let mut reader = Reader::new(&buffer); 26 | let key = { 27 | let key = reader.strz()?; 28 | let key_len = key.len(); 29 | 30 | (1..=79) 31 | .contains(&key_len) 32 | .then_some(key) 33 | .ok_or(DecoderError::KeySize(key_len))? 34 | }; 35 | let val = String::from_utf8_lossy( 36 | reader.slice(parse.len() - (key.len() + 1))?, 37 | ) 38 | .into_owned(); 39 | 40 | reader.end().unwrap(); 41 | Ok(Chunk::Text(Text { key, val })) 42 | } 43 | 44 | pub(crate) fn write( 45 | &self, 46 | enc: &mut Enc, 47 | ) -> Result<(), EncoderError> { 48 | // Checks 49 | if self.key.as_bytes().is_empty() { 50 | return Err(EncoderError::KeySize(0)); 51 | } 52 | 53 | // 1 Null-terminated string, 1 string 54 | enc.prepare(self.key.len() + self.val.len() + 1, consts::TEXT)?; 55 | enc.str(&self.key)?; 56 | enc.string(&self.val)?; 57 | enc.write_crc() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/chunk/time.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write}; 2 | 3 | use parsenic::{be::Read as _, Read as _, Reader}; 4 | 5 | use super::{Chunk, DecoderError, EncoderError}; 6 | use crate::{consts, decoder::Parser, encoder::Enc}; 7 | 8 | /// Time chunk (tIME) 9 | #[derive(Copy, Clone, Debug)] 10 | #[allow(missing_docs)] // self-explanatory 11 | pub struct Time { 12 | pub year: u16, 13 | pub month: u8, 14 | pub day: u8, 15 | pub hour: u8, 16 | pub minute: u8, 17 | pub second: u8, 18 | } 19 | 20 | impl Time { 21 | pub(crate) fn write( 22 | &self, 23 | enc: &mut Enc, 24 | ) -> Result<(), EncoderError> { 25 | // 7 Bytes 26 | enc.prepare(7, consts::TIME)?; 27 | enc.u16(self.year)?; 28 | enc.u8(self.month)?; 29 | enc.u8(self.day)?; 30 | enc.u8(self.hour)?; 31 | enc.u8(self.minute)?; 32 | enc.u8(self.second)?; 33 | enc.write_crc() 34 | } 35 | 36 | pub(crate) fn parse( 37 | parse: &mut Parser, 38 | ) -> Result { 39 | let buffer: [u8; 7] = parse.bytes()?; 40 | let mut reader = Reader::new(&buffer); 41 | let year = reader.u16()?; 42 | let month = reader.u8()?; 43 | let day = reader.u8()?; 44 | let hour = reader.u8()?; 45 | let minute = reader.u8()?; 46 | let second = reader.u8()?; 47 | 48 | reader.end().unwrap(); 49 | Ok(Chunk::Time(Time { 50 | year, 51 | month, 52 | day, 53 | hour, 54 | minute, 55 | second, 56 | })) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/chunk/trns.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write}; 2 | 3 | use parsenic::{be::Read as _, Read as _, Reader}; 4 | 5 | use super::{Chunk, DecoderError, DecoderResult, EncoderResult}; 6 | use crate::{consts, decoder::Parser, encoder::Enc}; 7 | 8 | /// Alpha Palette Chunk Data (tRNS) 9 | #[derive(Debug, Clone, PartialEq)] 10 | #[allow(variant_size_differences)] 11 | #[must_use] 12 | pub enum Transparency { 13 | /// Alpha values for the first `alpha.len()` entries in palette. 14 | Palette(Vec), 15 | /// What RGB value should be replaced with a transparent pixel 16 | RgbKey(u16, u16, u16), 17 | /// What gray value should be replaced with a transparent pixel 18 | GrayKey(u16), 19 | } 20 | 21 | impl Transparency { 22 | /// Get the length of a palette, panicking if transparent key 23 | pub(crate) fn len(&self) -> usize { 24 | self.as_slice().len() 25 | } 26 | 27 | /// Get the length of a palette, panicking if transparent key 28 | pub(crate) fn as_slice(&self) -> &[u8] { 29 | use Transparency::*; 30 | match self { 31 | Palette(p) => p.as_slice(), 32 | _ => unreachable!(), 33 | } 34 | } 35 | 36 | pub(crate) fn parse( 37 | parse: &mut Parser, 38 | ) -> DecoderResult { 39 | if parse.has_palette() { 40 | // Palette 41 | let apal = parse.raw()?; 42 | Ok(Chunk::Transparency(Transparency::Palette(apal))) 43 | } else { 44 | // Gray or RGB 45 | match parse.len() { 46 | 2 => { 47 | let buffer: [u8; 2] = parse.bytes()?; 48 | let mut reader = Reader::new(&buffer); 49 | let value = reader.u16()?; 50 | 51 | reader.end().unwrap(); 52 | Ok(Chunk::Transparency(Transparency::GrayKey(value))) 53 | } 54 | 6 => { 55 | let buffer: [u8; 6] = parse.bytes()?; 56 | let mut reader = Reader::new(&buffer); 57 | let [r, g, b] = 58 | [reader.u16()?, reader.u16()?, reader.u16()?]; 59 | 60 | reader.end().unwrap(); 61 | Ok(Chunk::Transparency(Transparency::RgbKey(r, g, b))) 62 | } 63 | _ => Err(DecoderError::ChunkLength(consts::TRANSPARENCY)), 64 | } 65 | } 66 | } 67 | 68 | pub(crate) fn write( 69 | &self, 70 | enc: &mut Enc, 71 | ) -> EncoderResult<()> { 72 | use Transparency::*; 73 | match self { 74 | Palette(plte) => { 75 | enc.prepare(plte.len(), consts::TRANSPARENCY)?; 76 | for alpha in plte.iter().cloned() { 77 | enc.u8(alpha)?; 78 | } 79 | } 80 | RgbKey(red, green, blue) => { 81 | enc.prepare(6, consts::TRANSPARENCY)?; 82 | enc.u16(*red)?; 83 | enc.u16(*green)?; 84 | enc.u16(*blue)?; 85 | } 86 | GrayKey(key) => { 87 | enc.prepare(2, consts::TRANSPARENCY)?; 88 | enc.u16(*key)? 89 | } 90 | } 91 | enc.write_crc() 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/chunk/unknown.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write}; 2 | 3 | use super::{Chunk, DecoderResult, EncoderResult}; 4 | use crate::{decoder::Parser, encoder::Enc}; 5 | 6 | /// An unknown PNG data chunk 7 | #[derive(Clone, Debug)] 8 | pub struct Unknown { 9 | /// The chunk name 10 | pub name: [u8; 4], 11 | /// The chunk data 12 | pub data: Vec, 13 | } 14 | 15 | impl Unknown { 16 | pub(crate) fn write( 17 | &self, 18 | enc: &mut Enc, 19 | ) -> EncoderResult<()> { 20 | enc.prepare(self.data.len(), self.name)?; 21 | enc.raw(&self.data)?; 22 | enc.write_crc() 23 | } 24 | 25 | pub(crate) fn parse( 26 | parse: &mut Parser, 27 | name: [u8; 4], 28 | ) -> DecoderResult { 29 | let data = parse.unknown_chunk()?; 30 | 31 | Ok(Chunk::Unknown(Unknown { name, data })) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/chunk/ztxt.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write}; 2 | 3 | use parsenic::{Read as _, Reader}; 4 | 5 | use super::{Chunk, DecoderError, DecoderResult, EncoderError, EncoderResult}; 6 | use crate::{consts, decoder::Parser, encoder::Enc, zlib, parsing::Read as _}; 7 | 8 | /// Compressed Text Chunk Data (zTXt) 9 | #[derive(Clone, Debug)] 10 | pub struct CompressedText { 11 | /// A keyword that gives a short description of what the text in `val` 12 | /// represents, e.g. Title, Author, Description, or anything else. Minimum 13 | /// of 1 character, and maximum 79 characters long. 14 | pub key: String, 15 | /// The actual message. It's discouraged to use a single line length 16 | /// longer than 79 characters 17 | pub val: String, 18 | } 19 | 20 | impl CompressedText { 21 | pub(crate) fn write( 22 | &self, 23 | enc: &mut Enc, 24 | ) -> EncoderResult<()> { 25 | // Checks 26 | if self.key.as_bytes().is_empty() || self.key.as_bytes().len() > 79 { 27 | return Err(EncoderError::KeySize(self.key.len())); 28 | } 29 | 30 | // Compress text 31 | let mut zdata = Vec::new(); 32 | zlib::compress(&mut zdata, self.val.as_bytes(), enc.level()); 33 | 34 | // Encode Chunk 35 | enc.prepare(self.key.len() + 2 + zdata.len(), consts::ZTEXT)?; 36 | enc.str(&self.key)?; 37 | enc.u8(0)?; // Compression Method 38 | enc.raw(&zdata)?; 39 | enc.write_crc() 40 | } 41 | 42 | pub(crate) fn parse( 43 | parse: &mut Parser, 44 | ) -> DecoderResult { 45 | let buffer = parse.raw()?; 46 | let mut reader = Reader::new(&buffer); 47 | let key = { 48 | let key = reader.strz()?; 49 | let key_len = key.len(); 50 | 51 | (1..=79) 52 | .contains(&key_len) 53 | .then_some(key) 54 | .ok_or(DecoderError::KeySize(key_len))? 55 | }; 56 | let _compression_method = { 57 | let compression_method = reader.u8()?; 58 | 59 | (compression_method == 0) 60 | .then_some(compression_method) 61 | .ok_or(DecoderError::CompressionMethod)? 62 | }; 63 | let ztxt = reader.slice(parse.len() - (key.len() + 2))?; 64 | let decoded = zlib::decompress(ztxt)?; 65 | let val = String::from_utf8_lossy(&decoded).into_owned(); 66 | 67 | Ok(Chunk::CompressedText(CompressedText { key, val })) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/consts.rs: -------------------------------------------------------------------------------- 1 | // Magic bytes to start a PNG file. 2 | pub(super) const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10]; 3 | 4 | // Chunk Identifiers 5 | pub(super) const IMAGE_HEADER: [u8; 4] = *b"IHDR"; 6 | pub(super) const IMAGE_DATA: [u8; 4] = *b"IDAT"; 7 | pub(super) const BACKGROUND: [u8; 4] = *b"bKGD"; 8 | pub(super) const TRANSPARENCY: [u8; 4] = *b"tRNS"; 9 | pub(super) const IMAGE_END: [u8; 4] = *b"IEND"; 10 | pub(super) const PALETTE: [u8; 4] = *b"PLTE"; 11 | pub(super) const ITEXT: [u8; 4] = *b"iTXt"; 12 | pub(super) const PHYSICAL: [u8; 4] = *b"pHYs"; 13 | pub(super) const TIME: [u8; 4] = *b"tIME"; 14 | pub(super) const ZTEXT: [u8; 4] = *b"zTXt"; 15 | pub(super) const TEXT: [u8; 4] = *b"tEXt"; 16 | 17 | pub(super) const MAX_CHUNK_SIZE: usize = 1 << 31; // 2³¹ 18 | 19 | // Initial value for CRC32 Checksum 20 | pub(super) const CRC32_INIT: u32 = 4_294_967_295; 21 | // Look-up table for CRC32 Checksum 22 | pub(super) const CRC32_LOOKUP: [u32; 256] = [ 23 | 0, 24 | 1_996_959_894, 25 | 3_993_919_788, 26 | 2_567_524_794, 27 | 124_634_137, 28 | 1_886_057_615, 29 | 3_915_621_685, 30 | 2_657_392_035, 31 | 249_268_274, 32 | 2_044_508_324, 33 | 3_772_115_230, 34 | 2_547_177_864, 35 | 162_941_995, 36 | 2_125_561_021, 37 | 3_887_607_047, 38 | 2_428_444_049, 39 | 498_536_548, 40 | 1_789_927_666, 41 | 4_089_016_648, 42 | 2_227_061_214, 43 | 450_548_861, 44 | 1_843_258_603, 45 | 4_107_580_753, 46 | 2_211_677_639, 47 | 325_883_990, 48 | 1_684_777_152, 49 | 4_251_122_042, 50 | 2_321_926_636, 51 | 335_633_487, 52 | 1_661_365_465, 53 | 4_195_302_755, 54 | 2_366_115_317, 55 | 997_073_096, 56 | 1_281_953_886, 57 | 3_579_855_332, 58 | 2_724_688_242, 59 | 1_006_888_145, 60 | 1_258_607_687, 61 | 3_524_101_629, 62 | 2_768_942_443, 63 | 901_097_722, 64 | 1_119_000_684, 65 | 3_686_517_206, 66 | 2_898_065_728, 67 | 853_044_451, 68 | 1_172_266_101, 69 | 3_705_015_759, 70 | 2_882_616_665, 71 | 651_767_980, 72 | 1_373_503_546, 73 | 3_369_554_304, 74 | 3_218_104_598, 75 | 565_507_253, 76 | 1_454_621_731, 77 | 3_485_111_705, 78 | 3_099_436_303, 79 | 671_266_974, 80 | 1_594_198_024, 81 | 3_322_730_930, 82 | 2_970_347_812, 83 | 795_835_527, 84 | 1_483_230_225, 85 | 3_244_367_275, 86 | 3_060_149_565, 87 | 1_994_146_192, 88 | 31_158_534, 89 | 2_563_907_772, 90 | 4_023_717_930, 91 | 1_907_459_465, 92 | 112_637_215, 93 | 2_680_153_253, 94 | 3_904_427_059, 95 | 2_013_776_290, 96 | 251_722_036, 97 | 2_517_215_374, 98 | 3_775_830_040, 99 | 2_137_656_763, 100 | 141_376_813, 101 | 2_439_277_719, 102 | 3_865_271_297, 103 | 1_802_195_444, 104 | 476_864_866, 105 | 2_238_001_368, 106 | 4_066_508_878, 107 | 1_812_370_925, 108 | 453_092_731, 109 | 2_181_625_025, 110 | 4_111_451_223, 111 | 1_706_088_902, 112 | 314_042_704, 113 | 2_344_532_202, 114 | 4_240_017_532, 115 | 1_658_658_271, 116 | 366_619_977, 117 | 2_362_670_323, 118 | 4_224_994_405, 119 | 1_303_535_960, 120 | 984_961_486, 121 | 2_747_007_092, 122 | 3_569_037_538, 123 | 1_256_170_817, 124 | 1_037_604_311, 125 | 2_765_210_733, 126 | 3_554_079_995, 127 | 1_131_014_506, 128 | 879_679_996, 129 | 2_909_243_462, 130 | 3_663_771_856, 131 | 1_141_124_467, 132 | 855_842_277, 133 | 2_852_801_631, 134 | 3_708_648_649, 135 | 1_342_533_948, 136 | 654_459_306, 137 | 3_188_396_048, 138 | 3_373_015_174, 139 | 1_466_479_909, 140 | 544_179_635, 141 | 3_110_523_913, 142 | 3_462_522_015, 143 | 1_591_671_054, 144 | 702_138_776, 145 | 2_966_460_450, 146 | 3_352_799_412, 147 | 1_504_918_807, 148 | 783_551_873, 149 | 3_082_640_443, 150 | 3_233_442_989, 151 | 3_988_292_384, 152 | 2_596_254_646, 153 | 62_317_068, 154 | 1_957_810_842, 155 | 3_939_845_945, 156 | 2_647_816_111, 157 | 81_470_997, 158 | 1_943_803_523, 159 | 3_814_918_930, 160 | 2_489_596_804, 161 | 225_274_430, 162 | 2_053_790_376, 163 | 3_826_175_755, 164 | 2_466_906_013, 165 | 167_816_743, 166 | 2_097_651_377, 167 | 4_027_552_580, 168 | 2_265_490_386, 169 | 503_444_072, 170 | 1_762_050_814, 171 | 4_150_417_245, 172 | 2_154_129_355, 173 | 426_522_225, 174 | 1_852_507_879, 175 | 4_275_313_526, 176 | 2_312_317_920, 177 | 282_753_626, 178 | 1_742_555_852, 179 | 4_189_708_143, 180 | 2_394_877_945, 181 | 397_917_763, 182 | 1_622_183_637, 183 | 3_604_390_888, 184 | 2_714_866_558, 185 | 953_729_732, 186 | 1_340_076_626, 187 | 3_518_719_985, 188 | 2_797_360_999, 189 | 1_068_828_381, 190 | 1_219_638_859, 191 | 3_624_741_850, 192 | 2_936_675_148, 193 | 906_185_462, 194 | 1_090_812_512, 195 | 3_747_672_003, 196 | 2_825_379_669, 197 | 829_329_135, 198 | 1_181_335_161, 199 | 3_412_177_804, 200 | 3_160_834_842, 201 | 628_085_408, 202 | 1_382_605_366, 203 | 3_423_369_109, 204 | 3_138_078_467, 205 | 570_562_233, 206 | 1_426_400_815, 207 | 3_317_316_542, 208 | 2_998_733_608, 209 | 733_239_954, 210 | 1_555_261_956, 211 | 3_268_935_591, 212 | 3_050_360_625, 213 | 752_459_403, 214 | 1_541_320_221, 215 | 2_607_071_920, 216 | 3_965_973_030, 217 | 1_969_922_972, 218 | 40_735_498, 219 | 2_617_837_225, 220 | 3_943_577_151, 221 | 1_913_087_877, 222 | 83_908_371, 223 | 2_512_341_634, 224 | 3_803_740_692, 225 | 2_075_208_622, 226 | 213_261_112, 227 | 2_463_272_603, 228 | 3_855_990_285, 229 | 2_094_854_071, 230 | 198_958_881, 231 | 2_262_029_012, 232 | 4_057_260_610, 233 | 1_759_359_992, 234 | 534_414_190, 235 | 2_176_718_541, 236 | 4_139_329_115, 237 | 1_873_836_001, 238 | 414_664_567, 239 | 2_282_248_934, 240 | 4_279_200_368, 241 | 1_711_684_554, 242 | 285_281_116, 243 | 2_405_801_727, 244 | 4_167_216_745, 245 | 1_634_467_795, 246 | 376_229_701, 247 | 2_685_067_896, 248 | 3_608_007_406, 249 | 1_308_918_612, 250 | 956_543_938, 251 | 2_808_555_105, 252 | 3_495_958_263, 253 | 1_231_636_301, 254 | 1_047_427_035, 255 | 2_932_959_818, 256 | 3_654_703_836, 257 | 1_088_359_270, 258 | 936_918_000, 259 | 2_847_714_899, 260 | 3_736_837_829, 261 | 1_202_900_863, 262 | 817_233_897, 263 | 3_183_342_108, 264 | 3_401_237_130, 265 | 1_404_277_552, 266 | 615_818_150, 267 | 3_134_207_493, 268 | 3_453_421_203, 269 | 1_423_857_449, 270 | 601_450_431, 271 | 3_009_837_614, 272 | 3_294_710_456, 273 | 1_567_103_746, 274 | 711_928_724, 275 | 3_020_668_471, 276 | 3_272_380_065, 277 | 1_510_334_235, 278 | 755_167_117, 279 | ]; 280 | -------------------------------------------------------------------------------- /src/decode.rs: -------------------------------------------------------------------------------- 1 | //! PNG file decoding 2 | 3 | mod chunks; 4 | mod error; 5 | mod steps; 6 | 7 | pub use chunks::Chunks; 8 | pub use error::{Error, Result}; 9 | pub use steps::Steps; 10 | -------------------------------------------------------------------------------- /src/decode/chunks.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use crate::{ 4 | chunk::{ 5 | Background, Chunk, CompressedText, ImageData, ImageEnd, ImageHeader, 6 | InternationalText, Palette, Physical, Text, Time, Transparency, 7 | Unknown, 8 | }, 9 | consts, 10 | decode::Result, 11 | decoder::Parser, 12 | }; 13 | 14 | /// Iterator over [`Chunk`](struct.Chunk.html)s - Decoder for PNG files. 15 | #[derive(Debug)] 16 | pub struct Chunks { 17 | /// Decoder 18 | dec: Parser, 19 | } 20 | 21 | impl Chunks { 22 | /// Create a new encoder. Will return an error if it's not a PNG file. 23 | pub(crate) fn new(dec: Parser) -> Self { 24 | Chunks { dec } 25 | } 26 | 27 | /// Get the next chunk in the PNG file. 28 | fn get_next(&mut self) -> Result> { 29 | // Always start reading at the beginning of the next chunk: 30 | let name = if let Some(name) = self.dec.prepare()? { 31 | name 32 | } else { 33 | return Ok(None); 34 | }; 35 | // Choose correct parser for the chunk based on it's name. 36 | use consts::*; 37 | let chunk = match name { 38 | IMAGE_HEADER => ImageHeader::parse(&mut self.dec), 39 | IMAGE_DATA => ImageData::parse(&mut self.dec), 40 | IMAGE_END => Ok(ImageEnd::parse()), 41 | PALETTE => Palette::parse(&mut self.dec), 42 | BACKGROUND => Background::parse(&mut self.dec), 43 | ITEXT => InternationalText::parse(&mut self.dec), 44 | PHYSICAL => Physical::parse(&mut self.dec), 45 | TEXT => Text::parse(&mut self.dec), 46 | TIME => Time::parse(&mut self.dec), 47 | TRANSPARENCY => Transparency::parse(&mut self.dec), 48 | ZTEXT => CompressedText::parse(&mut self.dec), 49 | id => Unknown::parse(&mut self.dec, id), 50 | }?; 51 | // Check the CRC Checksum at the end of the chunk. 52 | self.dec.check_crc(&name)?; 53 | // Return the Chunk 54 | Ok(Some(chunk)) 55 | } 56 | } 57 | 58 | impl Iterator for Chunks { 59 | type Item = Result; 60 | 61 | fn next(&mut self) -> Option { 62 | // Do a swappity 63 | match self.get_next() { 64 | Ok(Some(c)) => Some(Ok(c)), 65 | Ok(None) => None, 66 | Err(e) => Some(Err(e)), 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/decode/error.rs: -------------------------------------------------------------------------------- 1 | use crate::chunk::ColorType; 2 | 3 | /// PNG Pong Decoder Result Type 4 | pub type Result = std::result::Result; 5 | 6 | impl From for Error { 7 | fn from(err: std::io::Error) -> Error { 8 | Error::Io(std::sync::Arc::new(err)) 9 | } 10 | } 11 | 12 | impl From for Error { 13 | fn from(_err: parsenic::error::LenError) -> Error { 14 | Self::Eof 15 | } 16 | } 17 | 18 | /// Decoding Errors. 19 | #[derive(Clone, Debug)] 20 | #[allow(variant_size_differences)] 21 | pub enum Error { 22 | /// A wrapped I/O error. 23 | Io(std::sync::Arc), 24 | /// Unrecognized color type 25 | ColorType(u8), 26 | /// Out of bounds bit depth 27 | BitDepth(u8), 28 | /// Invalid color type / bit depth combination 29 | ColorMode(ColorType, u8), 30 | /// Pixel size in background color doesn't match pixel size in image data 31 | BackgroundSize(ColorType), 32 | /// The first 8 bytes are not the correct PNG signature 33 | InvalidSignature, 34 | /// Adler checksum not correct, data must be corrupted 35 | AdlerChecksum, 36 | /// Inflate algorithm failure 37 | Inflate(miniz_oxide::inflate::TINFLStatus), 38 | /// ZLib compression includes preset dictionary, which is not allowed 39 | /// according to the PNG specification 40 | PresetDict, 41 | /// Invalid compression method in zlib header 42 | CompressionMethod, 43 | /// Invalid FCHECK in zlib header 44 | ZlibHeader, 45 | /// ZLib data is too small 46 | ZlibTooSmall, 47 | /// TODO 48 | InterlaceMethod, 49 | /// TODO 50 | FilterMethod, 51 | /// TODO 52 | ImageDimensions, 53 | /// File doesn't contain any chunks. 54 | Empty, 55 | /// Key is not between 1-79 characters 56 | KeySize(usize), 57 | /// The length of the END symbol 256 in the Huffman tree is 0 58 | HuffmanEnd, 59 | /// Unrecognized filter type 60 | IllegalFilterType, 61 | /// Alpha palette is larger than the palette. 62 | AlphaPaletteLen, 63 | /// Chunk is the wrong size 64 | ChunkSize, 65 | /// Mode has an alpha channel, but also an alpha palette (must pick one) 66 | AlphaPaletteWithAlphaMode, 67 | /// Chunk was expected to end, but didn't 68 | NoEnd, 69 | /// Invalid unit type 70 | PhysUnits, 71 | /// Null terminator is missing. 72 | NulTerm, 73 | /// Invalid chunk length for the chunk type 74 | ChunkLength([u8; 4]), 75 | /// Not a critical error, should be ignored (chunk not recognized). 76 | UnknownChunkType([u8; 4]), 77 | /// Input reading appears to end in the middle of a PNG file 78 | Eof, 79 | /// Chunks are out of order 80 | ChunkOrder, 81 | /// IDAT Chunk not found. 82 | NoImageData, 83 | /// Chunk(s) were found after the IEND chunk. 84 | TrailingChunk, 85 | /// Multiple of a chunk were found when only one of this type is allowed. 86 | Multiple([u8; 4]), 87 | /// CRC32 Checksum failed for a chunk 88 | Crc32([u8; 4]), 89 | } 90 | 91 | impl std::fmt::Display for Error { 92 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 93 | use Error::*; 94 | match self { 95 | Io(io) => write!(f, "I/O Error: {}", io), 96 | ColorType(_) => write!(f, "Unrecognized color type"), 97 | BitDepth(_) => write!(f, "Out of bounds bit depth"), 98 | ColorMode(_ct, _bd) => write!(f, "Invalid color type / bit depth combination"), 99 | BackgroundSize(_) => write!(f, "Background color type mismatch with image color type"), 100 | InvalidSignature => write!(f, "Not a PNG file"), 101 | AdlerChecksum => write!(f, "Adler checksum not correct, data must be corrupted"), 102 | Inflate(e) => write!(f, "Inflate: {:?}", e), 103 | PresetDict => write!(f, "ZLib compression using preset dictionary, PNG doesn't allow"), 104 | CompressionMethod => write!(f, "Invalid compression method in zlib header"), 105 | ZlibHeader => write!(f, "Invalid FCHECK in zlib header"), 106 | ZlibTooSmall => write!(f, "ZLib data is too small"), 107 | InterlaceMethod => write!(f, "Invalid interlace method"), 108 | FilterMethod => write!(f, "Invalid filter method"), 109 | ImageDimensions => write!(f, "Invalid image dimensions, must be greater than 0"), 110 | Empty => write!(f, "File doesn't contain any chunks."), // FIXME: NoImageData 111 | KeySize(size) => write!(f, "Text size ({}) doesn't fit inequality 1 ≤ x ≤ 79", size), 112 | HuffmanEnd => write!(f, "The length of the END symbol 256 in the Huffman tree is 0"), 113 | IllegalFilterType => write!(f, "Unrecognized filter type"), 114 | AlphaPaletteLen => write!(f, "Alpha palette is larger than the palette."), 115 | ChunkSize => write!(f, "Chunk is the wrong size"), // FIXME: Replace with ChunkLength 116 | AlphaPaletteWithAlphaMode => write!(f, "Mode has an alpha channel, but also an alpha palette (must pick one)"), 117 | NoEnd => write!(f, "Chunk was expected to end, but didn't"), // FIXME: Replace with ChunkLength 118 | PhysUnits => write!(f, "Unknown physical units (must be unspecified or meter)"), 119 | NulTerm => write!(f, "Expected null terminator, but not found"), 120 | ChunkLength(bytes) => write!(f, "{} chunk wrong length", String::from_utf8_lossy(bytes)), 121 | UnknownChunkType(bytes) => write!(f, "{} chunk unrecognized", String::from_utf8_lossy(bytes)), 122 | Eof => write!(f, "Unexpected end of file"), 123 | ChunkOrder => write!(f, "PNG chunks are out of order"), 124 | NoImageData => write!(f, "No IDAT chunk exists, invalid PNG file"), 125 | TrailingChunk => write!(f, "Trailing chunks were found after IEND, which is invalid"), 126 | Multiple(bytes) => write!(f, "Only one {} chunk allowed, but found multiple", String::from_utf8_lossy(bytes)), 127 | Crc32(bytes) => write!(f, "CRC32 Checksum failed for {} chunk", String::from_utf8_lossy(bytes)), 128 | } 129 | } 130 | } 131 | 132 | impl std::error::Error for Error {} 133 | -------------------------------------------------------------------------------- /src/decoder.rs: -------------------------------------------------------------------------------- 1 | use std::io::{ErrorKind, Read}; 2 | 3 | use crate::{ 4 | consts, 5 | decode::{Chunks, Error, Result, Steps}, 6 | Step, 7 | }; 8 | 9 | /// Chunk parser. 10 | #[derive(Debug)] 11 | pub(crate) struct Parser { 12 | /// Chunk length 13 | length: u32, 14 | /// CRC32 15 | chksum: u32, 16 | /// Decoder 17 | decode: Decoder, 18 | /// Palette chunk found? 19 | palette: bool, 20 | } 21 | 22 | impl Parser { 23 | /// Prepare a chunk for reading, returning it's name. 24 | pub(crate) fn prepare(&mut self) -> Result> { 25 | let first = match self.u8() { 26 | Ok(first) => first, 27 | Err(Error::Io(e)) if e.kind() == ErrorKind::UnexpectedEof => { 28 | return Ok(None) 29 | } 30 | Err(e) => return Err(e), 31 | }; 32 | self.length = 33 | u32::from_be_bytes([first, self.u8()?, self.u8()?, self.u8()?]); 34 | // Start checksum over 35 | self.chksum = consts::CRC32_INIT; 36 | // Return chunk name 37 | let name = [self.u8()?, self.u8()?, self.u8()?, self.u8()?]; 38 | if self.length > consts::MAX_CHUNK_SIZE as u32 { 39 | return Err(Error::ChunkLength(name)); 40 | } 41 | Ok(Some(name)) 42 | } 43 | 44 | /// Call this when palette chunk is found, whether or not it shows up 45 | /// influences how other chunks are parsed. 46 | pub(crate) fn set_palette(&mut self) { 47 | self.palette = true; 48 | } 49 | 50 | /// Has palette been parsed yet? 51 | pub(crate) fn has_palette(&self) -> bool { 52 | self.palette 53 | } 54 | 55 | /// Get the length of the chunk. 56 | pub(crate) fn len(&self) -> usize { 57 | self.length.try_into().unwrap() 58 | } 59 | 60 | /// Read and ignore the entire chunk. 61 | pub(crate) fn unknown_chunk(&mut self) -> Result> { 62 | self.vec(self.len()) 63 | } 64 | 65 | /// Read entire chunk into a `Vec`. 66 | pub(crate) fn raw(&mut self) -> Result> { 67 | self.vec(self.len()) 68 | } 69 | 70 | /// Get an array of bytes out of the reader. 71 | pub(crate) fn bytes(&mut self) -> Result<[u8; N]> { 72 | let mut array = [0; N]; 73 | 74 | self.decode 75 | .reader 76 | .read_exact(&mut array) 77 | .map_err(Error::from)?; 78 | 79 | for byte in array { 80 | let index: usize = (self.chksum as u8 ^ byte).into(); 81 | 82 | self.chksum = consts::CRC32_LOOKUP[index] ^ (self.chksum >> 8); 83 | } 84 | 85 | Ok(array) 86 | } 87 | 88 | /// Check if the CRC matches calculated CRC. 89 | pub(crate) fn check_crc(&mut self, name: &[u8; 4]) -> Result<()> { 90 | let mut crc32 = [0; 4]; 91 | self.decode.reader.read_exact(&mut crc32)?; 92 | if u32::from_be_bytes(crc32) != (self.chksum ^ consts::CRC32_INIT) { 93 | return Err(Error::Crc32(*name)); 94 | } 95 | Ok(()) 96 | } 97 | 98 | /// Get a u8 out of the reader. 99 | fn u8(&mut self) -> Result { 100 | self.bytes().map(|[byte]| byte) 101 | } 102 | 103 | /// Read into a `Vec`. 104 | fn vec(&mut self, len: usize) -> Result> { 105 | let mut out = Vec::with_capacity(len); 106 | for _ in 0..len { 107 | out.push(self.u8()?); 108 | } 109 | Ok(out) 110 | } 111 | } 112 | 113 | /// PNG file decoder 114 | /// 115 | /// Can be converted into one of two iterators: 116 | /// - [into_iter] / [into_steps] for high-level [Step]s 117 | /// - [into_chunks] for low-level [Chunk]s 118 | /// 119 | /// [into_iter]: struct.Decoder.html#method.into_iter 120 | /// [into_steps]: struct.Decoder.html#method.into_steps 121 | /// [into_chunks]: struct.Decoder.html#method.into_chunks 122 | /// [Step]: struct.Step.html 123 | /// [Chunk]: chunk/enum.Chunk.html 124 | #[derive(Debug)] 125 | pub struct Decoder { 126 | // The source of PNG input. 127 | reader: R, 128 | } 129 | 130 | impl Decoder { 131 | /// Create a new PNG decoder. Returns `Err` if it's not a PNG file. 132 | pub fn new(mut reader: R) -> Result { 133 | // Read first 8 bytes (PNG Signature) 134 | let mut buf = [0u8; 8]; 135 | reader.read_exact(&mut buf).map_err(Error::from)?; 136 | if buf != consts::PNG_SIGNATURE { 137 | return Err(Error::InvalidSignature); 138 | } 139 | 140 | Ok(Decoder { reader }) 141 | } 142 | 143 | /// Convert into a `Chunk` iterator. 144 | pub fn into_chunks(self) -> Chunks { 145 | Chunks::new(self.parser()) 146 | } 147 | 148 | /// Convert into a `Step` iterator. 149 | pub fn into_steps(self) -> Steps { 150 | Steps::new(self.into_chunks()) 151 | } 152 | 153 | /// Convert into a `Parser`. 154 | fn parser(self) -> Parser { 155 | Parser { 156 | decode: self, 157 | length: 0, 158 | chksum: 0, 159 | palette: false, 160 | } 161 | } 162 | } 163 | 164 | impl IntoIterator for Decoder { 165 | type IntoIter = Steps; 166 | type Item = Result; 167 | 168 | /// Convert into a raster step `Iterator` 169 | fn into_iter(self) -> Self::IntoIter { 170 | self.into_steps() 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/encode.rs: -------------------------------------------------------------------------------- 1 | //! PNG file encoding 2 | 3 | mod chunk_enc; 4 | mod error; 5 | pub(super) mod filter; 6 | mod step_enc; // Share with unfilter 7 | 8 | pub use chunk_enc::ChunkEnc; 9 | pub use error::{Error, Result}; 10 | pub use filter::FilterStrategy; 11 | pub use step_enc::StepEnc; 12 | -------------------------------------------------------------------------------- /src/encode/chunk_enc.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use crate::{chunk::Chunk, encode::Error, encoder::Enc}; 4 | 5 | /// Chunk Encoder for PNG files. 6 | /// 7 | /// Note that this doesn't enforce correct ordering of chunks or valid chunk 8 | /// combinations. If you need it, use `StepEncoder`, the higher-level API. 9 | #[derive(Debug)] 10 | pub struct ChunkEnc { 11 | // FIXME: use .encode() instead of pub(crate). 12 | pub(crate) enc: Enc, 13 | } 14 | 15 | impl ChunkEnc { 16 | /// Create a new encoder. 17 | pub(crate) fn new(enc: Enc) -> Self { 18 | Self { enc } 19 | } 20 | 21 | /// Encode one [`Chunk`](struct.Chunk.html) 22 | pub fn encode(&mut self, chunk: &mut Chunk) -> Result<(), Error> { 23 | use Chunk::*; 24 | match chunk { 25 | ImageHeader(image_header) => image_header.write(&mut self.enc), 26 | ImageData(image_data) => image_data.write(&mut self.enc), 27 | ImageEnd(image_end) => image_end.write(&mut self.enc), 28 | Palette(palette) => palette.write(&mut self.enc), 29 | Background(background) => background.write(&mut self.enc), 30 | InternationalText(itext) => itext.write(&mut self.enc), 31 | Physical(physical) => physical.write(&mut self.enc), 32 | Text(text) => text.write(&mut self.enc), 33 | Time(time) => time.write(&mut self.enc), 34 | Transparency(transparency) => transparency.write(&mut self.enc), 35 | CompressedText(ztext) => ztext.write(&mut self.enc), 36 | Unknown(unknown) => unknown.write(&mut self.enc), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/encode/error.rs: -------------------------------------------------------------------------------- 1 | /// PNG Pong Encoder Result Type 2 | pub type Result = std::result::Result; 3 | 4 | impl From for Error { 5 | fn from(err: std::io::Error) -> Error { 6 | Error::Io(std::sync::Arc::new(err)) 7 | } 8 | } 9 | 10 | /// Encoding Errors. 11 | #[derive(Debug)] 12 | #[allow(variant_size_differences)] 13 | pub enum Error { 14 | /// A wrapped I/O error. 15 | Io(std::sync::Arc), 16 | /// Chunks arranged in invalid sequence. (FIXME: Replace with ChunkOrder) 17 | InvalidChunkSequence, 18 | /// Chunk is too large to save in a PNG file (length must fit in 32 bits) 19 | ChunkTooBig, 20 | /// key is not between 1-79 characters 21 | KeySize(usize), 22 | /// PLTE chunk with a palette that has less than 1 or more than 256 colors 23 | BadPalette, 24 | /// Chunks arranged in invalid sequence. Provides PNG chunk identifier of 25 | /// the out-of-order chunk. 26 | ChunkOrder([u8; 4]), 27 | } 28 | 29 | impl std::fmt::Display for Error { 30 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 31 | use Error::*; 32 | match self { 33 | Io(io) => write!(f, "I/O Error: {}", io), 34 | InvalidChunkSequence => write!(f, "Invalid chunk sequence"), 35 | ChunkTooBig => write!(f, "Chunk too big"), 36 | KeySize(size) => { 37 | write!(f, "Key size {size} is not between 1 and 79 characters") 38 | } 39 | BadPalette => write!(f, "Invalid palette"), 40 | ChunkOrder(bytes) => write!( 41 | f, 42 | "Chunk {} out of order", 43 | String::from_utf8_lossy(bytes) 44 | ), 45 | } 46 | } 47 | } 48 | 49 | impl std::error::Error for Error {} 50 | -------------------------------------------------------------------------------- /src/encoder.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use crate::{ 4 | consts, 5 | encode::{ChunkEnc, Error, FilterStrategy, Result, StepEnc}, 6 | }; 7 | 8 | /// Chunk encoder. 9 | #[derive(Debug)] 10 | pub(crate) struct Enc { 11 | /// Encoder 12 | encode: Encoder, 13 | /// CRC32 14 | chksum: u32, 15 | } 16 | 17 | impl Enc { 18 | /// Prepare a chunk for writing (reset checksum). 19 | pub(crate) fn prepare(&mut self, len: usize, name: [u8; 4]) -> Result<()> { 20 | assert!(len <= consts::MAX_CHUNK_SIZE); 21 | let len: u32 = len.try_into().unwrap(); 22 | self.encode 23 | .writer 24 | .write_all(&len.to_be_bytes()) 25 | .map_err(Error::from)?; 26 | self.chksum = consts::CRC32_INIT; 27 | for c in name.iter().cloned() { 28 | self.u8(c)?; 29 | } 30 | Ok(()) 31 | } 32 | 33 | /// Write a u8 34 | pub(crate) fn u8(&mut self, value: u8) -> Result<()> { 35 | self.encode 36 | .writer 37 | .write_all(&[value]) 38 | .map_err(Error::from)?; 39 | let index: usize = (self.chksum as u8 ^ value).into(); 40 | self.chksum = consts::CRC32_LOOKUP[index] ^ (self.chksum >> 8); 41 | Ok(()) 42 | } 43 | 44 | /// Write a u16 45 | pub(crate) fn u16(&mut self, value: u16) -> Result<()> { 46 | let bytes = value.to_be_bytes(); 47 | for byte in bytes.iter().cloned() { 48 | self.u8(byte)?; 49 | } 50 | Ok(()) 51 | } 52 | 53 | /// Write a u32 54 | pub(crate) fn u32(&mut self, value: u32) -> Result<()> { 55 | let bytes = value.to_be_bytes(); 56 | for byte in bytes.iter().cloned() { 57 | self.u8(byte)?; 58 | } 59 | Ok(()) 60 | } 61 | 62 | /// Write a string 63 | pub(crate) fn string(&mut self, value: &str) -> Result<()> { 64 | for byte in value.bytes() { 65 | self.u8(byte)?; 66 | } 67 | Ok(()) 68 | } 69 | 70 | /// Write a null-terminated string 71 | pub(crate) fn str(&mut self, value: &str) -> Result<()> { 72 | self.string(value)?; 73 | self.u8(0) 74 | } 75 | 76 | /// Write raw data 77 | pub(crate) fn raw(&mut self, raw: &[u8]) -> Result<()> { 78 | for byte in raw.iter().cloned() { 79 | self.u8(byte)?; 80 | } 81 | Ok(()) 82 | } 83 | 84 | /// Calculate and write Chunk CRC, ending the chunk. 85 | pub(crate) fn write_crc(&mut self) -> Result<()> { 86 | let crc = self.chksum ^ consts::CRC32_INIT; 87 | self.encode 88 | .writer 89 | .write_all(&crc.to_be_bytes()) 90 | .map_err(Error::from) 91 | } 92 | 93 | /// Get the chosen filter strategy 94 | pub(crate) fn filter_strategy(&self) -> Option { 95 | self.encode.filter_strategy 96 | } 97 | 98 | /// Get the compression level. 99 | pub(crate) fn level(&self) -> u8 { 100 | self.encode.level 101 | } 102 | 103 | /// Whether or not interlaced. 104 | pub(crate) fn interlace(&self) -> bool { 105 | self.encode.interlace 106 | } 107 | } 108 | 109 | /// PNG file encoder 110 | /// 111 | /// Can be converted into one of two encoders: 112 | /// - [into_step_enc] for high-level [Step]s 113 | /// - [into_chunk_enc] for low-level [Chunk]s 114 | /// 115 | /// [into_iter]: struct.Decoder.html#method.into_iter 116 | /// [into_step_enc]: struct.Decoder.html#method.into_step_enc 117 | /// [into_chunk_enc]: struct.Decoder.html#method.into_chunk_enc 118 | /// [Step]: struct.Step.html 119 | /// [Chunk]: struct.Chunk.html 120 | #[derive(Debug)] 121 | pub struct Encoder { 122 | filter_strategy: Option, 123 | level: u8, 124 | interlace: bool, 125 | writer: W, 126 | } 127 | 128 | impl Encoder { 129 | /// Create a new PNG encoder. 130 | pub fn new(writer: W) -> Self { 131 | Encoder { 132 | writer, 133 | filter_strategy: None, 134 | level: 6, 135 | interlace: false, 136 | } 137 | } 138 | 139 | /// Set a specific filter strategy. If this is never called, than png_pong 140 | /// attempts to choose the best (compromise speed / compression) filter 141 | /// strategy. 142 | pub fn filter_strategy(mut self, strategy: FilterStrategy) -> Self { 143 | self.filter_strategy = Some(strategy); 144 | self 145 | } 146 | 147 | /// Set the compression level (default: 6). Must be between 0 and 10. 148 | pub fn compression_level(mut self, level: u8) -> Self { 149 | assert!(level <= 10); 150 | self.level = level; 151 | self 152 | } 153 | 154 | /// Encode interlaced (default non-interlaced) 155 | pub fn interlace(mut self) -> Self { 156 | self.interlace = true; 157 | self 158 | } 159 | 160 | /// Convert into a chunk encoder. 161 | pub fn into_chunk_enc(self) -> ChunkEnc { 162 | ChunkEnc::new(self.into_enc()) 163 | } 164 | 165 | /// Convert into a step encoder. 166 | pub fn into_step_enc(self) -> StepEnc { 167 | StepEnc::new(self.into_chunk_enc()) 168 | } 169 | 170 | fn into_enc(self) -> Enc { 171 | Enc { 172 | encode: self, 173 | chksum: 0, 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A library for decoding and encoding PNG images and APNG animations. 2 | //! 3 | //! ## Getting Started 4 | //! Add the following to your `Cargo.toml`. 5 | //! 6 | //! ```toml 7 | //! [dependencies.png_pong] 8 | //! version = "0.9" 9 | //! ``` 10 | //! 11 | //! ### Example 12 | //! ```rust 13 | //! // Saving raster as a PNG file 14 | //! let raster = png_pong::PngRaster::Rgba8(pix::Raster::with_pixels(1, 1, &[ 15 | //! pix::rgb::SRgba8::new(0, 0, 0, 0)][..] 16 | //! )); 17 | //! let mut out_data = Vec::new(); 18 | //! let mut encoder = png_pong::Encoder::new(&mut out_data).into_step_enc(); 19 | //! let step = png_pong::Step{ raster, delay: 0 }; 20 | //! encoder.encode(&step).expect("Failed to add frame"); 21 | //! std::fs::write("graphic.png", out_data).expect("Failed to save image"); 22 | //! 23 | //! // Loading PNG file into a Raster 24 | //! let data = std::fs::read("graphic.png").expect("Failed to open PNG"); 25 | //! let data = std::io::Cursor::new(data); 26 | //! let decoder = png_pong::Decoder::new(data).expect("Not PNG").into_steps(); 27 | //! let png_pong::Step { raster, delay } = decoder 28 | //! .last() 29 | //! .expect("No frames in PNG") 30 | //! .expect("PNG parsing error"); 31 | //! ``` 32 | 33 | #![doc( 34 | html_logo_url = "https://raw.githubusercontent.com/AldaronLau/png_pong/v0/res/icon.png", 35 | html_favicon_url = "https://raw.githubusercontent.com/AldaronLau/png_pong/v0/res/icon.png", 36 | html_root_url = "https://docs.rs/png_pong" 37 | )] 38 | #![forbid(unsafe_code)] 39 | #![warn( 40 | anonymous_parameters, 41 | missing_copy_implementations, 42 | missing_debug_implementations, 43 | missing_docs, 44 | nonstandard_style, 45 | rust_2018_idioms, 46 | single_use_lifetimes, 47 | trivial_casts, 48 | trivial_numeric_casts, 49 | unreachable_pub, 50 | unused_extern_crates, 51 | unused_qualifications, 52 | variant_size_differences 53 | )] 54 | 55 | pub mod chunk; 56 | pub mod decode; 57 | pub mod encode; 58 | 59 | pub(crate) mod decoder; 60 | 61 | mod adam7; 62 | mod bitstream; 63 | mod consts; 64 | mod encoder; 65 | mod parsing; 66 | mod raster; 67 | mod step; 68 | mod zlib; 69 | 70 | pub use decoder::Decoder; 71 | pub use encoder::Encoder; 72 | pub use raster::PngRaster; 73 | pub use step::Step; 74 | -------------------------------------------------------------------------------- /src/parsing.rs: -------------------------------------------------------------------------------- 1 | use parsenic::result::LenResult; 2 | use traitful::extend; 3 | 4 | #[extend] 5 | pub(crate) trait Read: parsenic::Read { 6 | /// Get a nul terminated String out of a reader 7 | fn strz(&mut self) -> LenResult { 8 | let mut bytes = [0u8; 4]; 9 | let mut index = 0; 10 | let mut out = String::new(); 11 | 12 | loop { 13 | let byte = self.u8()?; 14 | 15 | if byte == 0 { 16 | break; 17 | } 18 | 19 | bytes[index] = byte; 20 | index += 1; 21 | 22 | match std::str::from_utf8(&bytes[0..index]) { 23 | Ok(c) => { 24 | out.push_str(c); 25 | index = 0; 26 | } 27 | Err(e) => { 28 | if e.error_len().is_some() { 29 | out.push(std::char::REPLACEMENT_CHARACTER); 30 | index = 0; 31 | } 32 | } 33 | } 34 | } 35 | 36 | Ok(out) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/raster.rs: -------------------------------------------------------------------------------- 1 | use pix::{ 2 | chan::{Ch16, Ch8}, 3 | el::Pixel, 4 | gray::{Gray8, SGray16, SGray8, SGraya16, SGraya8}, 5 | rgb::{SRgb16, SRgb8, SRgba16, SRgba8}, 6 | Palette, Raster, 7 | }; 8 | 9 | use crate::chunk::{ColorType, ImageHeader}; 10 | 11 | /// A Raster of one of the PNG types (all are sRGB gamma). 12 | /// PNGs with less than 8 bits per channel are scaled up to 8 bits per channel. 13 | #[allow(missing_debug_implementations)] 14 | pub enum PngRaster { 15 | /// 1, 2, 4, 8-bit greyscale 16 | Gray8(Raster), 17 | /// 16-bit grayscale 18 | Gray16(Raster), 19 | /// 8-bit sRGB 20 | Rgb8(Raster), 21 | /// 16-bit sRGB 22 | Rgb16(Raster), 23 | /// 1, 2, 4, 8-bit sRGB(A) palette 24 | Palette(Raster, Box, Vec), 25 | /// 8-bit grayscale with alpha 26 | Graya8(Raster), 27 | /// 16-bit grayscale with alpha 28 | Graya16(Raster), 29 | /// 8-bit sRGB with alpha 30 | Rgba8(Raster), 31 | /// 16-bit sRGB with alpha 32 | Rgba16(Raster), 33 | } 34 | 35 | impl PngRaster { 36 | pub(crate) fn header(&self, interlace: bool) -> ImageHeader { 37 | use PngRaster::*; 38 | match self { 39 | Gray8(r) => ImageHeader { 40 | width: r.width(), 41 | height: r.height(), 42 | color_type: ColorType::Grey, 43 | bit_depth: 8, 44 | interlace, 45 | }, 46 | Gray16(r) => ImageHeader { 47 | width: r.width(), 48 | height: r.height(), 49 | color_type: ColorType::Grey, 50 | bit_depth: 16, 51 | interlace, 52 | }, 53 | Rgb8(r) => ImageHeader { 54 | width: r.width(), 55 | height: r.height(), 56 | color_type: ColorType::Rgb, 57 | bit_depth: 8, 58 | interlace, 59 | }, 60 | Rgb16(r) => ImageHeader { 61 | width: r.width(), 62 | height: r.height(), 63 | color_type: ColorType::Rgb, 64 | bit_depth: 16, 65 | interlace, 66 | }, 67 | Palette(r, _pal, _pa) => ImageHeader { 68 | width: r.width(), 69 | height: r.height(), 70 | color_type: ColorType::Palette, 71 | bit_depth: 8, 72 | interlace, 73 | }, 74 | Graya8(r) => ImageHeader { 75 | width: r.width(), 76 | height: r.height(), 77 | color_type: ColorType::GreyAlpha, 78 | bit_depth: 8, 79 | interlace, 80 | }, 81 | Graya16(r) => ImageHeader { 82 | width: r.width(), 83 | height: r.height(), 84 | color_type: ColorType::GreyAlpha, 85 | bit_depth: 16, 86 | interlace, 87 | }, 88 | Rgba8(r) => ImageHeader { 89 | width: r.width(), 90 | height: r.height(), 91 | color_type: ColorType::Rgba, 92 | bit_depth: 8, 93 | interlace, 94 | }, 95 | Rgba16(r) => ImageHeader { 96 | width: r.width(), 97 | height: r.height(), 98 | color_type: ColorType::Rgba, 99 | bit_depth: 16, 100 | interlace, 101 | }, 102 | } 103 | } 104 | } 105 | 106 | impl From for Raster

107 | where 108 | P::Chan: From + From, 109 | { 110 | fn from(raster: PngRaster) -> Raster

{ 111 | use PngRaster::*; 112 | match raster { 113 | Gray8(r) => Raster::with_raster(&r), 114 | Gray16(r) => Raster::with_raster(&r), 115 | Rgb8(r) => Raster::with_raster(&r), 116 | Rgb16(r) => Raster::with_raster(&r), 117 | Palette(raster, pal, pa) => { 118 | let mut pixels = Vec::with_capacity(raster.pixels().len()); 119 | for pixel in raster.pixels() { 120 | let i: u8 = pixel.one().into(); 121 | let i = i as usize; 122 | let px: SRgb8 = pal.entry(i).unwrap(); 123 | let px = SRgba8::new( 124 | px.one(), 125 | px.two(), 126 | px.three(), 127 | Ch8::new(pa[i]), 128 | ); 129 | pixels.push(px.convert()); 130 | } 131 | Raster::with_pixels(raster.width(), raster.height(), pixels) 132 | } 133 | Graya8(r) => Raster::with_raster(&r), 134 | Graya16(r) => Raster::with_raster(&r), 135 | Rgba8(r) => Raster::with_raster(&r), 136 | Rgba16(r) => Raster::with_raster(&r), 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/step.rs: -------------------------------------------------------------------------------- 1 | use crate::PngRaster; 2 | 3 | /// A Frame 4 | pub struct Step { 5 | /// Raster associated with this frame. 6 | pub raster: PngRaster, 7 | /// TODO: Delay associated with this frame. 8 | pub delay: u32, 9 | } 10 | 11 | impl std::fmt::Debug for Step { 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | write!(f, "{}", self.delay) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/zlib.rs: -------------------------------------------------------------------------------- 1 | //! Compression algorithms 2 | 3 | use miniz_oxide::{deflate::compress_to_vec, inflate::decompress_to_vec}; 4 | 5 | use crate::decode::Error; 6 | 7 | // FIXME: Streaming API 8 | pub(crate) fn decompress(inp: &[u8]) -> Result, Error> { 9 | if inp.len() < 2 { 10 | return Err(Error::ZlibTooSmall); 11 | } 12 | /* read information from zlib header */ 13 | if (inp[0] as u32 * 256 + inp[1] as u32) % 31 != 0 { 14 | /* error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK 15 | * value is supposed to be made that way */ 16 | return Err(Error::ZlibHeader); 17 | } 18 | let cm = inp[0] as u32 & 15; 19 | let cinfo = ((inp[0] as u32) >> 4) & 15; 20 | let fdict = ((inp[1] as u32) >> 5) & 1; 21 | if cm != 8 || cinfo > 7 { 22 | /* error: only compression method 8: inflate with sliding window of 23 | * 32k is supported by the PNG spec */ 24 | return Err(Error::CompressionMethod); 25 | } 26 | if fdict != 0 { 27 | /*error: the specification of PNG says about the zlib stream: 28 | "The additional flags shall not specify a preset dictionary."*/ 29 | return Err(Error::PresetDict); 30 | } 31 | 32 | let out = match decompress_to_vec(&inp[2..(inp.len() - 4)]) { 33 | Ok(rtn) => rtn, 34 | Err(e) => { 35 | return Err(Error::Inflate(e.status)); 36 | } 37 | }; 38 | 39 | let adler32_val = u32::from_be_bytes([ 40 | inp[inp.len() - 4], 41 | inp[inp.len() - 3], 42 | inp[inp.len() - 2], 43 | inp[inp.len() - 1], 44 | ]); 45 | let checksum = adler32(&out); 46 | if checksum != adler32_val { 47 | return Err(Error::AdlerChecksum); 48 | } 49 | 50 | Ok(out) 51 | } 52 | 53 | // FIXME: Streaming API 54 | pub(crate) fn compress(outv: &mut Vec, inp: &[u8], level: u8) { 55 | /*initially, *out must be NULL and outsize 0, if you just give some random *out 56 | that's pointing to a non allocated buffer, this'll crash*/ 57 | /* zlib data: 1 byte CMF (cm+cinfo), 1 byte FLG, deflate data, 4 byte 58 | * adler32_val checksum of the Decompressed data */ 59 | let cmf = 120; 60 | /* 0b01111000: CM 8, cinfo 7. With cinfo 7, any window size up to 32768 61 | * can be used. */ 62 | let flevel = 0; 63 | let fdict = 0; 64 | let mut cmfflg = 256 * cmf + fdict * 32 + flevel * 64; 65 | let fcheck = 31 - cmfflg % 31; 66 | cmfflg += fcheck; 67 | /* Vec-controlled version of the output buffer, for dynamic array */ 68 | outv.push((cmfflg >> 8) as u8); 69 | outv.push((cmfflg & 255) as u8); 70 | let deflated = compress_to_vec(inp, level); 71 | let adler32_val = adler32(inp); 72 | outv.extend_from_slice(&deflated); 73 | outv.extend(adler32_val.to_be_bytes().iter()); 74 | } 75 | 76 | /// Return the Adler32 of the bytes data[0..len-1] 77 | fn adler32(data: &[u8]) -> u32 { 78 | let mut adler = simd_adler32::Adler32::new(); 79 | adler.write(data); 80 | adler.finish() 81 | } 82 | -------------------------------------------------------------------------------- /tests/PngSuite.LICENSE: -------------------------------------------------------------------------------- 1 | PngSuite 2 | -------- 3 | 4 | Permission to use, copy, modify and distribute these images for any 5 | purpose and without fee is hereby granted. 6 | 7 | 8 | (c) Willem van Schaik, 1996, 2011 9 | 10 | -------------------------------------------------------------------------------- /tests/PngSuite.README: -------------------------------------------------------------------------------- 1 | PNGSUITE 2 | ---------------- 3 | 4 | testset for PNG-(de)coders 5 | created by Willem van Schaik 6 | ------------------------------------ 7 | 8 | This is a collection of graphics images created to test the png applications 9 | like viewers, converters and editors. All (as far as that is possible) 10 | formats supported by the PNG standard are represented. 11 | 12 | The suite consists of the following files: 13 | 14 | - PngSuite.README - this file 15 | - PngSuite.LICENSE - the PngSuite is freeware 16 | - PngSuite.png - image with PngSuite logo 17 | - PngSuite.tgz - archive of all PNG testfiles 18 | - PngSuite.zip - same in .zip format for PCs 19 | 20 | 21 | -------- 22 | (c) Willem van Schaik 23 | willem@schaik.com 24 | Calgary, April 2011 25 | 26 | -------------------------------------------------------------------------------- /tests/apng/APNG-Fadeout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/APNG-Fadeout.png -------------------------------------------------------------------------------- /tests/apng/APNG-Glass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/APNG-Glass.png -------------------------------------------------------------------------------- /tests/apng/APNG-IC1696_by_Jukka_Metsavainio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/APNG-IC1696_by_Jukka_Metsavainio.png -------------------------------------------------------------------------------- /tests/apng/APNG-Icos4D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/APNG-Icos4D.png -------------------------------------------------------------------------------- /tests/apng/APNG-Saturn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/APNG-Saturn.png -------------------------------------------------------------------------------- /tests/apng/APNG-StarV838.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/APNG-StarV838.png -------------------------------------------------------------------------------- /tests/apng/APNG-from-GIF-LostWorld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/APNG-from-GIF-LostWorld.png -------------------------------------------------------------------------------- /tests/apng/APNG-from-GIF-Mouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/APNG-from-GIF-Mouse.png -------------------------------------------------------------------------------- /tests/apng/APNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/APNG.png -------------------------------------------------------------------------------- /tests/apng/Gold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/Gold.png -------------------------------------------------------------------------------- /tests/apng/Newton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/Newton.png -------------------------------------------------------------------------------- /tests/apng/clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/clock.png -------------------------------------------------------------------------------- /tests/apng/colors-apng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/colors-apng.png -------------------------------------------------------------------------------- /tests/apng/diamond-apng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/diamond-apng.png -------------------------------------------------------------------------------- /tests/apng/graham11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/graham11.png -------------------------------------------------------------------------------- /tests/apng/o_sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/apng/o_sample.png -------------------------------------------------------------------------------- /tests/png/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/0.png -------------------------------------------------------------------------------- /tests/png/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/1.png -------------------------------------------------------------------------------- /tests/png/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/2.png -------------------------------------------------------------------------------- /tests/png/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/3.png -------------------------------------------------------------------------------- /tests/png/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/4.png -------------------------------------------------------------------------------- /tests/png/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/5.png -------------------------------------------------------------------------------- /tests/png/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/6.png -------------------------------------------------------------------------------- /tests/png/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/7.png -------------------------------------------------------------------------------- /tests/png/PngSuite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/PngSuite.png -------------------------------------------------------------------------------- /tests/png/bad/README.md: -------------------------------------------------------------------------------- 1 | # Bad PNGs 2 | This is borrowed from the libpng test PNG suite. All of these PNGs should 3 | result in a `DecoderError`. 4 | -------------------------------------------------------------------------------- /tests/png/bad/bad_iCCP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/bad_iCCP.png -------------------------------------------------------------------------------- /tests/png/bad/badadler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/badadler.png -------------------------------------------------------------------------------- /tests/png/bad/badcrc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/badcrc.png -------------------------------------------------------------------------------- /tests/png/bad/empty_ancillary_chunks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/empty_ancillary_chunks.png -------------------------------------------------------------------------------- /tests/png/bad/huge_IDAT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_IDAT.png -------------------------------------------------------------------------------- /tests/png/bad/huge_bKGD_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_bKGD_chunk.png -------------------------------------------------------------------------------- /tests/png/bad/huge_cHRM_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_cHRM_chunk.png -------------------------------------------------------------------------------- /tests/png/bad/huge_eXIf_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_eXIf_chunk.png -------------------------------------------------------------------------------- /tests/png/bad/huge_gAMA_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_gAMA_chunk.png -------------------------------------------------------------------------------- /tests/png/bad/huge_hIST_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_hIST_chunk.png -------------------------------------------------------------------------------- /tests/png/bad/huge_iCCP_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_iCCP_chunk.png -------------------------------------------------------------------------------- /tests/png/bad/huge_iTXt_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_iTXt_chunk.png -------------------------------------------------------------------------------- /tests/png/bad/huge_juNK_unsafe_to_copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_juNK_unsafe_to_copy.png -------------------------------------------------------------------------------- /tests/png/bad/huge_juNk_safe_to_copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_juNk_safe_to_copy.png -------------------------------------------------------------------------------- /tests/png/bad/huge_pCAL_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_pCAL_chunk.png -------------------------------------------------------------------------------- /tests/png/bad/huge_pHYs_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_pHYs_chunk.png -------------------------------------------------------------------------------- /tests/png/bad/huge_sCAL_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_sCAL_chunk.png -------------------------------------------------------------------------------- /tests/png/bad/huge_sPLT_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_sPLT_chunk.png -------------------------------------------------------------------------------- /tests/png/bad/huge_sRGB_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_sRGB_chunk.png -------------------------------------------------------------------------------- /tests/png/bad/huge_sTER_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_sTER_chunk.png -------------------------------------------------------------------------------- /tests/png/bad/huge_tEXt_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_tEXt_chunk.png -------------------------------------------------------------------------------- /tests/png/bad/huge_tIME_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_tIME_chunk.png -------------------------------------------------------------------------------- /tests/png/bad/huge_zTXt_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/bad/huge_zTXt_chunk.png -------------------------------------------------------------------------------- /tests/png/fry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/fry.png -------------------------------------------------------------------------------- /tests/png/gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/gray.png -------------------------------------------------------------------------------- /tests/png/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/icon.png -------------------------------------------------------------------------------- /tests/png/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/noise.png -------------------------------------------------------------------------------- /tests/png/plopgrizzly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/plopgrizzly.png -------------------------------------------------------------------------------- /tests/png/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/profile.png -------------------------------------------------------------------------------- /tests/png/res.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/res.png -------------------------------------------------------------------------------- /tests/png/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/png/test.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/ccwn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/ccwn2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/ccwn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/ccwn3p08.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/cdfn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/cdfn2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/cdhn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/cdhn2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/cdsn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/cdsn2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/cdun2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/cdun2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/ch1n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/ch1n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/ch2n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/ch2n3p08.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/cm0n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/cm0n0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/cm7n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/cm7n0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/cm9n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/cm9n0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/cs3n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/cs3n2c16.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/cs3n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/cs3n3p08.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/cs5n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/cs5n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/cs5n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/cs5n3p08.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/cs8n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/cs8n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/cs8n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/cs8n3p08.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/ct0n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/ct0n0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/ct1n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/ct1n0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/cten0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/cten0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/ctfn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/ctfn0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/ctgn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/ctgn0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/cthn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/cthn0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/ctjn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/ctjn0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/ctzn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/ctzn0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-ancillary/exif2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-ancillary/exif2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-background/bgai4a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-background/bgai4a08.png -------------------------------------------------------------------------------- /tests/pngsuite-background/bgai4a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-background/bgai4a16.png -------------------------------------------------------------------------------- /tests/pngsuite-background/bgan6a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-background/bgan6a08.png -------------------------------------------------------------------------------- /tests/pngsuite-background/bgan6a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-background/bgan6a16.png -------------------------------------------------------------------------------- /tests/pngsuite-background/bgbn4a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-background/bgbn4a08.png -------------------------------------------------------------------------------- /tests/pngsuite-background/bggn4a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-background/bggn4a16.png -------------------------------------------------------------------------------- /tests/pngsuite-background/bgwn6a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-background/bgwn6a08.png -------------------------------------------------------------------------------- /tests/pngsuite-background/bgyn6a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-background/bgyn6a16.png -------------------------------------------------------------------------------- /tests/pngsuite-basic/basn0g01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-basic/basn0g01.png -------------------------------------------------------------------------------- /tests/pngsuite-basic/basn0g02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-basic/basn0g02.png -------------------------------------------------------------------------------- /tests/pngsuite-basic/basn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-basic/basn0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-basic/basn0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-basic/basn0g08.png -------------------------------------------------------------------------------- /tests/pngsuite-basic/basn0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-basic/basn0g16.png -------------------------------------------------------------------------------- /tests/pngsuite-basic/basn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-basic/basn2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-basic/basn2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-basic/basn2c16.png -------------------------------------------------------------------------------- /tests/pngsuite-basic/basn3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-basic/basn3p01.png -------------------------------------------------------------------------------- /tests/pngsuite-basic/basn3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-basic/basn3p02.png -------------------------------------------------------------------------------- /tests/pngsuite-basic/basn3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-basic/basn3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-basic/basn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-basic/basn3p08.png -------------------------------------------------------------------------------- /tests/pngsuite-basic/basn4a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-basic/basn4a08.png -------------------------------------------------------------------------------- /tests/pngsuite-basic/basn4a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-basic/basn4a16.png -------------------------------------------------------------------------------- /tests/pngsuite-basic/basn6a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-basic/basn6a08.png -------------------------------------------------------------------------------- /tests/pngsuite-basic/basn6a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-basic/basn6a16.png -------------------------------------------------------------------------------- /tests/pngsuite-chunkorder/oi1n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-chunkorder/oi1n0g16.png -------------------------------------------------------------------------------- /tests/pngsuite-chunkorder/oi1n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-chunkorder/oi1n2c16.png -------------------------------------------------------------------------------- /tests/pngsuite-chunkorder/oi2n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-chunkorder/oi2n0g16.png -------------------------------------------------------------------------------- /tests/pngsuite-chunkorder/oi2n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-chunkorder/oi2n2c16.png -------------------------------------------------------------------------------- /tests/pngsuite-chunkorder/oi4n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-chunkorder/oi4n0g16.png -------------------------------------------------------------------------------- /tests/pngsuite-chunkorder/oi4n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-chunkorder/oi4n2c16.png -------------------------------------------------------------------------------- /tests/pngsuite-chunkorder/oi9n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-chunkorder/oi9n0g16.png -------------------------------------------------------------------------------- /tests/pngsuite-chunkorder/oi9n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-chunkorder/oi9n2c16.png -------------------------------------------------------------------------------- /tests/pngsuite-corrupt/xc1n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-corrupt/xc1n0g08.png -------------------------------------------------------------------------------- /tests/pngsuite-corrupt/xc9n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-corrupt/xc9n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-corrupt/xcrn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-corrupt/xcrn0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-corrupt/xcsn0g01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-corrupt/xcsn0g01.png -------------------------------------------------------------------------------- /tests/pngsuite-corrupt/xd0n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-corrupt/xd0n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-corrupt/xd3n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-corrupt/xd3n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-corrupt/xd9n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-corrupt/xd9n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-corrupt/xdtn0g01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-corrupt/xdtn0g01.png -------------------------------------------------------------------------------- /tests/pngsuite-corrupt/xhdn0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-corrupt/xhdn0g08.png -------------------------------------------------------------------------------- /tests/pngsuite-corrupt/xlfn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-corrupt/xlfn0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-corrupt/xs1n0g01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-corrupt/xs1n0g01.png -------------------------------------------------------------------------------- /tests/pngsuite-corrupt/xs2n0g01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-corrupt/xs2n0g01.png -------------------------------------------------------------------------------- /tests/pngsuite-corrupt/xs4n0g01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-corrupt/xs4n0g01.png -------------------------------------------------------------------------------- /tests/pngsuite-corrupt/xs7n0g01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-corrupt/xs7n0g01.png -------------------------------------------------------------------------------- /tests/pngsuite-filtering/f00n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-filtering/f00n0g08.png -------------------------------------------------------------------------------- /tests/pngsuite-filtering/f00n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-filtering/f00n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-filtering/f01n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-filtering/f01n0g08.png -------------------------------------------------------------------------------- /tests/pngsuite-filtering/f01n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-filtering/f01n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-filtering/f02n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-filtering/f02n0g08.png -------------------------------------------------------------------------------- /tests/pngsuite-filtering/f02n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-filtering/f02n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-filtering/f03n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-filtering/f03n0g08.png -------------------------------------------------------------------------------- /tests/pngsuite-filtering/f03n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-filtering/f03n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-filtering/f04n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-filtering/f04n0g08.png -------------------------------------------------------------------------------- /tests/pngsuite-filtering/f04n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-filtering/f04n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-filtering/f99n0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-filtering/f99n0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g03n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g03n0g16.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g03n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g03n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g03n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g03n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g04n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g04n0g16.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g04n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g04n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g04n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g04n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g05n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g05n0g16.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g05n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g05n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g05n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g05n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g07n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g07n0g16.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g07n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g07n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g07n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g07n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g10n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g10n0g16.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g10n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g10n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g10n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g10n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g25n0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g25n0g16.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g25n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g25n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-gamma/g25n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-gamma/g25n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-interlaced/basi0g01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-interlaced/basi0g01.png -------------------------------------------------------------------------------- /tests/pngsuite-interlaced/basi0g02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-interlaced/basi0g02.png -------------------------------------------------------------------------------- /tests/pngsuite-interlaced/basi0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-interlaced/basi0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-interlaced/basi0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-interlaced/basi0g08.png -------------------------------------------------------------------------------- /tests/pngsuite-interlaced/basi0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-interlaced/basi0g16.png -------------------------------------------------------------------------------- /tests/pngsuite-interlaced/basi2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-interlaced/basi2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-interlaced/basi2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-interlaced/basi2c16.png -------------------------------------------------------------------------------- /tests/pngsuite-interlaced/basi3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-interlaced/basi3p01.png -------------------------------------------------------------------------------- /tests/pngsuite-interlaced/basi3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-interlaced/basi3p02.png -------------------------------------------------------------------------------- /tests/pngsuite-interlaced/basi3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-interlaced/basi3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-interlaced/basi3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-interlaced/basi3p08.png -------------------------------------------------------------------------------- /tests/pngsuite-interlaced/basi4a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-interlaced/basi4a08.png -------------------------------------------------------------------------------- /tests/pngsuite-interlaced/basi4a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-interlaced/basi4a16.png -------------------------------------------------------------------------------- /tests/pngsuite-interlaced/basi6a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-interlaced/basi6a08.png -------------------------------------------------------------------------------- /tests/pngsuite-interlaced/basi6a16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-interlaced/basi6a16.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s01i3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s01i3p01.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s01n3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s01n3p01.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s02i3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s02i3p01.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s02n3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s02n3p01.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s03i3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s03i3p01.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s03n3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s03n3p01.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s04i3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s04i3p01.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s04n3p01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s04n3p01.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s05i3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s05i3p02.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s05n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s05n3p02.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s06i3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s06i3p02.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s06n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s06n3p02.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s07i3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s07i3p02.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s07n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s07n3p02.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s08i3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s08i3p02.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s08n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s08n3p02.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s09i3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s09i3p02.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s09n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s09n3p02.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s32i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s32i3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s32n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s32n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s33i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s33i3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s33n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s33n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s34i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s34i3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s34n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s34n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s35i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s35i3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s35n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s35n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s36i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s36i3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s36n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s36n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s37i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s37i3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s37n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s37n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s38i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s38i3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s38n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s38n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s39i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s39i3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s39n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s39n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s40i3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s40i3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-oddsizes/s40n3p04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-oddsizes/s40n3p04.png -------------------------------------------------------------------------------- /tests/pngsuite-palette/pp0n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-palette/pp0n2c16.png -------------------------------------------------------------------------------- /tests/pngsuite-palette/pp0n6a08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-palette/pp0n6a08.png -------------------------------------------------------------------------------- /tests/pngsuite-palette/ps1n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-palette/ps1n0g08.png -------------------------------------------------------------------------------- /tests/pngsuite-palette/ps1n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-palette/ps1n2c16.png -------------------------------------------------------------------------------- /tests/pngsuite-palette/ps2n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-palette/ps2n0g08.png -------------------------------------------------------------------------------- /tests/pngsuite-palette/ps2n2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-palette/ps2n2c16.png -------------------------------------------------------------------------------- /tests/pngsuite-transparency/tbbn0g04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-transparency/tbbn0g04.png -------------------------------------------------------------------------------- /tests/pngsuite-transparency/tbbn2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-transparency/tbbn2c16.png -------------------------------------------------------------------------------- /tests/pngsuite-transparency/tbbn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-transparency/tbbn3p08.png -------------------------------------------------------------------------------- /tests/pngsuite-transparency/tbgn2c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-transparency/tbgn2c16.png -------------------------------------------------------------------------------- /tests/pngsuite-transparency/tbgn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-transparency/tbgn3p08.png -------------------------------------------------------------------------------- /tests/pngsuite-transparency/tbrn2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-transparency/tbrn2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-transparency/tbwn0g16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-transparency/tbwn0g16.png -------------------------------------------------------------------------------- /tests/pngsuite-transparency/tbwn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-transparency/tbwn3p08.png -------------------------------------------------------------------------------- /tests/pngsuite-transparency/tbyn3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-transparency/tbyn3p08.png -------------------------------------------------------------------------------- /tests/pngsuite-transparency/tm3n3p02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-transparency/tm3n3p02.png -------------------------------------------------------------------------------- /tests/pngsuite-transparency/tp0n0g08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-transparency/tp0n0g08.png -------------------------------------------------------------------------------- /tests/pngsuite-transparency/tp0n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-transparency/tp0n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-transparency/tp0n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-transparency/tp0n3p08.png -------------------------------------------------------------------------------- /tests/pngsuite-transparency/tp1n3p08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-transparency/tp1n3p08.png -------------------------------------------------------------------------------- /tests/pngsuite-zlib/z00n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-zlib/z00n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-zlib/z03n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-zlib/z03n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-zlib/z06n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-zlib/z06n2c08.png -------------------------------------------------------------------------------- /tests/pngsuite-zlib/z09n2c08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AldaronLau/png_pong/0ef3f5d123e4947c7f89a940d724fa2951f2b2e6/tests/pngsuite-zlib/z09n2c08.png -------------------------------------------------------------------------------- /tests/roundtrip.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use pix::{ 4 | chan::Ch8, 5 | el::Pixel, 6 | gray::SGray8, 7 | rgb::{SRgb8, SRgba8}, 8 | Raster, 9 | }; 10 | use png_pong::{Decoder, Encoder, PngRaster}; 11 | 12 | fn roundtrip_core>(raster_a: PngRaster) -> Raster { 13 | // Encode as SRgba8 14 | let mut file = Vec::::new(); 15 | let mut encoder = Encoder::new(&mut file).into_step_enc(); 16 | encoder.still(&raster_a).unwrap(); 17 | 18 | // Decode as SRgba8 19 | let mut decoder = Decoder::new(Cursor::new(file)) 20 | .expect("Not PNG") 21 | .into_steps(); 22 | let raster_b: Raster = decoder.next().unwrap().unwrap().raster.into(); 23 | 24 | // 25 | let raster_a: Raster = raster_a.into(); 26 | 27 | assert_eq!(raster_a.as_u8_slice().len(), raster_b.as_u8_slice().len()); 28 | assert_eq!(raster_a.as_u8_slice(), raster_b.as_u8_slice()); 29 | 30 | raster_b 31 | } 32 | 33 | fn roundtrip>(filename: &str) -> Raster { 34 | // Decode as SRgba8 35 | let file = std::fs::read(filename).unwrap(); 36 | let mut decoder = Decoder::new(Cursor::new(file)) 37 | .expect("Not PNG") 38 | .into_steps(); 39 | let raster_a = decoder.next().unwrap().unwrap().raster; 40 | 41 | roundtrip_core(raster_a) 42 | } 43 | 44 | #[test] 45 | fn crushed() { 46 | let a = roundtrip::("tests/png/0.png"); 47 | let b = roundtrip::("tests/png/1.png"); 48 | let c = roundtrip::("tests/png/2.png"); 49 | let d = roundtrip::("tests/png/3.png"); 50 | let aa = roundtrip::("tests/png/4.png"); 51 | let bb = roundtrip::("tests/png/5.png"); 52 | let cc = roundtrip::("tests/png/6.png"); 53 | let dd = roundtrip::("tests/png/7.png"); 54 | assert_eq!(a.as_u8_slice(), aa.as_u8_slice()); 55 | assert_eq!(b.as_u8_slice(), bb.as_u8_slice()); 56 | assert_eq!(c.as_u8_slice(), cc.as_u8_slice()); 57 | assert_eq!(d.as_u8_slice(), dd.as_u8_slice()); 58 | } 59 | 60 | #[test] 61 | fn fry() { 62 | roundtrip::("tests/png/fry.png"); 63 | } 64 | 65 | #[test] 66 | fn gray() { 67 | roundtrip::("tests/png/gray.png"); 68 | } 69 | 70 | #[test] 71 | fn random() { 72 | let mut data = vec![0u8; 639 * 479 * 3]; 73 | for (i, px) in data.iter_mut().enumerate() { 74 | *px = ((i ^ (13 + i * 17) ^ (i * 13) ^ (i / 113 * 11)) >> 5) as u8; 75 | } 76 | 77 | let raster = PngRaster::Rgb8(Raster::::with_u8_buffer( 78 | 639, 79 | 479, 80 | data.as_slice(), 81 | )); 82 | roundtrip_core::(raster); 83 | } 84 | 85 | // FIXME: Text 86 | /* 87 | #[test] 88 | fn text_chunks() { 89 | let mut s = State::new(); 90 | s.encoder.text_compression = 0; 91 | let longstr = "World 123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_"; 92 | assert!(longstr.len() > 89); 93 | s.info_png_mut().add_text("Hello", longstr); 94 | assert_eq!(1, s.info_png().text_keys_cstr().count()); 95 | 96 | let raster: pix::Raster = 97 | pix::RasterBuilder::new().with_u8_buffer(1, 1, &[0u8, 0, 0, 0][..]); 98 | 99 | let data = s.encode(&raster).unwrap(); 100 | 101 | assert!(data.windows(4).any(|w| w == b"tEXt")); 102 | 103 | let mut s = State::new(); 104 | s.read_text_chunks(true); 105 | s.decode(data).unwrap(); 106 | assert_eq!(1, s.info_png().text_keys_cstr().count()); 107 | }*/ 108 | --------------------------------------------------------------------------------