0

[rust png] Roll png crate to version 0.18*

This CL has been created semi-automatically.  Manual changes have been
made as follows:

* Before automated update:
    - Switch to 0.18 in
      `//third_party/rust/chromium_crates_io/Cargo.toml`
      (`=...` syntax is needed because 0.18 is a "prerelease")
    - Remove 0.17-targeting patches under
      `//third_party/rust/chromium_crates_io/patches/png`
* After automated update:
    - Remove old `//third_party/rust/foo/vX` directories
    - Edit `skia/BUILD.gn` to enable `png_0_18` crate feature
      (this part depends on http://review.skia.org/958801)
    - Record the audit in
      `.../chromium_crates_io/supply-chain/audits.toml`

Updated crates:

* png: 0.17.16 => 0.18.0-rc

Removed crates:

* bitflags@1.3.2

Chromium `supply-chain/config.toml` policy requires that the following
audit criteria are met (note that these are the *minimum* required
criteria and `supply-chain/audits.toml` can and should record a stricter
certification if possible;  see also //docs/rust-unsafe.md):

* png@0.18.0-rc: crypto-safe, safe-to-deploy, ub-risk-2

Bug: None
Change-Id: I9008912d7ac58b66887b7bf276367e56aad51828
Cq-Include-Trybots: chromium/try:android-rust-arm32-rel
Cq-Include-Trybots: chromium/try:android-rust-arm64-dbg
Cq-Include-Trybots: chromium/try:android-rust-arm64-rel
Cq-Include-Trybots: chromium/try:linux-rust-x64-dbg
Cq-Include-Trybots: chromium/try:linux-rust-x64-rel
Cq-Include-Trybots: chromium/try:win-rust-x64-dbg
Cq-Include-Trybots: chromium/try:win-rust-x64-rel
Disable-Rts: True
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6318699
Reviewed-by: Liza Burakova <liza@chromium.org>
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Łukasz Anforowicz <lukasza@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1429744}
This commit is contained in:
Lukasz Anforowicz
2025-03-07 14:05:01 -08:00
committed by Chromium LUCI CQ
parent 0b14f5ae80
commit 2654c789bc
87 changed files with 1940 additions and 6233 deletions

@ -630,7 +630,8 @@ if (enable_rust_png) {
sources = skia_codec_rust_png_ffi_rs_srcs
cxx_bindings = skia_codec_rust_png_ffi_cxx_bridge_srcs
public_deps = [ ":rust_png_ffi_cpp" ]
deps = [ "//third_party/rust/png/v0_17:lib" ]
deps = [ "//third_party/rust/png/v0_18:lib" ]
features = [ "png_0_18" ]
configs -= [ "//build/config/compiler:chromium_code" ]
configs += [
":skia_config",

@ -40,11 +40,6 @@ name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "2.9.0"
@ -115,7 +110,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"base64",
"bitflags 2.9.0",
"bitflags",
"bstr",
"bytemuck",
"bytes",
@ -954,10 +949,10 @@ dependencies = [
[[package]]
name = "png"
version = "0.17.16"
version = "0.18.0-rc"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.3.2",
"bitflags",
"crc32fast",
"fdeflate",
"flate2",

@ -30,7 +30,7 @@ itertools = "0.11"
lazy_static = "1"
libc = "0.2"
nom = "7.1.3"
png = "0.17"
png = "=0.18.0-rc"
proc-macro2 = "1"
prost = "0.13.3"
qr_code = "2"

@ -1,72 +0,0 @@
From aa16ef97ab23c52b34c43e9d98d7f3cb56ad35f1 Mon Sep 17 00:00:00 2001
From: Lukasz Anforowicz <lukasza@chromium.org>
Date: Fri, 17 Jan 2025 23:10:45 +0000
Subject: [PATCH] Add support for parsing `eXIf` chunk.
The `eXIf` chunk was already supported in `encoder.rs`, but this commit
also adds support to `decoder/stream.rs`. This commit is needed for
parity with the existing PNG decoder in Blink / Chromium - see
https://crbug.com/390707316
---
src/decoder/stream.rs | 24 +++++++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/decoder/stream.rs b/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/decoder/stream.rs
index 1b00968..904d3be 100644
--- a/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/decoder/stream.rs
+++ b/third_party/rust/chromium_crates_io/vendor/png-0.17.14/src/decoder/stream.rs
@@ -989,6 +989,7 @@ impl StreamingDecoder {
chunk::cICP => Ok(self.parse_cicp()),
chunk::mDCV => Ok(self.parse_mdcv()),
chunk::cLLI => Ok(self.parse_clli()),
+ chunk::eXIf => Ok(self.parse_exif()),
chunk::bKGD => Ok(self.parse_bkgd()),
chunk::iCCP if !self.decode_options.ignore_iccp_chunk => self.parse_iccp(),
chunk::tEXt if !self.decode_options.ignore_text_chunk => self.parse_text(),
@@ -1487,6 +1488,16 @@ impl StreamingDecoder {
Decoded::Nothing
}
+ fn parse_exif(&mut self) -> Decoded {
+ // We ignore a second, duplicated eXIf chunk (if any).
+ let info = self.info.as_mut().unwrap();
+ if info.exif_metadata.is_none() {
+ info.exif_metadata = Some(self.current_chunk.raw_bytes.clone().into());
+ }
+
+ Decoded::Nothing
+ }
+
fn parse_iccp(&mut self) -> Result<Decoded, DecodingError> {
if self.have_idat {
Err(DecodingError::Format(
@@ -2125,7 +2136,7 @@ mod tests {
assert!(decoder.read_info().is_ok());
}
- /// Test handling of `mDCV` and `cLLI` chunks.`
+ /// Test handling of `mDCV` and `cLLI` chunks.
#[test]
fn test_mdcv_and_clli_chunks() {
let decoder = crate::Decoder::new(File::open("tests/bugfixes/cicp_pq.png").unwrap());
@@ -2155,6 +2166,17 @@ mod tests {
assert_relative_eq!(clli.max_frame_average_light_level as f32 / 10_000.0, 2627.0);
}
+ /// Test handling of `eXIf` chunk.
+ #[test]
+ fn test_exif_chunk() {
+ let decoder =
+ crate::Decoder::new(File::open("tests/bugfixes/F-exif-chunk-early.png").unwrap());
+ let reader = decoder.read_info().unwrap();
+ let info = reader.info();
+ let exif = info.exif_metadata.as_ref().unwrap().as_ref();
+ assert_eq!(exif.len(), 90);
+ }
+
/// Tests what happens then [`Reader.finish`] is called twice.
#[test]
fn test_finishing_twice() {
--
2.48.1.262.g85cc9f2d1e-goog

@ -2210,6 +2210,12 @@ who = "Adrian Taylor <adetaylor@chromium.org>"
criteria = ["safe-to-deploy", "does-not-implement-crypto", "ub-risk-0"]
delta = "0.17.15 -> 0.17.16"
[[audits.png]]
who = "Lukasz Anforowicz <lukasza@chromium.org>"
criteria = ["safe-to-deploy", "does-not-implement-crypto", "ub-risk-0"]
delta = "0.17.16 -> 0.18.0-rc"
notes = "Still no `unsafe`, no cryptography, and justified usage of `std::fs`."
[[audits.potential_utf]]
who = "Manish Goregaokar <manishearth@google.com>"
criteria = ["safe-to-deploy", "does-not-implement-crypto", "ub-risk-2"]

@ -62,9 +62,6 @@ criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"]
[policy."base64:0.22.1"]
criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"]
[policy."bitflags:1.3.2"]
criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"]
[policy."bitflags:2.9.0"]
criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"]
@ -332,7 +329,7 @@ criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"]
[policy."num-traits:0.2.19"]
criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"]
[policy."png:0.17.16"]
[policy."png:0.18.0-rc"]
criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"]
[policy."potential_utf:0.1.0"]

@ -1,5 +0,0 @@
{
"git": {
"sha1": "ed185cfb1c447c1b4bd6ac021c9ec3bb02c9e2f2"
}
}

@ -1,56 +0,0 @@
name: Rust
on: [push, pull_request]
env:
CARGO_TERM_COLOR: always
jobs:
check:
name: Test
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
rust:
- stable
- beta
- nightly
- 1.46.0
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
- name: Default features
uses: actions-rs/cargo@v1
with:
command: test
args: --features example_generated
embedded:
name: Build (embedded)
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
target: thumbv6m-none-eabi
override: true
- name: Default features
uses: actions-rs/cargo@v1
with:
command: build
args: -Z avoid-dev-deps --features example_generated --target thumbv6m-none-eabi

@ -1,5 +0,0 @@
wip
target
Cargo.lock
/.idea/

@ -1,206 +0,0 @@
# 1.3.2
- Allow `non_snake_case` in generated flags types ([#256])
[#252]: https://github.com/bitflags/bitflags/pull/256
# 1.3.1
- Revert unconditional `#[repr(transparent)]` ([#252])
[#252]: https://github.com/bitflags/bitflags/pull/252
# 1.3.0 (yanked)
- Add `#[repr(transparent)]` ([#187])
- End `empty` doc comment with full stop ([#202])
- Fix typo in crate root docs ([#206])
- Document from_bits_unchecked unsafety ([#207])
- Let `is_all` ignore extra bits ([#211])
- Allows empty flag definition ([#225])
- Making crate accessible from std ([#227])
- Make `from_bits` a const fn ([#229])
- Allow multiple bitflags structs in one macro invocation ([#235])
- Add named functions to perform set operations ([#244])
- Fix typos in method docs ([#245])
- Modernization of the `bitflags` macro to take advantage of newer features and 2018 idioms ([#246])
- Fix regression (in an unreleased feature) and simplify tests ([#247])
- Use `Self` and fix bug when overriding `stringify!` ([#249])
[#187]: https://github.com/bitflags/bitflags/pull/187
[#202]: https://github.com/bitflags/bitflags/pull/202
[#206]: https://github.com/bitflags/bitflags/pull/206
[#207]: https://github.com/bitflags/bitflags/pull/207
[#211]: https://github.com/bitflags/bitflags/pull/211
[#225]: https://github.com/bitflags/bitflags/pull/225
[#227]: https://github.com/bitflags/bitflags/pull/227
[#229]: https://github.com/bitflags/bitflags/pull/229
[#235]: https://github.com/bitflags/bitflags/pull/235
[#244]: https://github.com/bitflags/bitflags/pull/244
[#245]: https://github.com/bitflags/bitflags/pull/245
[#246]: https://github.com/bitflags/bitflags/pull/246
[#247]: https://github.com/bitflags/bitflags/pull/247
[#249]: https://github.com/bitflags/bitflags/pull/249
# 1.2.1
- Remove extraneous `#[inline]` attributes ([#194])
[#194]: https://github.com/bitflags/bitflags/pull/194
# 1.2.0
- Fix typo: {Lower, Upper}Exp - {Lower, Upper}Hex ([#183])
- Add support for "unknown" bits ([#188])
[#183]: https://github.com/rust-lang-nursery/bitflags/pull/183
[#188]: https://github.com/rust-lang-nursery/bitflags/pull/188
# 1.1.0
This is a re-release of `1.0.5`, which was yanked due to a bug in the RLS.
# 1.0.5
- Use compiletest_rs flags supported by stable toolchain ([#171])
- Put the user provided attributes first ([#173])
- Make bitflags methods `const` on newer compilers ([#175])
[#171]: https://github.com/rust-lang-nursery/bitflags/pull/171
[#173]: https://github.com/rust-lang-nursery/bitflags/pull/173
[#175]: https://github.com/rust-lang-nursery/bitflags/pull/175
# 1.0.4
- Support Rust 2018 style macro imports ([#165])
```rust
use bitflags::bitflags;
```
[#165]: https://github.com/rust-lang-nursery/bitflags/pull/165
# 1.0.3
- Improve zero value flag handling and documentation ([#157])
[#157]: https://github.com/rust-lang-nursery/bitflags/pull/157
# 1.0.2
- 30% improvement in compile time of bitflags crate ([#156])
- Documentation improvements ([#153])
- Implementation cleanup ([#149])
[#156]: https://github.com/rust-lang-nursery/bitflags/pull/156
[#153]: https://github.com/rust-lang-nursery/bitflags/pull/153
[#149]: https://github.com/rust-lang-nursery/bitflags/pull/149
# 1.0.1
- Add support for `pub(restricted)` specifier on the bitflags struct ([#135])
- Optimize performance of `all()` when called from a separate crate ([#136])
[#135]: https://github.com/rust-lang-nursery/bitflags/pull/135
[#136]: https://github.com/rust-lang-nursery/bitflags/pull/136
# 1.0.0
- **[breaking change]** Macro now generates [associated constants](https://doc.rust-lang.org/reference/items.html#associated-constants) ([#24])
- **[breaking change]** Minimum supported version is Rust **1.20**, due to usage of associated constants
- After being broken in 0.9, the `#[deprecated]` attribute is now supported again ([#112])
- Other improvements to unit tests and documentation ([#106] and [#115])
[#24]: https://github.com/rust-lang-nursery/bitflags/pull/24
[#106]: https://github.com/rust-lang-nursery/bitflags/pull/106
[#112]: https://github.com/rust-lang-nursery/bitflags/pull/112
[#115]: https://github.com/rust-lang-nursery/bitflags/pull/115
## How to update your code to use associated constants
Assuming the following structure definition:
```rust
bitflags! {
struct Something: u8 {
const FOO = 0b01,
const BAR = 0b10
}
}
```
In 0.9 and older you could do:
```rust
let x = FOO.bits | BAR.bits;
```
Now you must use:
```rust
let x = Something::FOO.bits | Something::BAR.bits;
```
# 0.9.1
- Fix the implementation of `Formatting` traits when other formatting traits were present in scope ([#105])
[#105]: https://github.com/rust-lang-nursery/bitflags/pull/105
# 0.9.0
- **[breaking change]** Use struct keyword instead of flags to define bitflag types ([#84])
- **[breaking change]** Terminate const items with semicolons instead of commas ([#87])
- Implement the `Hex`, `Octal`, and `Binary` formatting traits ([#86])
- Printing an empty flag value with the `Debug` trait now prints "(empty)" instead of nothing ([#85])
- The `bitflags!` macro can now be used inside of a fn body, to define a type local to that function ([#74])
[#74]: https://github.com/rust-lang-nursery/bitflags/pull/74
[#84]: https://github.com/rust-lang-nursery/bitflags/pull/84
[#85]: https://github.com/rust-lang-nursery/bitflags/pull/85
[#86]: https://github.com/rust-lang-nursery/bitflags/pull/86
[#87]: https://github.com/rust-lang-nursery/bitflags/pull/87
# 0.8.2
- Update feature flag used when building bitflags as a dependency of the Rust toolchain
# 0.8.1
- Allow bitflags to be used as a dependency of the Rust toolchain
# 0.8.0
- Add support for the experimental `i128` and `u128` integer types ([#57])
- Add set method: `flags.set(SOME_FLAG, true)` or `flags.set(SOME_FLAG, false)` ([#55])
This may break code that defines its own set method
[#55]: https://github.com/rust-lang-nursery/bitflags/pull/55
[#57]: https://github.com/rust-lang-nursery/bitflags/pull/57
# 0.7.1
*(yanked)*
# 0.7.0
- Implement the Extend trait ([#49])
- Allow definitions inside the `bitflags!` macro to refer to items imported from other modules ([#51])
[#49]: https://github.com/rust-lang-nursery/bitflags/pull/49
[#51]: https://github.com/rust-lang-nursery/bitflags/pull/51
# 0.6.0
- The `no_std` feature was removed as it is now the default
- The `assignment_operators` feature was remove as it is now enabled by default
- Some clippy suggestions have been applied

@ -1,73 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance, race,
religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at coc@senaite.org. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org

@ -1,58 +0,0 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies
#
# If you believe there's an error in this file please file an
# issue against the rust-lang/cargo repository. If you're
# editing this file be aware that the upstream Cargo.toml
# will likely look very different (and much more reasonable)
[package]
edition = "2018"
name = "bitflags"
version = "1.3.2"
authors = ["The Rust Project Developers"]
exclude = ["bors.toml"]
description = "A macro to generate structures which behave like bitflags.\n"
homepage = "https://github.com/bitflags/bitflags"
documentation = "https://docs.rs/bitflags"
readme = "README.md"
keywords = ["bit", "bitmask", "bitflags", "flags"]
categories = ["no-std"]
license = "MIT/Apache-2.0"
repository = "https://github.com/bitflags/bitflags"
[package.metadata.docs.rs]
features = ["example_generated"]
[dependencies.compiler_builtins]
version = "0.1.2"
optional = true
[dependencies.core]
version = "1.0.0"
optional = true
package = "rustc-std-workspace-core"
[dev-dependencies.rustversion]
version = "1.0"
[dev-dependencies.serde]
version = "1.0"
[dev-dependencies.serde_derive]
version = "1.0"
[dev-dependencies.serde_json]
version = "1.0"
[dev-dependencies.trybuild]
version = "1.0"
[dev-dependencies.walkdir]
version = "2.3"
[features]
default = []
example_generated = []
rustc-dep-of-std = ["core", "compiler_builtins"]

@ -1,39 +0,0 @@
[package]
name = "bitflags"
# NB: When modifying, also modify:
# 1. html_root_url in lib.rs
# 2. number in readme (for breaking changes)
version = "1.3.2"
edition = "2018"
authors = ["The Rust Project Developers"]
license = "MIT/Apache-2.0"
keywords = ["bit", "bitmask", "bitflags", "flags"]
readme = "README.md"
repository = "https://github.com/bitflags/bitflags"
homepage = "https://github.com/bitflags/bitflags"
documentation = "https://docs.rs/bitflags"
categories = ["no-std"]
description = """
A macro to generate structures which behave like bitflags.
"""
exclude = ["bors.toml"]
[dependencies]
core = { version = '1.0.0', optional = true, package = 'rustc-std-workspace-core' }
compiler_builtins = { version = '0.1.2', optional = true }
[dev-dependencies]
trybuild = "1.0"
rustversion = "1.0"
walkdir = "2.3"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
[features]
default = []
example_generated = []
rustc-dep-of-std = ["core", "compiler_builtins"]
[package.metadata.docs.rs]
features = ["example_generated"]

@ -1,25 +0,0 @@
Copyright (c) 2014 The Rust Project Developers
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

@ -1,32 +0,0 @@
bitflags
========
[![Rust](https://github.com/bitflags/bitflags/workflows/Rust/badge.svg)](https://github.com/bitflags/bitflags/actions)
[![Join the chat at https://gitter.im/bitflags/Lobby](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/bitflags/Lobby?utm_source=badge&utm_medium=badge&utm_content=badge)
[![Latest version](https://img.shields.io/crates/v/bitflags.svg)](https://crates.io/crates/bitflags)
[![Documentation](https://docs.rs/bitflags/badge.svg)](https://docs.rs/bitflags)
![License](https://img.shields.io/crates/l/bitflags.svg)
A Rust macro to generate structures which behave like a set of bitflags
- [Documentation](https://docs.rs/bitflags)
- [Release notes](https://github.com/bitflags/bitflags/releases)
## Usage
Add this to your `Cargo.toml`:
```toml
[dependencies]
bitflags = "1.3"
```
and this to your source code:
```rust
use bitflags::bitflags;
```
## Rust Version Support
The minimum supported Rust version is 1.46 due to use of associated constants and const functions.

@ -1,14 +0,0 @@
//! This module shows an example of code generated by the macro. **IT MUST NOT BE USED OUTSIDE THIS
//! CRATE**.
bitflags! {
/// This is the same `Flags` struct defined in the [crate level example](../index.html#example).
/// Note that this struct is just for documentation purposes only, it must not be used outside
/// this crate.
pub struct Flags: u32 {
const A = 0b00000001;
const B = 0b00000010;
const C = 0b00000100;
const ABC = Self::A.bits | Self::B.bits | Self::C.bits;
}
}

File diff suppressed because it is too large Load Diff

@ -1,20 +0,0 @@
#![no_std]
use bitflags::bitflags;
bitflags! {
/// baz
struct Flags: u32 {
const A = 0b00000001;
#[doc = "bar"]
const B = 0b00000010;
const C = 0b00000100;
#[doc = "foo"]
const ABC = Flags::A.bits | Flags::B.bits | Flags::C.bits;
}
}
#[test]
fn basic() {
assert_eq!(Flags::ABC, Flags::A | Flags::B | Flags::C);
}

@ -1,10 +0,0 @@
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy)]
struct Flags: u32 {
const A = 0b00000001;
}
}
fn main() {}

@ -1,27 +0,0 @@
error[E0119]: conflicting implementations of trait `std::clone::Clone` for type `Flags`
--> $DIR/copy.rs:3:1
|
3 | / bitflags! {
4 | | #[derive(Clone, Copy)]
| | ----- first implementation here
5 | | struct Flags: u32 {
6 | | const A = 0b00000001;
7 | | }
8 | | }
| |_^ conflicting implementation for `Flags`
|
= note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0119]: conflicting implementations of trait `std::marker::Copy` for type `Flags`
--> $DIR/copy.rs:3:1
|
3 | / bitflags! {
4 | | #[derive(Clone, Copy)]
| | ---- first implementation here
5 | | struct Flags: u32 {
6 | | const A = 0b00000001;
7 | | }
8 | | }
| |_^ conflicting implementation for `Flags`
|
= note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)

@ -1,10 +0,0 @@
use bitflags::bitflags;
bitflags! {
#[derive(PartialEq, Eq)]
struct Flags: u32 {
const A = 0b00000001;
}
}
fn main() {}

@ -1,55 +0,0 @@
error[E0119]: conflicting implementations of trait `std::cmp::PartialEq` for type `Flags`
--> $DIR/eq.rs:3:1
|
3 | / bitflags! {
4 | | #[derive(PartialEq, Eq)]
| | --------- first implementation here
5 | | struct Flags: u32 {
6 | | const A = 0b00000001;
7 | | }
8 | | }
| |_^ conflicting implementation for `Flags`
|
= note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0119]: conflicting implementations of trait `std::cmp::Eq` for type `Flags`
--> $DIR/eq.rs:3:1
|
3 | / bitflags! {
4 | | #[derive(PartialEq, Eq)]
| | -- first implementation here
5 | | struct Flags: u32 {
6 | | const A = 0b00000001;
7 | | }
8 | | }
| |_^ conflicting implementation for `Flags`
|
= note: this error originates in the derive macro `Eq` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0119]: conflicting implementations of trait `std::marker::StructuralPartialEq` for type `Flags`
--> $DIR/eq.rs:3:1
|
3 | / bitflags! {
4 | | #[derive(PartialEq, Eq)]
| | --------- first implementation here
5 | | struct Flags: u32 {
6 | | const A = 0b00000001;
7 | | }
8 | | }
| |_^ conflicting implementation for `Flags`
|
= note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0119]: conflicting implementations of trait `std::marker::StructuralEq` for type `Flags`
--> $DIR/eq.rs:3:1
|
3 | / bitflags! {
4 | | #[derive(PartialEq, Eq)]
| | -- first implementation here
5 | | struct Flags: u32 {
6 | | const A = 0b00000001;
7 | | }
8 | | }
| |_^ conflicting implementation for `Flags`
|
= note: this error originates in the derive macro `Eq` (in Nightly builds, run with -Z macro-backtrace for more info)

@ -1,123 +0,0 @@
use std::{
fmt::{
self,
Debug,
Display,
LowerHex,
UpperHex,
Octal,
Binary,
},
ops::{
BitAnd,
BitOr,
BitXor,
BitAndAssign,
BitOrAssign,
BitXorAssign,
Not,
},
};
use bitflags::bitflags;
// Ideally we'd actually want this to work, but currently need something like `num`'s `Zero`
// With some design work it could be made possible
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct MyInt(u8);
impl BitAnd for MyInt {
type Output = Self;
fn bitand(self, other: Self) -> Self {
MyInt(self.0 & other.0)
}
}
impl BitOr for MyInt {
type Output = Self;
fn bitor(self, other: Self) -> Self {
MyInt(self.0 | other.0)
}
}
impl BitXor for MyInt {
type Output = Self;
fn bitxor(self, other: Self) -> Self {
MyInt(self.0 ^ other.0)
}
}
impl BitAndAssign for MyInt {
fn bitand_assign(&mut self, other: Self) {
self.0 &= other.0
}
}
impl BitOrAssign for MyInt {
fn bitor_assign(&mut self, other: Self) {
self.0 |= other.0
}
}
impl BitXorAssign for MyInt {
fn bitxor_assign(&mut self, other: Self) {
self.0 ^= other.0
}
}
impl Debug for MyInt {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Debug::fmt(&self.0, f)
}
}
impl Display for MyInt {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl LowerHex for MyInt {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
LowerHex::fmt(&self.0, f)
}
}
impl UpperHex for MyInt {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
UpperHex::fmt(&self.0, f)
}
}
impl Octal for MyInt {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Octal::fmt(&self.0, f)
}
}
impl Binary for MyInt {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Binary::fmt(&self.0, f)
}
}
impl Not for MyInt {
type Output = MyInt;
fn not(self) -> Self {
MyInt(!self.0)
}
}
bitflags! {
struct Flags128: MyInt {
const A = MyInt(0b0000_0001u8);
const B = MyInt(0b0000_0010u8);
const C = MyInt(0b0000_0100u8);
}
}
fn main() {}

@ -1,27 +0,0 @@
error[E0308]: mismatched types
--> $DIR/all_defined.rs:115:1
|
115 | / bitflags! {
116 | | struct Flags128: MyInt {
117 | | const A = MyInt(0b0000_0001u8);
118 | | const B = MyInt(0b0000_0010u8);
119 | | const C = MyInt(0b0000_0100u8);
120 | | }
121 | | }
| |_^ expected struct `MyInt`, found integer
|
= note: this error originates in the macro `__impl_all_bitflags` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> $DIR/all_defined.rs:115:1
|
115 | / bitflags! {
116 | | struct Flags128: MyInt {
117 | | const A = MyInt(0b0000_0001u8);
118 | | const B = MyInt(0b0000_0010u8);
119 | | const C = MyInt(0b0000_0100u8);
120 | | }
121 | | }
| |_^ expected struct `MyInt`, found integer
|
= note: this error originates in the macro `__impl_bitflags` (in Nightly builds, run with -Z macro-backtrace for more info)

@ -1,13 +0,0 @@
use bitflags::bitflags;
struct MyInt(u8);
bitflags! {
struct Flags128: MyInt {
const A = MyInt(0b0000_0001);
const B = MyInt(0b0000_0010);
const C = MyInt(0b0000_0100);
}
}
fn main() {}

@ -1,13 +0,0 @@
error[E0204]: the trait `Copy` may not be implemented for this type
--> $DIR/all_missing.rs:5:1
|
5 | / bitflags! {
6 | | struct Flags128: MyInt {
7 | | const A = MyInt(0b0000_0001);
8 | | const B = MyInt(0b0000_0010);
9 | | const C = MyInt(0b0000_0100);
10 | | }
11 | | }
| |_^ this field does not implement `Copy`
|
= note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)

@ -1,13 +0,0 @@
mod example {
use bitflags::bitflags;
bitflags! {
pub struct Flags1: u32 {
const FLAG_A = 0b00000001;
}
}
}
fn main() {
let flag1 = example::Flags1::FLAG_A.bits;
}

@ -1,10 +0,0 @@
error[E0616]: field `bits` of struct `Flags1` is private
--> $DIR/private_field.rs:12:41
|
12 | let flag1 = example::Flags1::FLAG_A.bits;
| ^^^^ private field
|
help: a method `bits` also exists, call it with parentheses
|
12 | let flag1 = example::Flags1::FLAG_A.bits();
| ^^

@ -1,18 +0,0 @@
mod example {
use bitflags::bitflags;
bitflags! {
pub struct Flags1: u32 {
const FLAG_A = 0b00000001;
}
struct Flags2: u32 {
const FLAG_B = 0b00000010;
}
}
}
fn main() {
let flag1 = example::Flags1::FLAG_A;
let flag2 = example::Flags2::FLAG_B;
}

@ -1,18 +0,0 @@
error[E0603]: struct `Flags2` is private
--> $DIR/private_flags.rs:17:26
|
17 | let flag2 = example::Flags2::FLAG_B;
| ^^^^^^ private struct
|
note: the struct `Flags2` is defined here
--> $DIR/private_flags.rs:4:5
|
4 | / bitflags! {
5 | | pub struct Flags1: u32 {
6 | | const FLAG_A = 0b00000001;
7 | | }
... |
11 | | }
12 | | }
| |_____^
= note: this error originates in the macro `bitflags` (in Nightly builds, run with -Z macro-backtrace for more info)

@ -1,9 +0,0 @@
use bitflags::bitflags;
bitflags! {
pub struct Flags1: u32 {
pub const FLAG_A = 0b00000001;
}
}
fn main() {}

@ -1,5 +0,0 @@
error: no rules expected the token `pub`
--> $DIR/pub_const.rs:5:9
|
5 | pub const FLAG_A = 0b00000001;
| ^^^ no rules expected this token in macro call

@ -1,17 +0,0 @@
use bitflags::bitflags;
bitflags! {
struct Flags: u32 {
const A = 0b00000001;
}
}
impl From<u32> for Flags {
fn from(v: u32) -> Flags {
Flags::from_bits_truncate(v)
}
}
fn main() {
}

@ -1,10 +0,0 @@
use bitflags::bitflags;
bitflags! {
#[derive(Default)]
struct Flags: u32 {
const A = 0b00000001;
}
}
fn main() {}

@ -1,15 +0,0 @@
use bitflags::bitflags;
bitflags! {
struct Flags: u32 {
const A = 0b00000001;
}
}
impl Flags {
pub fn new() -> Flags {
Flags::A
}
}
fn main() {}

@ -1,14 +0,0 @@
use bitflags::bitflags;
// Checks for possible errors caused by overriding names used by `bitflags!` internally.
mod core {}
mod _core {}
bitflags! {
struct Test: u8 {
const A = 1;
}
}
fn main() {}

@ -1,19 +0,0 @@
use bitflags::bitflags;
// Checks for possible errors caused by overriding names used by `bitflags!` internally.
#[allow(unused_macros)]
macro_rules! stringify {
($($t:tt)*) => { "..." };
}
bitflags! {
struct Test: u8 {
const A = 1;
}
}
fn main() {
// Just make sure we don't call the redefined `stringify` macro
assert_eq!(format!("{:?}", Test::A), "A");
}

@ -1,10 +0,0 @@
use bitflags::bitflags;
bitflags! {
#[repr(C)]
struct Flags: u32 {
const A = 0b00000001;
}
}
fn main() {}

@ -1,10 +0,0 @@
use bitflags::bitflags;
bitflags! {
#[repr(transparent)]
struct Flags: u32 {
const A = 0b00000001;
}
}
fn main() {}

@ -1,11 +0,0 @@
use bitflags::bitflags;
bitflags! {
pub struct Flags1: u32 {
const FLAG_A = 0b00000001;
}
}
fn main() {
assert_eq!(0b00000001, Flags1::FLAG_A.bits);
}

@ -1,19 +0,0 @@
mod a {
mod b {
use bitflags::bitflags;
bitflags! {
pub(in crate::a) struct Flags: u32 {
const FLAG_A = 0b00000001;
}
}
}
pub fn flags() -> u32 {
b::Flags::FLAG_A.bits()
}
}
fn main() {
assert_eq!(0b00000001, a::flags());
}

@ -1,63 +0,0 @@
use std::{
fs,
ffi::OsStr,
io,
path::Path,
};
use walkdir::WalkDir;
#[test]
fn fail() {
prepare_stderr_files("tests/compile-fail").unwrap();
let t = trybuild::TestCases::new();
t.compile_fail("tests/compile-fail/**/*.rs");
}
#[test]
fn pass() {
let t = trybuild::TestCases::new();
t.pass("tests/compile-pass/**/*.rs");
}
// Compiler messages may change between versions
// We don't want to have to track these too closely for `bitflags`, but
// having some message to check makes sure user-facing errors are sensical.
//
// The approach we use is to run the test on all compilers, but only check stderr
// output on beta (which is the next stable release). We do this by default ignoring
// any `.stderr` files in the `compile-fail` directory, and copying `.stderr.beta` files
// when we happen to be running on a beta compiler.
fn prepare_stderr_files(path: impl AsRef<Path>) -> io::Result<()> {
for entry in WalkDir::new(path) {
let entry = entry?;
if entry.path().extension().and_then(OsStr::to_str) == Some("beta") {
let renamed = entry.path().with_extension("");
// Unconditionally remove a corresponding `.stderr` file for a `.stderr.beta`
// file if it exists. On `beta` compilers, we'll recreate it. On other compilers,
// we don't want to end up checking it anyways.
if renamed.exists() {
fs::remove_file(&renamed)?;
}
rename_beta_stderr(entry.path(), renamed)?;
}
}
Ok(())
}
#[rustversion::beta]
fn rename_beta_stderr(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
fs::copy(from, to)?;
Ok(())
}
#[rustversion::not(beta)]
fn rename_beta_stderr(_: impl AsRef<Path>, _: impl AsRef<Path>) -> io::Result<()> {
Ok(())
}

@ -1,6 +0,0 @@
{
"git": {
"sha1": "fbf256669ff23594bf4c618b61fde6a52b79e088"
},
"path_in_vcs": ""
}

File diff suppressed because it is too large Load Diff

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -1,198 +0,0 @@
use glium::{
backend::glutin::Display,
glutin::{
self, dpi,
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::ControlFlow,
},
texture::{ClientFormat, RawImage2d},
BlitTarget, Rect, Surface,
};
use std::{borrow::Cow, env, fs::File, io, path};
/// Load the image using `png`
fn load_image(path: &path::PathBuf) -> io::Result<RawImage2d<'static, u8>> {
use png::ColorType::*;
let mut decoder = png::Decoder::new(File::open(path)?);
decoder.set_transformations(png::Transformations::normalize_to_color8());
let mut reader = decoder.read_info()?;
let mut img_data = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut img_data)?;
let (data, format) = match info.color_type {
Rgb => (img_data, ClientFormat::U8U8U8),
Rgba => (img_data, ClientFormat::U8U8U8U8),
Grayscale => (
{
let mut vec = Vec::with_capacity(img_data.len() * 3);
for g in img_data {
vec.extend([g, g, g].iter().cloned())
}
vec
},
ClientFormat::U8U8U8,
),
GrayscaleAlpha => (
{
let mut vec = Vec::with_capacity(img_data.len() * 3);
for ga in img_data.chunks(2) {
let g = ga[0];
let a = ga[1];
vec.extend([g, g, g, a].iter().cloned())
}
vec
},
ClientFormat::U8U8U8U8,
),
_ => unreachable!("uncovered color type"),
};
Ok(RawImage2d {
data: Cow::Owned(data),
width: info.width,
height: info.height,
format,
})
}
fn main_loop(files: Vec<path::PathBuf>) -> io::Result<()> {
let mut files = files.into_iter();
let image = load_image(&files.next().unwrap())?;
let event_loop = glutin::event_loop::EventLoop::new();
let window_builder = glutin::window::WindowBuilder::new().with_title("Show Example");
let context_builder = glutin::ContextBuilder::new().with_vsync(true);
let display = glium::Display::new(window_builder, context_builder, &event_loop)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
resize_window(&display, &image);
let mut texture = glium::Texture2d::new(&display, image).unwrap();
draw(&display, &texture);
event_loop.run(move |event, _, control_flow| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => exit(control_flow),
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: code,
..
},
..
},
..
} => match code {
Some(VirtualKeyCode::Escape) => exit(control_flow),
Some(VirtualKeyCode::Right) => match &files.next() {
Some(path) => {
match load_image(path) {
Ok(image) => {
resize_window(&display, &image);
texture = glium::Texture2d::new(&display, image).unwrap();
draw(&display, &texture);
}
Err(err) => {
println!("Error: {}", err);
exit(control_flow);
}
};
}
None => exit(control_flow),
},
_ => {}
},
Event::RedrawRequested(_) => draw(&display, &texture),
_ => {}
});
}
fn draw(display: &glium::Display, texture: &glium::Texture2d) {
let frame = display.draw();
fill_v_flipped(
&texture.as_surface(),
&frame,
glium::uniforms::MagnifySamplerFilter::Linear,
);
frame.finish().unwrap();
}
fn exit(control_flow: &mut ControlFlow) {
*control_flow = ControlFlow::Exit;
}
fn fill_v_flipped<S1, S2>(src: &S1, target: &S2, filter: glium::uniforms::MagnifySamplerFilter)
where
S1: Surface,
S2: Surface,
{
let src_dim = src.get_dimensions();
let src_rect = Rect {
left: 0,
bottom: 0,
width: src_dim.0,
height: src_dim.1,
};
let target_dim = target.get_dimensions();
let target_rect = BlitTarget {
left: 0,
bottom: target_dim.1,
width: target_dim.0 as i32,
height: -(target_dim.1 as i32),
};
src.blit_color(&src_rect, target, &target_rect, filter);
}
fn resize_window(display: &Display, image: &RawImage2d<'static, u8>) {
let mut width = image.width;
let mut height = image.height;
if width < 50 && height < 50 {
width *= 10;
height *= 10;
}
display
.gl_window()
.window()
.set_inner_size(dpi::LogicalSize::new(f64::from(width), f64::from(height)));
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!("Usage: show files [...]");
} else {
let mut files = vec![];
for file in args.iter().skip(1) {
match if file.contains('*') {
(|| -> io::Result<_> {
for entry in glob::glob(file)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err.msg))?
{
files.push(
entry
.map_err(|_| io::Error::new(io::ErrorKind::Other, "glob error"))?,
)
}
Ok(())
})()
} else {
files.push(path::PathBuf::from(file));
Ok(())
} {
Ok(_) => (),
Err(err) => {
println!("{}: {}", file, err);
break;
}
}
}
// "tests/pngsuite/pngsuite.png"
match main_loop(files) {
Ok(_) => (),
Err(err) => println!("Error: {}", err),
}
}
}

@ -0,0 +1,6 @@
{
"git": {
"sha1": "eb9b5d7f371b88f15aaca6a8d21c58b86c400d76"
},
"path_in_vcs": ""
}

@ -1,3 +1,30 @@
## 0.18.0
### API Breaking Changes
* Removed deprecated `Info::encode` and `Encoder::set_srgb` methods.
* Improved the compression settings API for encoding.
* `Decoder` now requires a reader that implements `Seek` and `BufRead` traits.
* Bump bitflags dependency to 2.0.
### Other additions
* Added `Reader::read_row` method.
* Add support for parsing eXIf chunk.
* Treat most auxiliary chunk errors as benign.
## 0.17.16
* Make gAMA and cHRM fallback optional for sRGB ([#547])
* Pass through nightly feature to crc32fast crate to get SIMD crc32 on Aarch64 ([#545])
* Fix bug in iCCP chunk encoding ([#458])
* Deprecate Info::encode ([#550])
[#545]: https://github.com/image-rs/image-png/pull/545
[#458]: https://github.com/image-rs/image-png/pull/548
[#550]: https://github.com/image-rs/image-png/pull/550
[#547]: https://github.com/image-rs/image-png/pull/547
## 0.17.15
### Added
@ -7,7 +34,7 @@
* Add support for parsing the sBIT chunk ([#524])
* Add support for parsing the bKGD chunk ([#538])
* Add support for parsing the cICP chunk ([#529])
* Add support for parsing mDCv and cLLi chunks ([#528], ([#543]))
* Add support for parsing mDCV and cLLI chunks ([#528], ([#543]))
### Changed

@ -0,0 +1,966 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "bumpalo"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "ciborium"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
[[package]]
name = "ciborium-ll"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
dependencies = [
"ciborium-io",
"half",
]
[[package]]
name = "clap"
version = "3.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
dependencies = [
"atty",
"bitflags 1.3.2",
"clap_derive",
"clap_lex",
"indexmap",
"once_cell",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_derive"
version = "3.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "criterion"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb"
dependencies = [
"anes",
"atty",
"cast",
"ciborium",
"clap",
"criterion-plot",
"itertools",
"lazy_static",
"num-traits",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
[[package]]
name = "either"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d"
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]]
name = "flate2"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
dependencies = [
"crc32fast",
"libz-rs-sys",
"miniz_oxide",
]
[[package]]
name = "getopts"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
dependencies = [
"unicode-width",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "glob"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "half"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
dependencies = [
"cfg-if",
"crunchy",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "home"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.170"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
[[package]]
name = "libz-rs-sys"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "902bc563b5d65ad9bba616b490842ef0651066a1a1dc3ce1087113ffcb873c8d"
dependencies = [
"zlib-rs",
]
[[package]]
name = "log"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "oorandom"
version = "11.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
[[package]]
name = "os_str_bytes"
version = "6.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
[[package]]
name = "plotters"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
[[package]]
name = "plotters-svg"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
dependencies = [
"plotters-backend",
]
[[package]]
name = "png"
version = "0.18.0-rc"
dependencies = [
"approx",
"bitflags 2.9.0",
"byteorder",
"clap",
"crc32fast",
"criterion",
"fdeflate",
"flate2",
"getopts",
"glob",
"miniz_oxide",
"rand",
"term",
]
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustversion"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
[[package]]
name = "ryu"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "serde"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "serde_json"
version = "1.0.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "term"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3bb6001afcea98122260987f8b7b5da969ecad46dbf0b5453702f776b491a41"
dependencies = [
"home",
"windows-sys 0.52.0",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "unicode-ident"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn 2.0.98",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "zlib-rs"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b20717f0917c908dc63de2e44e97f1e6b126ca58d0e391cee86d504eb8fbd05"

@ -10,10 +10,10 @@
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
rust-version = "1.57"
edition = "2021"
rust-version = "1.65"
name = "png"
version = "0.17.16"
version = "0.18.0-rc"
authors = ["The image-rs Developers"]
build = false
include = [
@ -56,10 +56,6 @@ path = "examples/png-generate.rs"
name = "pngcheck"
path = "examples/pngcheck.rs"
[[example]]
name = "show"
path = "examples/show.rs"
[[bench]]
name = "decoder"
path = "benches/decoder.rs"
@ -78,7 +74,7 @@ harness = false
required-features = ["benchmarks"]
[dependencies.bitflags]
version = "1.0"
version = "2.0"
[dependencies.crc32fast]
version = "1.2.0"
@ -87,7 +83,7 @@ version = "1.2.0"
version = "0.3.3"
[dependencies.flate2]
version = "1.0.11"
version = "1.0.35"
[dependencies.miniz_oxide]
version = "0.8"
@ -109,11 +105,6 @@ version = "0.4.0"
[dev-dependencies.getopts]
version = "0.2.14"
[dev-dependencies.glium]
version = "0.32"
features = ["glutin"]
default-features = false
[dev-dependencies.glob]
version = "0.3"
@ -126,6 +117,7 @@ version = "1.0.1"
[features]
benchmarks = []
unstable = ["crc32fast/nightly"]
zlib-rs = ["flate2/zlib-rs"]
[lints.rust.unexpected_cfgs]
level = "warn"

@ -1,6 +1,6 @@
[package]
name = "png"
version = "0.17.16"
version = "0.18.0-rc"
license = "MIT OR Apache-2.0"
description = "PNG decoding and encoding library in pure Rust"
@ -8,8 +8,8 @@ categories = ["multimedia::images"]
authors = ["The image-rs Developers"]
repository = "https://github.com/image-rs/image-png"
edition = "2018"
rust-version = "1.57"
edition = "2021"
rust-version = "1.65"
include = [
"/LICENSE-MIT",
"/LICENSE-APACHE",
@ -21,10 +21,10 @@ include = [
]
[dependencies]
bitflags = "1.0"
bitflags = "2.0"
crc32fast = "1.2.0"
fdeflate = "0.3.3"
flate2 = "1.0.11"
flate2 = "1.0.35"
miniz_oxide = { version = "0.8", features = ["simd"] }
[dev-dependencies]
@ -33,13 +33,19 @@ byteorder = "1.5.0"
clap = { version = "3.0", features = ["derive"] }
criterion = "0.4.0"
getopts = "0.2.14"
glium = { version = "0.32", features = ["glutin"], default-features = false }
glob = "0.3"
rand = "0.8.4"
term = "1.0.1"
[features]
# Use nightly-only features for a minor performance boost in PNG decoding
unstable = ["crc32fast/nightly"]
# Use zlib-rs for faster PNG encoding at the cost of some `unsafe` code.
# WARNING: this changes the flate2 backend for your entire dependency tree!
# While the `png` crate always uses fully memory-safe decoding,
# this enables zlib-rs and introduces some unsafe code to all other crates
# that rely on flate2, including the decoding codepaths.
zlib-rs = ["flate2/zlib-rs"]
benchmarks = []
[lints.rust]

@ -1,4 +1,4 @@
use std::fs;
use std::{fs, io::Cursor};
use criterion::{
criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion, Throughput,
@ -41,6 +41,13 @@ fn load_all(c: &mut Criterion) {
bench_noncompressed_png(&mut g, 2048, 0x7fffffff); // 16 MB
bench_noncompressed_png(&mut g, 12288, 0x7fffffff); // 576 MB
g.finish();
// Incremental decoding via `read_row`
let mut g = c.benchmark_group("row-by-row");
let mut data = Vec::new();
test_utils::write_noncompressed_png(&mut data, 128, 4096);
bench_read_row(&mut g, data, "128x128-4k-idat");
g.finish();
}
criterion_group! {benches, load_all}
@ -52,21 +59,12 @@ fn bench_noncompressed_png(g: &mut BenchmarkGroup<WallTime>, size: u32, idat_byt
bench_file(g, data, format!("{size}x{size}.png"));
}
/// This benchmarks decoding via a call to `Reader::next_frame`.
fn bench_file(g: &mut BenchmarkGroup<WallTime>, data: Vec<u8>, name: String) {
if data.len() > 1_000_000 {
g.sample_size(10);
}
fn create_reader(data: &[u8]) -> Reader<&[u8]> {
let mut decoder = Decoder::new(data);
// Cover default transformations used by the `image` crate when constructing
// `image::codecs::png::PngDecoder`.
decoder.set_transformations(Transformations::EXPAND);
decoder.read_info().unwrap()
}
let mut reader = create_reader(data.as_slice());
let mut image = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut image).unwrap();
@ -79,3 +77,30 @@ fn bench_file(g: &mut BenchmarkGroup<WallTime>, data: Vec<u8>, name: String) {
})
});
}
/// This benchmarks decoding via a sequence of `Reader::read_row` calls.
fn bench_read_row(g: &mut BenchmarkGroup<WallTime>, data: Vec<u8>, name: &str) {
let reader = create_reader(data.as_slice());
let mut image = vec![0; reader.output_buffer_size()];
let bytes_per_row = reader.output_line_size(reader.info().width);
g.throughput(Throughput::Bytes(image.len() as u64));
g.bench_with_input(name, &data, |b, data| {
b.iter(|| {
let mut reader = create_reader(data.as_slice());
for output_row in image.chunks_exact_mut(bytes_per_row) {
reader.read_row(output_row).unwrap().unwrap();
}
})
});
}
fn create_reader(data: &[u8]) -> Reader<Cursor<&[u8]>> {
let mut decoder = Decoder::new(Cursor::new(data));
// Cover default transformations used by the `image` crate when constructing
// `image::codecs::png::PngDecoder`.
decoder.set_transformations(Transformations::EXPAND);
decoder.read_info().unwrap()
}

@ -9,17 +9,12 @@
use criterion::{criterion_group, criterion_main, Criterion, Throughput};
use png::benchable_apis::unfilter;
use png::FilterType;
use png::Filter;
use rand::Rng;
fn unfilter_all(c: &mut Criterion) {
let bpps = [1, 2, 3, 4, 6, 8];
let filters = [
FilterType::Sub,
FilterType::Up,
FilterType::Avg,
FilterType::Paeth,
];
let filters = [Filter::Sub, Filter::Up, Filter::Avg, Filter::Paeth];
for &filter in filters.iter() {
for &bpp in bpps.iter() {
bench_unfilter(c, filter, bpp);
@ -30,7 +25,7 @@ fn unfilter_all(c: &mut Criterion) {
criterion_group!(benches, unfilter_all);
criterion_main!(benches);
fn bench_unfilter(c: &mut Criterion, filter: FilterType, bpp: u8) {
fn bench_unfilter(c: &mut Criterion, filter: Filter, bpp: u8) {
let mut group = c.benchmark_group("unfilter");
fn get_random_bytes<R: Rng>(rng: &mut R, n: usize) -> Vec<u8> {

@ -1,7 +1,7 @@
/// Tests "editing"/re-encoding of an image:
/// decoding, editing, re-encoding
use std::fs::File;
use std::io::BufWriter;
use std::io::{BufReader, BufWriter};
use std::path::Path;
pub type BoxResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
@ -11,7 +11,7 @@ fn main() -> BoxResult<()> {
let path_in = Path::new(r"./tests/pngsuite/basi0g01.png");
// The decoder is a build for reader and can be used to set various decoding options
// via `Transformations`. The default output transformation is `Transformations::IDENTITY`.
let decoder = png::Decoder::new(File::open(path_in)?);
let decoder = png::Decoder::new(BufReader::new(File::open(path_in)?));
let mut reader = decoder.read_info()?;
// Allocate the output buffer.
let png_info = reader.info();

@ -1,4 +1,4 @@
use std::{fs, path::PathBuf};
use std::{fs, io::Cursor, path::PathBuf};
use clap::Parser;
use png::Decoder;
@ -43,20 +43,16 @@ fn run_encode(
encoder.set_depth(bit_depth);
encoder.set_compression(match args.speed {
Speed::Fast => png::Compression::Fast,
Speed::Default => png::Compression::Default,
Speed::Best => png::Compression::Best,
Speed::Default => png::Compression::Balanced,
Speed::Best => png::Compression::High,
});
encoder.set_filter(match args.filter {
Filter::None => png::FilterType::NoFilter,
Filter::Sub => png::FilterType::Sub,
Filter::Up => png::FilterType::Up,
Filter::Average => png::FilterType::Avg,
Filter::Paeth => png::FilterType::Paeth,
Filter::Adaptive => png::FilterType::Paeth,
});
encoder.set_adaptive_filter(match args.filter {
Filter::Adaptive => png::AdaptiveFilterType::Adaptive,
_ => png::AdaptiveFilterType::NonAdaptive,
Filter::None => png::Filter::NoFilter,
Filter::Sub => png::Filter::Sub,
Filter::Up => png::Filter::Up,
Filter::Average => png::Filter::Avg,
Filter::Paeth => png::Filter::Paeth,
Filter::Adaptive => png::Filter::Adaptive,
});
let mut encoder = encoder.write_header().unwrap();
encoder.write_image_data(image).unwrap();
@ -66,7 +62,7 @@ fn run_encode(
#[inline(never)]
fn run_decode(image: &[u8], output: &mut [u8]) {
let mut reader = Decoder::new(image).read_info().unwrap();
let mut reader = Decoder::new(Cursor::new(image)).read_info().unwrap();
reader.next_frame(output).unwrap();
}
@ -111,7 +107,7 @@ fn main() {
// Parse
let data = fs::read(entry.path()).unwrap();
let mut decoder = Decoder::new(&*data);
let mut decoder = Decoder::new(Cursor::new(&*data));
if decoder.read_header_info().ok().map(|h| h.color_type)
== Some(png::ColorType::Indexed)
{

@ -2,12 +2,13 @@
//! This module is gated behind the "benchmarks" feature.
use crate::common::BytesPerPixel;
use crate::filter::FilterType;
use crate::filter::{Filter, RowFilter};
use crate::{BitDepth, ColorType, Info};
/// Re-exporting `unfilter` to make it easier to benchmark, despite some items being only
/// `pub(crate)`: `fn unfilter`, `enum BytesPerPixel`.
pub fn unfilter(filter: FilterType, tbpp: u8, previous: &[u8], current: &mut [u8]) {
pub fn unfilter(filter: Filter, tbpp: u8, previous: &[u8], current: &mut [u8]) {
let filter = RowFilter::from_method(filter).unwrap(); // RowFilter type is private
let tbpp = BytesPerPixel::from_usize(tbpp as usize);
crate::filter::unfilter(filter, tbpp, previous, current)
}

@ -1,5 +1,7 @@
//! Common types shared between the encoder and decoder
use crate::text_metadata::{EncodableTextChunk, ITXtChunk, TEXtChunk, ZTXtChunk};
use crate::text_metadata::{ITXtChunk, TEXtChunk, ZTXtChunk};
#[allow(unused_imports)] // used by doc comments only
use crate::Filter;
use crate::{chunk, encoder};
use io::Write;
use std::{borrow::Cow, convert::TryFrom, fmt, io};
@ -77,6 +79,16 @@ impl ColorType {
|| self == ColorType::Rgba))
|| (bit_depth == BitDepth::Sixteen && self == ColorType::Indexed)
}
pub(crate) fn bits_per_pixel(&self, bit_depth: BitDepth) -> usize {
self.samples() * bit_depth as usize
}
pub(crate) fn bytes_per_pixel(&self, bit_depth: BitDepth) -> usize {
// If adjusting this for expansion or other transformation passes, remember to keep the old
// implementation for bpp_in_prediction, which is internal to the png specification.
self.samples() * ((bit_depth as usize + 7) >> 3)
}
}
/// Bit depth of the PNG file.
@ -303,33 +315,104 @@ impl AnimationControl {
}
/// The type and strength of applied compression.
///
/// This is a simple, high-level interface that will automatically choose
/// the appropriate DEFLATE compression mode and PNG filter.
///
/// If you need more control over the encoding parameters,
/// you can set the [DeflateCompression] and [Filter] manually.
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum Compression {
/// Default level
Default,
/// Fast minimal compression
Fast,
/// Higher compression level
/// No compression whatsoever. Fastest, but results in large files.
NoCompression,
/// Extremely fast but light compression.
Fastest,
/// Extremely fast compression with a decent compression ratio.
///
/// Best in this context isn't actually the highest possible level
/// the encoder can do, but is meant to emulate the `Best` setting in the `Flate2`
/// library.
Best,
#[deprecated(
since = "0.17.6",
note = "use one of the other compression levels instead, such as 'fast'"
)]
Huffman,
#[deprecated(
since = "0.17.6",
note = "use one of the other compression levels instead, such as 'fast'"
)]
Rle,
/// Significantly outperforms libpng and other popular encoders
/// by using a [specialized DEFLATE implementation tuned for PNG](https://crates.io/crates/fdeflate),
/// while still providing better compression ratio than the fastest modes of other encoders.
Fast,
/// Balances encoding speed and compression ratio
Balanced,
/// Spend more time to produce a slightly smaller file than with `Default`
High,
}
impl Default for Compression {
fn default() -> Self {
Self::Default
Self::Balanced
}
}
/// Advanced compression settings with more customization options than [Compression].
///
/// Note that this setting only affects DEFLATE compression.
/// Another setting that influences the compression ratio and lets you choose
/// between encoding speed and compression ratio is the [Filter].
///
/// ### Stability guarantees
///
/// The implementation details of DEFLATE compression may evolve over time,
/// even without a semver-breaking change to the version of `png` crate.
///
/// If a certain compression setting is superseded by other options,
/// it may be marked deprecated and remapped to a different option.
/// You will see a deprecation notice when compiling code relying on such options.
#[non_exhaustive]
#[derive(Debug, Clone, Copy)]
pub enum DeflateCompression {
/// Do not compress the data at all.
///
/// Useful for incompressible images,
/// or when speed is paramount and you don't care about size at all.
///
/// This mode also disables filters, forcing [Filter::NoFilter].
NoCompression,
/// Excellent for creating lightly compressed PNG images very quickly.
///
/// Uses the [fdeflate](https://crates.io/crates/fdeflate) crate under the hood
/// to achieve speeds far exceeding what libpng is capable of
/// while still providing a decent compression ratio.
FdeflateUltraFast,
/// Compression level between 1 and 9, where higher values mean better compression at the cost of
/// speed.
///
/// This is currently implemented via [flate2](https://crates.io/crates/flate2) crate
/// by passing through the [compression level](flate2::Compression::new).
///
/// The implementation details and the exact meaning of each level may change in the future,
/// including in semver-compatible releases.
Level(u8),
// Other variants can be added in the future
}
impl Default for DeflateCompression {
fn default() -> Self {
Self::from_simple(Compression::Balanced)
}
}
impl DeflateCompression {
pub(crate) fn from_simple(value: Compression) -> Self {
match value {
Compression::NoCompression => Self::NoCompression,
Compression::Fastest => Self::FdeflateUltraFast,
Compression::Fast => Self::FdeflateUltraFast,
Compression::Balanced => Self::Level(flate2::Compression::default().level() as u8),
Compression::High => Self::Level(flate2::Compression::best().level() as u8),
}
}
pub(crate) fn closest_flate2_level(&self) -> flate2::Compression {
match self {
DeflateCompression::NoCompression => flate2::Compression::none(),
DeflateCompression::FdeflateUltraFast => flate2::Compression::new(1),
DeflateCompression::Level(level) => flate2::Compression::new(u32::from(*level)),
}
}
}
@ -587,7 +670,6 @@ pub struct Info<'a> {
pub frame_control: Option<FrameControl>,
pub animation_control: Option<AnimationControl>,
pub compression: Compression,
/// Gamma of the source system.
/// Set by both `gAMA` as well as to a replacement by `sRGB` chunk.
pub source_gamma: Option<ScaledFloat>,
@ -633,9 +715,6 @@ impl Default for Info<'_> {
pixel_dims: None,
frame_control: None,
animation_control: None,
// Default to `deflate::Compression::Fast` and `filter::FilterType::Sub`
// to maintain backward compatible output.
compression: Compression::Fast,
source_gamma: None,
source_chromaticities: None,
srgb: None,
@ -683,14 +762,14 @@ impl Info<'_> {
/// Returns the number of bits per pixel.
pub fn bits_per_pixel(&self) -> usize {
self.color_type.samples() * self.bit_depth as usize
self.color_type.bits_per_pixel(self.bit_depth)
}
/// Returns the number of bytes per pixel.
pub fn bytes_per_pixel(&self) -> usize {
// If adjusting this for expansion or other transformation passes, remember to keep the old
// implementation for bpp_in_prediction, which is internal to the png specification.
self.color_type.samples() * ((self.bit_depth as usize + 7) >> 3)
self.color_type.bytes_per_pixel(self.bit_depth)
}
/// Return the number of bytes for this pixel used in prediction.
@ -725,6 +804,24 @@ impl Info<'_> {
.raw_row_length_from_width(self.bit_depth, width)
}
/// Gamma dependent on sRGB chunk
pub fn gamma(&self) -> Option<ScaledFloat> {
if self.srgb.is_some() {
Some(crate::srgb::substitute_gamma())
} else {
self.gama_chunk
}
}
/// Chromaticities dependent on sRGB chunk
pub fn chromaticities(&self) -> Option<SourceChromaticities> {
if self.srgb.is_some() {
Some(crate::srgb::substitute_chromaticities())
} else {
self.chrm_chunk
}
}
/// Mark the image data as conforming to the SRGB color space with the specified rendering intent.
///
/// Any ICC profiles will be ignored.
@ -735,91 +832,6 @@ impl Info<'_> {
self.srgb = Some(rendering_intent);
self.icc_profile = None;
}
/// Encode this header to the writer.
///
/// Note that this does _not_ include the PNG signature, it starts with the IHDR chunk and then
/// includes other chunks that were added to the header.
#[deprecated(note = "Use Encoder+Writer instead")]
pub fn encode<W: Write>(&self, mut w: W) -> encoder::Result<()> {
// Encode the IHDR chunk
let mut data = [0; 13];
data[..4].copy_from_slice(&self.width.to_be_bytes());
data[4..8].copy_from_slice(&self.height.to_be_bytes());
data[8] = self.bit_depth as u8;
data[9] = self.color_type as u8;
data[12] = self.interlaced as u8;
encoder::write_chunk(&mut w, chunk::IHDR, &data)?;
// Encode the pHYs chunk
if let Some(pd) = self.pixel_dims {
let mut phys_data = [0; 9];
phys_data[0..4].copy_from_slice(&pd.xppu.to_be_bytes());
phys_data[4..8].copy_from_slice(&pd.yppu.to_be_bytes());
match pd.unit {
Unit::Meter => phys_data[8] = 1,
Unit::Unspecified => phys_data[8] = 0,
}
encoder::write_chunk(&mut w, chunk::pHYs, &phys_data)?;
}
// If specified, the sRGB information overrides the source gamma and chromaticities.
if let Some(srgb) = &self.srgb {
srgb.encode(&mut w)?;
// gAMA and cHRM are optional, for backwards compatibility
let srgb_gamma = crate::srgb::substitute_gamma();
if Some(srgb_gamma) == self.source_gamma {
srgb_gamma.encode_gama(&mut w)?
}
let srgb_chromaticities = crate::srgb::substitute_chromaticities();
if Some(srgb_chromaticities) == self.source_chromaticities {
srgb_chromaticities.encode(&mut w)?;
}
} else {
if let Some(gma) = self.source_gamma {
gma.encode_gama(&mut w)?
}
if let Some(chrms) = self.source_chromaticities {
chrms.encode(&mut w)?;
}
if let Some(iccp) = &self.icc_profile {
encoder::write_iccp_chunk(&mut w, "_", iccp)?
}
}
if let Some(exif) = &self.exif_metadata {
encoder::write_chunk(&mut w, chunk::eXIf, exif)?;
}
if let Some(actl) = self.animation_control {
actl.encode(&mut w)?;
}
// The position of the PLTE chunk is important, it must come before the tRNS chunk and after
// many of the other metadata chunks.
if let Some(p) = &self.palette {
encoder::write_chunk(&mut w, chunk::PLTE, p)?;
};
if let Some(t) = &self.trns {
encoder::write_chunk(&mut w, chunk::tRNS, t)?;
}
for text_chunk in &self.uncompressed_latin1_text {
text_chunk.encode(&mut w)?;
}
for text_chunk in &self.compressed_latin1_text {
text_chunk.encode(&mut w)?;
}
for text_chunk in &self.utf8_text {
text_chunk.encode(&mut w)?;
}
Ok(())
}
}
impl BytesPerPixel {
@ -876,6 +888,7 @@ bitflags::bitflags! {
const SCALE_16 = 0x8000; // read only
```
"]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Transformations: u32 {
/// No transformation
const IDENTITY = 0x00000; // read and write */

@ -10,7 +10,7 @@ use self::stream::{DecodeOptions, DecodingError, FormatErrorInner, CHUNK_BUFFER_
use self::transform::{create_transform_fn, TransformFn};
use self::unfiltering_buffer::UnfilteringBuffer;
use std::io::Read;
use std::io::{BufRead, Seek};
use std::mem;
use crate::adam7::{self, Adam7Info};
@ -86,7 +86,7 @@ impl Default for Limits {
}
/// PNG Decoder
pub struct Decoder<R: Read> {
pub struct Decoder<R: BufRead + Seek> {
read_decoder: ReadDecoder<R>,
/// Output transformations
transform: Transformations,
@ -121,7 +121,7 @@ impl<'data> Row<'data> {
}
}
impl<R: Read> Decoder<R> {
impl<R: BufRead + Seek> Decoder<R> {
/// Create a new decoder configuration with default limits.
pub fn new(r: R) -> Decoder<R> {
Decoder::new_with_limits(r, Limits::default())
@ -159,17 +159,18 @@ impl<R: Read> Decoder<R> {
///
/// ```
/// use std::fs::File;
/// use std::io::BufReader;
/// use png::{Decoder, Limits};
/// // This image is 32×32, 1bit per pixel. The reader buffers one row which requires 4 bytes.
/// let mut limits = Limits::default();
/// limits.bytes = 3;
/// let mut decoder = Decoder::new_with_limits(File::open("tests/pngsuite/basi0g01.png").unwrap(), limits);
/// let mut decoder = Decoder::new_with_limits(BufReader::new(File::open("tests/pngsuite/basi0g01.png").unwrap()), limits);
/// assert!(decoder.read_info().is_err());
///
/// // This image is 32x32 pixels, so the decoder will allocate less than 10Kib
/// let mut limits = Limits::default();
/// limits.bytes = 10*1024;
/// let mut decoder = Decoder::new_with_limits(File::open("tests/pngsuite/basi0g01.png").unwrap(), limits);
/// let mut decoder = Decoder::new_with_limits(BufReader::new(File::open("tests/pngsuite/basi0g01.png").unwrap()), limits);
/// assert!(decoder.read_info().is_ok());
/// ```
pub fn set_limits(&mut self, limits: Limits) {
@ -248,8 +249,9 @@ impl<R: Read> Decoder<R> {
/// eg.
/// ```
/// use std::fs::File;
/// use std::io::BufReader;
/// use png::Decoder;
/// let mut decoder = Decoder::new(File::open("tests/pngsuite/basi0g01.png").unwrap());
/// let mut decoder = Decoder::new(BufReader::new(File::open("tests/pngsuite/basi0g01.png").unwrap()));
/// decoder.set_ignore_text_chunk(true);
/// assert!(decoder.read_info().is_ok());
/// ```
@ -262,8 +264,9 @@ impl<R: Read> Decoder<R> {
/// eg.
/// ```
/// use std::fs::File;
/// use std::io::BufReader;
/// use png::Decoder;
/// let mut decoder = Decoder::new(File::open("tests/iccp/broken_iccp.png").unwrap());
/// let mut decoder = Decoder::new(BufReader::new(File::open("tests/iccp/broken_iccp.png").unwrap()));
/// decoder.set_ignore_iccp_chunk(true);
/// assert!(decoder.read_info().is_ok());
/// ```
@ -281,7 +284,7 @@ impl<R: Read> Decoder<R> {
/// PNG reader (mostly high-level interface)
///
/// Provides a high level that iterates over lines or whole images.
pub struct Reader<R: Read> {
pub struct Reader<R: BufRead + Seek> {
decoder: ReadDecoder<R>,
bpp: BytesPerPixel,
subframe: SubframeInfo,
@ -317,7 +320,7 @@ struct SubframeInfo {
consumed_and_flushed: bool,
}
impl<R: Read> Reader<R> {
impl<R: BufRead + Seek> Reader<R> {
/// Advances to the start of the next animation frame and
/// returns a reference to the `FrameControl` info that describes it.
/// Skips and discards the image data of the previous frame if necessary.
@ -474,14 +477,45 @@ impl<R: Read> Reader<R> {
Ok(())
}
/// Returns the next processed row of the image
/// Returns the next processed row of the image (discarding `InterlaceInfo`).
///
/// See also [`Reader.read_row`], which reads into a caller-provided buffer.
pub fn next_row(&mut self) -> Result<Option<Row>, DecodingError> {
self.next_interlaced_row()
.map(|v| v.map(|v| Row { data: v.data }))
}
/// Returns the next processed row of the image
/// Returns the next processed row of the image.
///
/// See also [`Reader.read_row`], which reads into a caller-provided buffer.
pub fn next_interlaced_row(&mut self) -> Result<Option<InterlacedRow>, DecodingError> {
let mut output_buffer = mem::take(&mut self.scratch_buffer);
output_buffer.resize(self.output_line_size(self.info().width), 0u8);
let result = self.read_row(&mut output_buffer);
self.scratch_buffer = output_buffer;
result.map(move |option| {
option.map(move |interlace| {
let output_line_size = self.output_line_size_for_interlace_info(&interlace);
InterlacedRow {
data: &self.scratch_buffer[..output_line_size],
interlace,
}
})
})
}
/// Reads the next row of the image into the provided `output_buffer`.
/// `Ok(None)` will be returned if the current image frame has no more rows.
///
/// `output_buffer` needs to be long enough to accommodate [`Reader.output_line_size`] for
/// [`Info.width`] (initial interlaced rows may need less than that).
///
/// See also [`Reader.next_row`] and [`Reader.next_interlaced_row`], which read into a
/// `Reader`-owned buffer.
pub fn read_row(
&mut self,
output_buffer: &mut [u8],
) -> Result<Option<InterlaceInfo>, DecodingError> {
let interlace = match self.subframe.current_interlace_info.as_ref() {
None => {
self.finish_decoding()?;
@ -498,24 +532,21 @@ impl<R: Read> Reader<R> {
self.info().raw_row_length_from_width(width)
}
};
let output_line_size = self.output_line_size_for_interlace_info(&interlace);
let output_buffer = &mut output_buffer[..output_line_size];
self.next_interlaced_row_impl(rowlen, output_buffer)?;
Ok(Some(interlace))
}
fn output_line_size_for_interlace_info(&self, interlace: &InterlaceInfo) -> usize {
let width = match interlace {
InterlaceInfo::Adam7(Adam7Info { width, .. }) => width,
InterlaceInfo::Adam7(Adam7Info { width, .. }) => *width,
InterlaceInfo::Null(_) => self.subframe.width,
};
let output_line_size = self.output_line_size(width);
// TODO: change the interface of `next_interlaced_row` to take an output buffer instead of
// making us return a reference to a buffer that we own.
let mut output_buffer = mem::take(&mut self.scratch_buffer);
output_buffer.resize(output_line_size, 0u8);
let ret = self.next_interlaced_row_impl(rowlen, &mut output_buffer);
self.scratch_buffer = output_buffer;
ret?;
Ok(Some(InterlacedRow {
data: &self.scratch_buffer[..output_line_size],
interlace,
}))
self.output_line_size(width)
}
/// Read the rest of the image and chunks and finish up, including text chunks or others

@ -1,9 +1,7 @@
use super::stream::{
DecodeOptions, Decoded, DecodingError, FormatErrorInner, StreamingDecoder, CHUNK_BUFFER_SIZE,
};
use super::stream::{DecodeOptions, Decoded, DecodingError, FormatErrorInner, StreamingDecoder};
use super::Limits;
use std::io::{BufRead, BufReader, ErrorKind, Read};
use std::io::{BufRead, ErrorKind, Read, Seek};
use crate::chunk;
use crate::common::Info;
@ -18,14 +16,14 @@ use crate::common::Info;
/// * `finish_decoding_image_data()` - discarding remaining data from `IDAT` / `fdAT` sequence
/// * `read_until_end_of_input()` - reading until `IEND` chunk
pub(crate) struct ReadDecoder<R: Read> {
reader: BufReader<R>,
reader: R,
decoder: StreamingDecoder,
}
impl<R: Read> ReadDecoder<R> {
impl<R: BufRead + Seek> ReadDecoder<R> {
pub fn new(r: R) -> Self {
Self {
reader: BufReader::with_capacity(CHUNK_BUFFER_SIZE, r),
reader: r,
decoder: StreamingDecoder::new(),
}
}
@ -34,10 +32,7 @@ impl<R: Read> ReadDecoder<R> {
let mut decoder = StreamingDecoder::new_with_options(options);
decoder.limits = Limits::default();
Self {
reader: BufReader::with_capacity(CHUNK_BUFFER_SIZE, r),
decoder,
}
Self { reader: r, decoder }
}
pub fn set_limits(&mut self, limits: Limits) {

@ -24,6 +24,7 @@ pub const CHUNK_BUFFER_SIZE: usize = 32 * 1024;
///
/// This is used only in fuzzing. `afl` automatically adds `--cfg fuzzing` to RUSTFLAGS which can
/// be used to detect that build.
#[allow(unexpected_cfgs)]
const CHECKSUM_DISABLED: bool = cfg!(fuzzing);
/// Kind of `u32` value that is being read via `State::U32`.
@ -1141,74 +1142,69 @@ impl StreamingDecoder {
}
fn parse_sbit(&mut self) -> Result<Decoded, DecodingError> {
let mut parse = || {
let info = self.info.as_mut().unwrap();
if info.palette.is_some() {
let info = self.info.as_mut().unwrap();
if info.palette.is_some() {
return Err(DecodingError::Format(
FormatErrorInner::AfterPlte { kind: chunk::sBIT }.into(),
));
}
if self.have_idat {
return Err(DecodingError::Format(
FormatErrorInner::AfterIdat { kind: chunk::sBIT }.into(),
));
}
if info.sbit.is_some() {
return Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: chunk::sBIT }.into(),
));
}
let (color_type, bit_depth) = { (info.color_type, info.bit_depth) };
// The sample depth for color type 3 is fixed at eight bits.
let sample_depth = if color_type == ColorType::Indexed {
BitDepth::Eight
} else {
bit_depth
};
self.limits
.reserve_bytes(self.current_chunk.raw_bytes.len())?;
let vec = self.current_chunk.raw_bytes.clone();
let len = vec.len();
// expected lenth of the chunk
let expected = match color_type {
ColorType::Grayscale => 1,
ColorType::Rgb | ColorType::Indexed => 3,
ColorType::GrayscaleAlpha => 2,
ColorType::Rgba => 4,
};
// Check if the sbit chunk size is valid.
if expected != len {
return Err(DecodingError::Format(
FormatErrorInner::InvalidSbitChunkSize {
color_type,
expected,
len,
}
.into(),
));
}
for sbit in &vec {
if *sbit < 1 || *sbit > sample_depth as u8 {
return Err(DecodingError::Format(
FormatErrorInner::AfterPlte { kind: chunk::sBIT }.into(),
));
}
if self.have_idat {
return Err(DecodingError::Format(
FormatErrorInner::AfterIdat { kind: chunk::sBIT }.into(),
));
}
if info.sbit.is_some() {
return Err(DecodingError::Format(
FormatErrorInner::DuplicateChunk { kind: chunk::sBIT }.into(),
));
}
let (color_type, bit_depth) = { (info.color_type, info.bit_depth) };
// The sample depth for color type 3 is fixed at eight bits.
let sample_depth = if color_type == ColorType::Indexed {
BitDepth::Eight
} else {
bit_depth
};
self.limits
.reserve_bytes(self.current_chunk.raw_bytes.len())?;
let vec = self.current_chunk.raw_bytes.clone();
let len = vec.len();
// expected lenth of the chunk
let expected = match color_type {
ColorType::Grayscale => 1,
ColorType::Rgb | ColorType::Indexed => 3,
ColorType::GrayscaleAlpha => 2,
ColorType::Rgba => 4,
};
// Check if the sbit chunk size is valid.
if expected != len {
return Err(DecodingError::Format(
FormatErrorInner::InvalidSbitChunkSize {
color_type,
expected,
len,
FormatErrorInner::InvalidSbit {
sample_depth,
sbit: *sbit,
}
.into(),
));
}
for sbit in &vec {
if *sbit < 1 || *sbit > sample_depth as u8 {
return Err(DecodingError::Format(
FormatErrorInner::InvalidSbit {
sample_depth,
sbit: *sbit,
}
.into(),
));
}
}
info.sbit = Some(Cow::Owned(vec));
Ok(Decoded::Nothing)
};
parse().ok();
}
info.sbit = Some(Cow::Owned(vec));
Ok(Decoded::Nothing)
}
@ -1345,11 +1341,6 @@ impl StreamingDecoder {
};
info.chrm_chunk = Some(source_chromaticities);
// Ignore chromaticities if sRGB profile is used.
if info.srgb.is_none() {
info.source_chromaticities = Some(source_chromaticities);
}
Ok(Decoded::Nothing)
}
}
@ -1370,11 +1361,6 @@ impl StreamingDecoder {
let source_gamma = ScaledFloat::from_scaled(source_gamma);
info.gama_chunk = Some(source_gamma);
// Ignore chromaticities if sRGB profile is used.
if info.srgb.is_none() {
info.source_gamma = Some(source_gamma);
}
Ok(Decoded::Nothing)
}
}
@ -1398,8 +1384,6 @@ impl StreamingDecoder {
// Set srgb and override source gamma and chromaticities.
info.srgb = Some(rendering_intent);
info.source_gamma = Some(crate::srgb::substitute_gamma());
info.source_chromaticities = Some(crate::srgb::substitute_chromaticities());
Ok(Decoded::Nothing)
}
}
@ -1871,22 +1855,25 @@ mod tests {
use super::ScaledFloat;
use super::SourceChromaticities;
use crate::test_utils::*;
use crate::{Decoder, DecodingError, Reader};
use crate::{Decoder, DecodingError, Reader, SrgbRenderingIntent, Unit};
use approx::assert_relative_eq;
use byteorder::WriteBytesExt;
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::fs::File;
use std::io::{ErrorKind, Read, Write};
use std::io::BufRead;
use std::io::Cursor;
use std::io::Seek;
use std::io::{BufReader, ErrorKind, Read, Write};
use std::rc::Rc;
#[test]
fn image_gamma() -> Result<(), ()> {
fn trial(path: &str, expected: Option<ScaledFloat>) {
let decoder = crate::Decoder::new(File::open(path).unwrap());
let decoder = crate::Decoder::new(BufReader::new(File::open(path).unwrap()));
let reader = decoder.read_info().unwrap();
let actual: Option<ScaledFloat> = reader.info().source_gamma;
let actual: Option<ScaledFloat> = reader.info().gamma();
assert!(actual == expected);
}
trial("tests/pngsuite/f00n0g08.png", None);
@ -1925,9 +1912,9 @@ mod tests {
#[test]
fn image_source_chromaticities() -> Result<(), ()> {
fn trial(path: &str, expected: Option<SourceChromaticities>) {
let decoder = crate::Decoder::new(File::open(path).unwrap());
let decoder = crate::Decoder::new(BufReader::new(File::open(path).unwrap()));
let reader = decoder.read_info().unwrap();
let actual: Option<SourceChromaticities> = reader.info().source_chromaticities;
let actual: Option<SourceChromaticities> = reader.info().chromaticities();
assert!(actual == expected);
}
trial(
@ -2112,7 +2099,7 @@ mod tests {
#[test]
fn image_source_sbit() {
fn trial(path: &str, expected: Option<Cow<[u8]>>) {
let decoder = crate::Decoder::new(File::open(path).unwrap());
let decoder = crate::Decoder::new(BufReader::new(File::open(path).unwrap()));
let reader = decoder.read_info().unwrap();
let actual: Option<Cow<[u8]>> = reader.info().sbit.clone();
assert!(actual == expected);
@ -2139,7 +2126,9 @@ mod tests {
// https://github.com/image-rs/image/issues/1825#issuecomment-1321798639,
// but the 2nd iCCP chunk has been altered manually (see the 2nd comment below for more
// details).
let decoder = crate::Decoder::new(File::open("tests/bugfixes/issue#1825.png").unwrap());
let decoder = crate::Decoder::new(BufReader::new(
File::open("tests/bugfixes/issue#1825.png").unwrap(),
));
let reader = decoder.read_info().unwrap();
let icc_profile = reader.info().icc_profile.clone().unwrap().into_owned();
@ -2162,24 +2151,67 @@ mod tests {
enc.write_image_data(&[0]).unwrap();
enc.finish().unwrap();
let dec = crate::Decoder::new(encoded_image.as_slice());
let dec = crate::Decoder::new(Cursor::new(&encoded_image));
let dec = dec.read_info().unwrap();
assert_eq!(dummy_icc, &**dec.info().icc_profile.as_ref().unwrap());
}
#[test]
fn test_phys_roundtrip() {
let mut info = crate::Info::with_size(1, 1);
info.pixel_dims = Some(crate::PixelDimensions {
xppu: 12,
yppu: 34,
unit: Unit::Meter,
});
let mut encoded_image = Vec::new();
let enc = crate::Encoder::with_info(&mut encoded_image, info).unwrap();
let mut enc = enc.write_header().unwrap();
enc.write_image_data(&[0]).unwrap();
enc.finish().unwrap();
let dec = crate::Decoder::new(Cursor::new(&encoded_image));
let dec = dec.read_info().unwrap();
let phys = dec.info().pixel_dims.as_ref().unwrap();
assert_eq!(phys.xppu, 12);
assert_eq!(phys.yppu, 34);
assert_eq!(phys.unit, Unit::Meter);
}
#[test]
fn test_srgb_roundtrip() {
let mut info = crate::Info::with_size(1, 1);
info.srgb = Some(SrgbRenderingIntent::Saturation);
let mut encoded_image = Vec::new();
let enc = crate::Encoder::with_info(&mut encoded_image, info).unwrap();
let mut enc = enc.write_header().unwrap();
enc.write_image_data(&[0]).unwrap();
enc.finish().unwrap();
let dec = crate::Decoder::new(Cursor::new(&encoded_image));
let dec = dec.read_info().unwrap();
assert_eq!(dec.info().srgb.unwrap(), SrgbRenderingIntent::Saturation);
}
#[test]
fn test_png_with_broken_iccp() {
let decoder = crate::Decoder::new(File::open("tests/iccp/broken_iccp.png").unwrap());
let decoder = crate::Decoder::new(BufReader::new(
File::open("tests/iccp/broken_iccp.png").unwrap(),
));
assert!(decoder.read_info().is_ok());
let mut decoder = crate::Decoder::new(File::open("tests/iccp/broken_iccp.png").unwrap());
let mut decoder = crate::Decoder::new(BufReader::new(
File::open("tests/iccp/broken_iccp.png").unwrap(),
));
decoder.set_ignore_iccp_chunk(true);
assert!(decoder.read_info().is_ok());
}
/// Test handling of `mDCV` and `cLLI` chunks.
/// Test handling of `cICP`, `mDCV`, and `cLLI` chunks.
#[test]
fn test_mdcv_and_clli_chunks() {
let decoder = crate::Decoder::new(File::open("tests/bugfixes/cicp_pq.png").unwrap());
fn test_cicp_mdcv_and_clli_chunks() {
let decoder = crate::Decoder::new(BufReader::new(
File::open("tests/bugfixes/cicp_pq.png").unwrap(),
));
let reader = decoder.read_info().unwrap();
let info = reader.info();
@ -2209,8 +2241,9 @@ mod tests {
/// Test handling of `eXIf` chunk.
#[test]
fn test_exif_chunk() {
let decoder =
crate::Decoder::new(File::open("tests/bugfixes/F-exif-chunk-early.png").unwrap());
let decoder = crate::Decoder::new(BufReader::new(
File::open("tests/bugfixes/F-exif-chunk-early.png").unwrap(),
));
let reader = decoder.read_info().unwrap();
let info = reader.info();
let exif = info.exif_metadata.as_ref().unwrap().as_ref();
@ -2222,7 +2255,7 @@ mod tests {
fn test_finishing_twice() {
let mut png = Vec::new();
write_noncompressed_png(&mut png, 16, 1024);
let decoder = Decoder::new(png.as_slice());
let decoder = Decoder::new(Cursor::new(&png));
let mut reader = decoder.read_info().unwrap();
// First call to `finish` - expecting success.
@ -2310,7 +2343,7 @@ mod tests {
write_fdat_prefix(&mut png, 2, 8);
write_chunk(&mut png, b"fdAT", &[]);
let decoder = Decoder::new(png.as_slice());
let decoder = Decoder::new(Cursor::new(&png));
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size()];
reader.next_frame(&mut buf).unwrap();
@ -2338,7 +2371,7 @@ mod tests {
write_fdat_prefix(&mut png, 2, 8);
write_chunk(&mut png, b"fdAT", &[1, 0, 0]);
let decoder = Decoder::new(png.as_slice());
let decoder = Decoder::new(Cursor::new(&png));
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size()];
reader.next_frame(&mut buf).unwrap();
@ -2376,7 +2409,7 @@ mod tests {
};
// Start decoding.
let decoder = Decoder::new(png.as_slice());
let decoder = Decoder::new(Cursor::new(&png));
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size()];
let Some(animation_control) = reader.info().animation_control else {
@ -2439,7 +2472,7 @@ mod tests {
write_iend(&mut png);
png
};
let decoder = Decoder::new(png.as_slice());
let decoder = Decoder::new(Cursor::new(&png));
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size()];
@ -2457,7 +2490,7 @@ mod tests {
write_chunk(&mut png, b"IDAT", &[]);
png
};
let decoder = Decoder::new(png.as_slice());
let decoder = Decoder::new(Cursor::new(&png));
let Err(err) = decoder.read_info() else {
panic!("Expected an error")
};
@ -2476,21 +2509,25 @@ mod tests {
/// `StreamingInput` can be used by tests to simulate a streaming input
/// (e.g. a slow http response, where all bytes are not immediately available).
#[derive(Clone)]
struct StreamingInput(Rc<RefCell<StreamingInputState>>);
struct StreamingInput {
full_input: Vec<u8>,
state: Rc<RefCell<StreamingInputState>>,
}
struct StreamingInputState {
full_input: Vec<u8>,
current_pos: usize,
available_len: usize,
}
impl StreamingInput {
fn new(full_input: Vec<u8>) -> Self {
Self(Rc::new(RefCell::new(StreamingInputState {
Self {
full_input,
current_pos: 0,
available_len: 0,
})))
state: Rc::new(RefCell::new(StreamingInputState {
current_pos: 0,
available_len: 0,
})),
}
}
fn with_noncompressed_png(width: u32, idat_size: usize) -> Self {
@ -2500,14 +2537,14 @@ mod tests {
}
fn expose_next_byte(&self) {
let mut state = self.0.borrow_mut();
assert!(state.available_len < state.full_input.len());
let mut state = self.state.borrow_mut();
assert!(state.available_len < self.full_input.len());
state.available_len += 1;
}
fn stream_input_until_reader_is_available(&self) -> Reader<StreamingInput> {
loop {
self.0.borrow_mut().current_pos = 0;
self.state.borrow_mut().current_pos = 0;
match Decoder::new(self.clone()).read_info() {
Ok(reader) => {
break reader;
@ -2522,24 +2559,49 @@ mod tests {
fn decode_full_input<F, R>(&self, f: F) -> R
where
F: FnOnce(Reader<&[u8]>) -> R,
F: FnOnce(Reader<Cursor<&[u8]>>) -> R,
{
let state = self.0.borrow();
let decoder = Decoder::new(state.full_input.as_slice());
let decoder = Decoder::new(Cursor::new(&*self.full_input));
f(decoder.read_info().unwrap())
}
}
impl Read for StreamingInput {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let mut state = self.0.borrow_mut();
let mut available_bytes = &state.full_input[state.current_pos..state.available_len];
let mut state = self.state.borrow_mut();
let mut available_bytes = &self.full_input[state.current_pos..state.available_len];
let number_of_read_bytes = available_bytes.read(buf)?;
state.current_pos += number_of_read_bytes;
assert!(state.current_pos <= state.available_len);
Ok(number_of_read_bytes)
}
}
impl BufRead for StreamingInput {
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
let state = self.state.borrow();
Ok(&self.full_input[state.current_pos..state.available_len])
}
fn consume(&mut self, amt: usize) {
let mut state = self.state.borrow_mut();
state.current_pos += amt;
assert!(state.current_pos <= state.available_len);
}
}
impl Seek for StreamingInput {
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
let mut state = self.state.borrow_mut();
state.current_pos = match pos {
std::io::SeekFrom::Start(n) => n as usize,
std::io::SeekFrom::End(n) => (self.full_input.len() as i64 + n) as usize,
std::io::SeekFrom::Current(n) => (state.current_pos as i64 + n) as usize,
} as usize;
Ok(state.current_pos as u64)
}
fn stream_position(&mut self) -> std::io::Result<u64> {
Ok(self.state.borrow().current_pos as u64)
}
}
/// Test resuming/retrying `Reader.next_frame` after `UnexpectedEof`.
#[test]
@ -2645,25 +2707,25 @@ mod tests {
/// Creates a ready-to-test [`Reader`] which decodes a PNG that contains:
/// IHDR, IDAT, IEND.
fn create_reader_of_ihdr_idat() -> Reader<VecDeque<u8>> {
let mut png = VecDeque::new();
fn create_reader_of_ihdr_idat() -> Reader<Cursor<Vec<u8>>> {
let mut png = Vec::new();
write_noncompressed_png(&mut png, /* width = */ 16, /* idat_size = */ 1024);
Decoder::new(png).read_info().unwrap()
Decoder::new(Cursor::new(png)).read_info().unwrap()
}
/// Creates a ready-to-test [`Reader`] which decodes an animated PNG that contains:
/// IHDR, acTL, fcTL, IDAT, fcTL, fdAT, IEND. (i.e. IDAT is part of the animation)
fn create_reader_of_ihdr_actl_fctl_idat_fctl_fdat() -> Reader<VecDeque<u8>> {
let idat_width = 16;
fn create_reader_of_ihdr_actl_fctl_idat_fctl_fdat() -> Reader<Cursor<Vec<u8>>> {
let width = 16;
let mut fctl = crate::FrameControl {
width: idat_width,
height: idat_width, // same height and width
width,
height: width,
..Default::default()
};
let mut png = VecDeque::new();
let mut png = Vec::new();
write_png_sig(&mut png);
write_rgba8_ihdr_with_width(&mut png, idat_width);
write_rgba8_ihdr_with_width(&mut png, width);
write_actl(
&mut png,
&crate::AnimationControl {
@ -2690,12 +2752,12 @@ mod tests {
write_fdat(&mut png, 2, &fdat_data);
write_iend(&mut png);
Decoder::new(png).read_info().unwrap()
Decoder::new(Cursor::new(png)).read_info().unwrap()
}
/// Creates a ready-to-test [`Reader`] which decodes an animated PNG that contains: IHDR, acTL,
/// IDAT, fcTL, fdAT, fcTL, fdAT, IEND. (i.e. IDAT is *not* part of the animation)
fn create_reader_of_ihdr_actl_idat_fctl_fdat_fctl_fdat() -> Reader<VecDeque<u8>> {
fn create_reader_of_ihdr_actl_idat_fctl_fdat_fctl_fdat() -> Reader<Cursor<Vec<u8>>> {
let width = 16;
let frame_data = generate_rgba8_with_width_and_height(width, width);
let mut fctl = crate::FrameControl {
@ -2704,7 +2766,7 @@ mod tests {
..Default::default()
};
let mut png = VecDeque::new();
let mut png = Vec::new();
write_png_sig(&mut png);
write_rgba8_ihdr_with_width(&mut png, width);
write_actl(
@ -2723,10 +2785,10 @@ mod tests {
write_fdat(&mut png, 3, &frame_data);
write_iend(&mut png);
Decoder::new(png).read_info().unwrap()
Decoder::new(Cursor::new(png)).read_info().unwrap()
}
fn get_fctl_sequence_number(reader: &Reader<impl Read>) -> u32 {
fn get_fctl_sequence_number(reader: &Reader<impl BufRead + Seek>) -> u32 {
reader
.info()
.frame_control
@ -2966,4 +3028,28 @@ mod tests {
&err,
);
}
#[test]
fn test_incorrect_trns_chunk_is_ignored() {
let png = {
let mut png = Vec::new();
write_png_sig(&mut png);
write_rgba8_ihdr_with_width(&mut png, 8);
write_chunk(&mut png, b"tRNS", &[12, 34, 56]);
write_chunk(
&mut png,
b"IDAT",
&generate_rgba8_with_width_and_height(8, 8),
);
write_iend(&mut png);
png
};
let decoder = Decoder::new(Cursor::new(&png));
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size()];
assert!(reader.info().trns.is_none());
reader.next_frame(&mut buf).unwrap();
assert_eq!(3093270825, crc32fast::hash(&buf));
assert!(reader.info().trns.is_none());
}
}

@ -1,6 +1,6 @@
use super::stream::{DecodingError, FormatErrorInner};
use crate::common::BytesPerPixel;
use crate::filter::{unfilter, FilterType};
use crate::filter::{unfilter, RowFilter};
// Buffer for temporarily holding decompressed, not-yet-`unfilter`-ed rows.
pub(crate) struct UnfilteringBuffer {
@ -96,7 +96,7 @@ impl UnfilteringBuffer {
debug_assert!(prev.is_empty() || prev.len() == (rowlen - 1));
// Get the filter type.
let filter = FilterType::from_u8(row[0]).ok_or(DecodingError::Format(
let filter = RowFilter::from_u8(row[0]).ok_or(DecodingError::Format(
FormatErrorInner::UnknownFilterMethod(row[0]).into(),
))?;
let row = &mut row[1..rowlen];

@ -9,13 +9,14 @@ use flate2::write::ZlibEncoder;
use crate::chunk::{self, ChunkType};
use crate::common::{
AnimationControl, BitDepth, BlendOp, BytesPerPixel, ColorType, Compression, DisposeOp,
FrameControl, Info, ParameterError, ParameterErrorKind, PixelDimensions, ScaledFloat,
FrameControl, Info, ParameterError, ParameterErrorKind, PixelDimensions, ScaledFloat, Unit,
};
use crate::filter::{filter, AdaptiveFilterType, FilterType};
use crate::filter::{filter, Filter};
use crate::text_metadata::{
encode_iso_8859_1, EncodableTextChunk, ITXtChunk, TEXtChunk, TextEncodingError, ZTXtChunk,
};
use crate::traits::WriteBytesExt;
use crate::DeflateCompression;
pub type Result<T> = result::Result<T, EncodingError>;
@ -151,13 +152,13 @@ pub struct Encoder<'a, W: Write> {
options: Options,
}
/// Decoding options, internal type, forwarded to the Writer.
/// Encoding options, internal type, forwarded to the Writer.
#[derive(Default)]
struct Options {
filter: FilterType,
adaptive_filter: AdaptiveFilterType,
filter: Filter,
sep_def_img: bool,
validate_sequence: bool,
compression: DeflateCompression,
}
impl<'a, W: Write> Encoder<'a, W> {
@ -265,18 +266,6 @@ impl<'a, W: Write> Encoder<'a, W> {
self.info.source_chromaticities = Some(source_chromaticities);
}
/// Mark the image data as conforming to the SRGB color space with the specified rendering intent.
///
/// Matching source gamma and chromaticities chunks are added automatically.
/// Any manually specified source gamma, chromaticities, or ICC profiles will be ignored.
#[doc(hidden)]
#[deprecated(note = "use set_source_srgb")]
pub fn set_srgb(&mut self, rendering_intent: super::SrgbRenderingIntent) {
self.info.set_source_srgb(rendering_intent);
self.info.source_gamma = Some(crate::srgb::substitute_gamma());
self.info.source_chromaticities = Some(crate::srgb::substitute_chromaticities());
}
/// Mark the image data as conforming to the SRGB color space with the specified rendering intent.
///
/// Any ICC profiles will be ignored.
@ -308,31 +297,28 @@ impl<'a, W: Write> Encoder<'a, W> {
self.info.bit_depth = depth;
}
/// Set compression parameters.
///
/// Accepts a `Compression` or any type that can transform into a `Compression`. Notably `deflate::Compression` and
/// `deflate::CompressionOptions` which "just work".
/// Set compression parameters, see [Compression] for the available options.
pub fn set_compression(&mut self, compression: Compression) {
self.info.compression = compression;
self.set_deflate_compression(DeflateCompression::from_simple(compression));
self.set_filter(Filter::from_simple(compression));
}
/// Provides in-depth customization of DEFLATE compression options.
///
/// For a simpler selection of compression options see [Self::set_compression].
pub fn set_deflate_compression(&mut self, compression: DeflateCompression) {
self.options.compression = compression;
}
/// Set the used filter type.
///
/// The default filter is [`FilterType::Sub`] which provides a basic prediction algorithm for
/// sample values based on the previous. For a potentially better compression ratio, at the
/// cost of more complex processing, try out [`FilterType::Paeth`].
pub fn set_filter(&mut self, filter: FilterType) {
self.options.filter = filter;
}
/// Set the adaptive filter type.
/// The default filter is [`Filter::Adaptive`] which automatically selects the best filter
/// for each row of the image.
///
/// Adaptive filtering attempts to select the best filter for each line
/// based on heuristics which minimize the file size for compression rather
/// than use a single filter for the entire image. The default method is
/// [`AdaptiveFilterType::NonAdaptive`].
pub fn set_adaptive_filter(&mut self, adaptive_filter: AdaptiveFilterType) {
self.options.adaptive_filter = adaptive_filter;
/// You should only change this if you are after very fast compression,
/// and either don't care about compression ratio or know exactly what works best for your images.
pub fn set_filter(&mut self, filter: Filter) {
self.options.filter = filter;
}
/// Set the fraction of time every frame is going to be displayed, in seconds.
@ -495,7 +481,6 @@ struct PartialInfo {
color_type: ColorType,
frame_control: Option<FrameControl>,
animation_control: Option<AnimationControl>,
compression: Compression,
has_palette: bool,
}
@ -508,39 +493,25 @@ impl PartialInfo {
color_type: info.color_type,
frame_control: info.frame_control,
animation_control: info.animation_control,
compression: info.compression,
has_palette: info.palette.is_some(),
}
}
fn bpp_in_prediction(&self) -> BytesPerPixel {
// Passthrough
self.to_info().bpp_in_prediction()
BytesPerPixel::from_usize(self.bytes_per_pixel())
}
fn bytes_per_pixel(&self) -> usize {
self.color_type.bytes_per_pixel(self.bit_depth)
}
fn raw_row_length(&self) -> usize {
// Passthrough
self.to_info().raw_row_length()
self.raw_row_length_from_width(self.width)
}
fn raw_row_length_from_width(&self, width: u32) -> usize {
// Passthrough
self.to_info().raw_row_length_from_width(width)
}
/// Converts this partial info to an owned Info struct,
/// setting missing values to their defaults
fn to_info(&self) -> Info<'static> {
Info {
width: self.width,
height: self.height,
bit_depth: self.bit_depth,
color_type: self.color_type,
frame_control: self.frame_control,
animation_control: self.animation_control,
compression: self.compression,
..Default::default()
}
self.color_type
.raw_row_length_from_width(self.bit_depth, width)
}
}
@ -589,17 +560,100 @@ impl<W: Write> Writer<W> {
));
}
self.w.write_all(&[137, 80, 78, 71, 13, 10, 26, 10])?; // PNG signature
#[allow(deprecated)]
info.encode(&mut self.w)?;
self.encode_header(info)?;
Ok(self)
}
/// Encode PNG signature, IHDR, and then chunks that were added to the `Info`
fn encode_header(&mut self, info: &Info<'_>) -> Result<()> {
self.w.write_all(&[137, 80, 78, 71, 13, 10, 26, 10])?; // PNG signature
// Encode the IHDR chunk
let mut data = [0; 13];
data[..4].copy_from_slice(&info.width.to_be_bytes());
data[4..8].copy_from_slice(&info.height.to_be_bytes());
data[8] = info.bit_depth as u8;
data[9] = info.color_type as u8;
data[12] = info.interlaced as u8;
self.write_chunk(chunk::IHDR, &data)?;
// Encode the pHYs chunk
if let Some(pd) = info.pixel_dims {
let mut phys_data = [0; 9];
phys_data[0..4].copy_from_slice(&pd.xppu.to_be_bytes());
phys_data[4..8].copy_from_slice(&pd.yppu.to_be_bytes());
match pd.unit {
Unit::Meter => phys_data[8] = 1,
Unit::Unspecified => phys_data[8] = 0,
}
self.write_chunk(chunk::pHYs, &phys_data)?;
}
// If specified, the sRGB information overrides the source gamma and chromaticities.
if let Some(srgb) = &info.srgb {
srgb.encode(&mut self.w)?;
// gAMA and cHRM are optional, for backwards compatibility
let srgb_gamma = crate::srgb::substitute_gamma();
if Some(srgb_gamma) == info.source_gamma {
srgb_gamma.encode_gama(&mut self.w)?
}
let srgb_chromaticities = crate::srgb::substitute_chromaticities();
if Some(srgb_chromaticities) == info.source_chromaticities {
srgb_chromaticities.encode(&mut self.w)?;
}
} else {
if let Some(gma) = info.source_gamma {
gma.encode_gama(&mut self.w)?
}
if let Some(chrms) = info.source_chromaticities {
chrms.encode(&mut self.w)?;
}
if let Some(iccp) = &info.icc_profile {
self.write_iccp_chunk("_", iccp)?
}
}
if let Some(exif) = &info.exif_metadata {
self.write_chunk(chunk::eXIf, exif)?;
}
if let Some(actl) = info.animation_control {
actl.encode(&mut self.w)?;
}
// The position of the PLTE chunk is important, it must come before the tRNS chunk and after
// many of the other metadata chunks.
if let Some(p) = &info.palette {
self.write_chunk(chunk::PLTE, p)?;
};
if let Some(t) = &info.trns {
self.write_chunk(chunk::tRNS, t)?;
}
for text_chunk in &info.uncompressed_latin1_text {
self.write_text_chunk(text_chunk)?;
}
for text_chunk in &info.compressed_latin1_text {
self.write_text_chunk(text_chunk)?;
}
for text_chunk in &info.utf8_text {
self.write_text_chunk(text_chunk)?;
}
Ok(())
}
/// Write a raw chunk of PNG data.
///
/// The chunk will have its CRC calculated and correctly. The data is not filtered in any way,
/// but the chunk needs to be short enough to have its length encoded correctly.
/// This function calculates the required CRC sum so this should not be included in the input
/// `data`, otherwise the data is not filtered in any way. This function returns an error if
/// the length of `data` can't be parsed as a `u32` though the length of the chunk data should
/// not exceed `i32::MAX` or 2,147,483,647.
pub fn write_chunk(&mut self, name: ChunkType, data: &[u8]) -> Result<()> {
use std::convert::TryFrom;
@ -615,6 +669,31 @@ impl<W: Write> Writer<W> {
text_chunk.encode(&mut self.w)
}
fn write_iccp_chunk(&mut self, profile_name: &str, icc_profile: &[u8]) -> Result<()> {
let profile_name = encode_iso_8859_1(profile_name)?;
if profile_name.len() < 1 || profile_name.len() > 79 {
return Err(TextEncodingError::InvalidKeywordSize.into());
}
let estimated_compressed_size = icc_profile.len() * 3 / 4;
let chunk_size = profile_name
.len()
.checked_add(2) // string NUL + compression type. Checked add optimizes out later Vec reallocations.
.and_then(|s| s.checked_add(estimated_compressed_size))
.ok_or(EncodingError::LimitsExceeded)?;
let mut data = Vec::new();
data.try_reserve_exact(chunk_size)
.map_err(|_| EncodingError::LimitsExceeded)?;
data.extend(profile_name.into_iter().chain([0, 0]));
let mut encoder = ZlibEncoder::new(data, flate2::Compression::default());
encoder.write_all(icc_profile)?;
self.write_chunk(chunk::iCCP, &encoder.finish()?)
}
/// Check if we should allow writing another image.
fn validate_new_image(&self) -> Result<()> {
if !self.options.validate_sequence {
@ -692,22 +771,23 @@ impl<W: Write> Writer<W> {
let bpp = self.info.bpp_in_prediction();
let filter_method = self.options.filter;
let adaptive_method = self.options.adaptive_filter;
let zlib_encoded = match self.info.compression {
Compression::Fast => {
let zlib_encoded = match self.options.compression {
DeflateCompression::NoCompression => {
let mut compressor =
fdeflate::StoredOnlyCompressor::new(std::io::Cursor::new(Vec::new()))?;
for line in data.chunks(in_len) {
compressor.write_data(&[0])?;
compressor.write_data(line)?;
}
compressor.finish()?.into_inner()
}
DeflateCompression::FdeflateUltraFast => {
let mut compressor = fdeflate::Compressor::new(std::io::Cursor::new(Vec::new()))?;
let mut current = vec![0; in_len + 1];
for line in data.chunks(in_len) {
let filter_type = filter(
filter_method,
adaptive_method,
bpp,
prev,
line,
&mut current[1..],
);
let filter_type = filter(filter_method, bpp, prev, line, &mut current[1..]);
current[0] = filter_type as u8;
compressor.write_data(&current)?;
@ -721,10 +801,7 @@ impl<W: Write> Writer<W> {
// Write uncompressed data since the result from fast compression would take
// more space than that.
//
// We always use FilterType::NoFilter here regardless of the filter method
// requested by the user. Doing filtering again would only add performance
// cost for both encoding and subsequent decoding, without improving the
// compression ratio.
// This is essentially a fallback to NoCompression.
let mut compressor =
fdeflate::StoredOnlyCompressor::new(std::io::Cursor::new(Vec::new()))?;
for line in data.chunks(in_len) {
@ -736,19 +813,13 @@ impl<W: Write> Writer<W> {
compressed
}
}
_ => {
DeflateCompression::Level(level) => {
let mut current = vec![0; in_len];
let mut zlib = ZlibEncoder::new(Vec::new(), self.info.compression.to_options());
let mut zlib =
ZlibEncoder::new(Vec::new(), flate2::Compression::new(u32::from(level)));
for line in data.chunks(in_len) {
let filter_type = filter(
filter_method,
adaptive_method,
bpp,
prev,
line,
&mut current,
);
let filter_type = filter(filter_method, bpp, prev, line, &mut current);
zlib.write_all(&[filter_type as u8])?;
zlib.write_all(&current)?;
@ -818,25 +889,17 @@ impl<W: Write> Writer<W> {
Ok(())
}
/// Set the used filter type for the following frames.
/// Set the used filter type.
///
/// The default filter is [`FilterType::Sub`] which provides a basic prediction algorithm for
/// sample values based on the previous. For a potentially better compression ratio, at the
/// cost of more complex processing, try out [`FilterType::Paeth`].
pub fn set_filter(&mut self, filter: FilterType) {
/// The default filter is [`Filter::Adaptive`] which automatically selects the best filter
/// for each row of the image.
///
/// You should only change this if you are after very fast compression,
/// and either don't care about compression ratio or know exactly what works best for your images.
pub fn set_filter(&mut self, filter: Filter) {
self.options.filter = filter;
}
/// Set the adaptive filter type for the following frames.
///
/// Adaptive filtering attempts to select the best filter for each line
/// based on heuristics which minimize the file size for compression rather
/// than use a single filter for the entire image. The default method is
/// [`AdaptiveFilterType::NonAdaptive`].
pub fn set_adaptive_filter(&mut self, adaptive_filter: AdaptiveFilterType) {
self.options.adaptive_filter = adaptive_filter;
}
/// Set the fraction of time the following frames are going to be displayed,
/// in seconds
///
@ -1055,36 +1118,6 @@ impl<W: Write> Drop for Writer<W> {
}
}
// This should be moved to Writer after `Info::encoding` is gone
pub(crate) fn write_iccp_chunk<W: Write>(
w: &mut W,
profile_name: &str,
icc_profile: &[u8],
) -> Result<()> {
let profile_name = encode_iso_8859_1(profile_name)?;
if profile_name.len() < 1 || profile_name.len() > 79 {
return Err(TextEncodingError::InvalidKeywordSize.into());
}
let estimated_compressed_size = icc_profile.len() * 3 / 4;
let chunk_size = profile_name
.len()
.checked_add(2) // string NUL + compression type. Checked add optimizes out later Vec reallocations.
.and_then(|s| s.checked_add(estimated_compressed_size))
.ok_or(EncodingError::LimitsExceeded)?;
let mut data = Vec::new();
data.try_reserve_exact(chunk_size)
.map_err(|_| EncodingError::LimitsExceeded)?;
data.extend(profile_name.into_iter().chain([0, 0]));
let mut encoder = ZlibEncoder::new(data, flate2::Compression::default());
encoder.write_all(icc_profile)?;
write_chunk(w, chunk::iCCP, &encoder.finish()?)
}
enum ChunkOutput<'a, W: Write> {
Borrowed(&'a mut Writer<W>),
Owned(Writer<W>),
@ -1342,10 +1375,9 @@ pub struct StreamWriter<'a, W: Write> {
height: u32,
bpp: BytesPerPixel,
filter: FilterType,
adaptive_filter: AdaptiveFilterType,
filter: Filter,
fctl: Option<FrameControl>,
compression: Compression,
compression: DeflateCompression,
}
impl<'a, W: Write> StreamWriter<'a, W> {
@ -1354,21 +1386,20 @@ impl<'a, W: Write> StreamWriter<'a, W> {
width,
height,
frame_control: fctl,
compression,
..
} = writer.info;
let bpp = writer.info.bpp_in_prediction();
let in_len = writer.info.raw_row_length() - 1;
let filter = writer.options.filter;
let adaptive_filter = writer.options.adaptive_filter;
let compression = writer.options.compression;
let prev_buf = vec![0; in_len];
let curr_buf = vec![0; in_len];
let mut chunk_writer = ChunkWriter::new(writer, buf_len);
let (line_len, to_write) = chunk_writer.next_frame_info();
chunk_writer.write_header()?;
let zlib = ZlibEncoder::new(chunk_writer, compression.to_options());
let zlib = ZlibEncoder::new(chunk_writer, compression.closest_flate2_level());
Ok(StreamWriter {
writer: Wrapper::Zlib(zlib),
@ -1379,7 +1410,6 @@ impl<'a, W: Write> StreamWriter<'a, W> {
filter,
width,
height,
adaptive_filter,
line_len,
to_write,
fctl,
@ -1387,29 +1417,17 @@ impl<'a, W: Write> StreamWriter<'a, W> {
})
}
/// Set the used filter type for the next frame.
/// Set the used filter type.
///
/// The default filter is [`FilterType::Sub`] which provides a basic prediction algorithm for
/// sample values based on the previous.
/// The default filter is [`Filter::Adaptive`] which automatically selects the best filter
/// for each row of the image.
///
/// For optimal compression ratio you should enable adaptive filtering
/// instead of setting a single filter for the entire image, see
/// [set_adaptive_filter](Self::set_adaptive_filter).
pub fn set_filter(&mut self, filter: FilterType) {
/// You should only change this if you are after very fast compression,
/// and either don't care about compression ratio or know exactly what works best for your images.
pub fn set_filter(&mut self, filter: Filter) {
self.filter = filter;
}
/// Set the adaptive filter type for the next frame.
///
/// Adaptive filtering attempts to select the best filter for each line
/// based on heuristics which minimize the file size for compression rather
/// than use a single filter for the entire image.
///
/// The default method is [`AdaptiveFilterType::NonAdaptive`].
pub fn set_adaptive_filter(&mut self, adaptive_filter: AdaptiveFilterType) {
self.adaptive_filter = adaptive_filter;
}
/// Set the fraction of time the following frames are going to be displayed,
/// in seconds
///
@ -1611,7 +1629,7 @@ impl<'a, W: Write> StreamWriter<'a, W> {
// now it can be taken because the next statements cannot cause any errors
match self.writer.take() {
Wrapper::Chunk(wrt) => {
let encoder = ZlibEncoder::new(wrt, self.compression.to_options());
let encoder = ZlibEncoder::new(wrt, self.compression.closest_flate2_level());
self.writer = Wrapper::Zlib(encoder);
}
_ => unreachable!(),
@ -1659,7 +1677,6 @@ impl<'a, W: Write> Write for StreamWriter<'a, W> {
let mut filtered = vec![0; self.curr_buf.len()];
let filter_type = filter(
self.filter,
self.adaptive_filter,
self.bpp,
&self.prev_buf,
&self.curr_buf,
@ -1707,39 +1724,30 @@ impl<W: Write> Drop for StreamWriter<'_, W> {
}
}
/// Mod to encapsulate the converters depending on the `deflate` crate.
///
/// Since this only contains trait impls, there is no need to make this public, they are simply
/// available when the mod is compiled as well.
impl Compression {
fn to_options(self) -> flate2::Compression {
#[allow(deprecated)]
match self {
Compression::Default => flate2::Compression::default(),
Compression::Fast => flate2::Compression::fast(),
Compression::Best => flate2::Compression::best(),
#[allow(deprecated)]
Compression::Huffman => flate2::Compression::none(),
#[allow(deprecated)]
Compression::Rle => flate2::Compression::none(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Decoder;
use io::BufReader;
use rand::{thread_rng, Rng};
use std::cmp;
use std::fs::File;
use std::io::Cursor;
#[test]
fn roundtrip() {
fn roundtrip1() {
roundtrip_inner();
}
#[test]
fn roundtrip2() {
roundtrip_inner();
}
fn roundtrip_inner() {
// More loops = more random testing, but also more test wait time
for _ in 0..10 {
for _ in 0..5 {
for path in glob::glob("tests/pngsuite/*.png")
.unwrap()
.map(|r| r.unwrap())
@ -1750,42 +1758,55 @@ mod tests {
}
eprintln!("{}", path.display());
// Decode image
let decoder = Decoder::new(File::open(path).unwrap());
let decoder = Decoder::new(BufReader::new(File::open(path).unwrap()));
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf).unwrap();
// Encode decoded image
let mut out = Vec::new();
{
let mut wrapper = RandomChunkWriter {
rng: thread_rng(),
w: &mut out,
};
use DeflateCompression::*;
for compression in [NoCompression, FdeflateUltraFast, Level(4)] {
// Encode decoded image
let mut out = Vec::new();
{
let mut wrapper = RandomChunkWriter {
rng: thread_rng(),
w: &mut out,
};
let mut encoder = Encoder::new(&mut wrapper, info.width, info.height);
encoder.set_color(info.color_type);
encoder.set_depth(info.bit_depth);
if let Some(palette) = &reader.info().palette {
encoder.set_palette(palette.clone());
let mut encoder = Encoder::new(&mut wrapper, info.width, info.height);
encoder.set_color(info.color_type);
encoder.set_depth(info.bit_depth);
encoder.set_deflate_compression(compression);
if let Some(palette) = &reader.info().palette {
encoder.set_palette(palette.clone());
}
let mut encoder = encoder.write_header().unwrap();
encoder.write_image_data(&buf).unwrap();
}
let mut encoder = encoder.write_header().unwrap();
encoder.write_image_data(&buf).unwrap();
// Decode encoded decoded image
let decoder = Decoder::new(Cursor::new(&*out));
let mut reader = decoder.read_info().unwrap();
let mut buf2 = vec![0; reader.output_buffer_size()];
reader.next_frame(&mut buf2).unwrap();
// check if the encoded image is ok:
assert_eq!(buf, buf2);
}
// Decode encoded decoded image
let decoder = Decoder::new(&*out);
let mut reader = decoder.read_info().unwrap();
let mut buf2 = vec![0; reader.output_buffer_size()];
reader.next_frame(&mut buf2).unwrap();
// check if the encoded image is ok:
assert_eq!(buf, buf2);
}
}
}
#[test]
fn roundtrip_stream() {
fn roundtrip_stream1() {
roundtrip_stream_inner();
}
#[test]
fn roundtrip_stream2() {
roundtrip_stream_inner();
}
fn roundtrip_stream_inner() {
// More loops = more random testing, but also more test wait time
for _ in 0..10 {
for _ in 0..5 {
for path in glob::glob("tests/pngsuite/*.png")
.unwrap()
.map(|r| r.unwrap())
@ -1795,41 +1816,45 @@ mod tests {
continue;
}
// Decode image
let decoder = Decoder::new(File::open(path).unwrap());
let decoder = Decoder::new(BufReader::new(File::open(path).unwrap()));
let mut reader = decoder.read_info().unwrap();
let mut buf = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf).unwrap();
// Encode decoded image
let mut out = Vec::new();
{
let mut wrapper = RandomChunkWriter {
rng: thread_rng(),
w: &mut out,
};
use DeflateCompression::*;
for compression in [NoCompression, FdeflateUltraFast, Level(4)] {
// Encode decoded image
let mut out = Vec::new();
{
let mut wrapper = RandomChunkWriter {
rng: thread_rng(),
w: &mut out,
};
let mut encoder = Encoder::new(&mut wrapper, info.width, info.height);
encoder.set_color(info.color_type);
encoder.set_depth(info.bit_depth);
if let Some(palette) = &reader.info().palette {
encoder.set_palette(palette.clone());
let mut encoder = Encoder::new(&mut wrapper, info.width, info.height);
encoder.set_color(info.color_type);
encoder.set_depth(info.bit_depth);
encoder.set_deflate_compression(compression);
if let Some(palette) = &reader.info().palette {
encoder.set_palette(palette.clone());
}
let mut encoder = encoder.write_header().unwrap();
let mut stream_writer = encoder.stream_writer().unwrap();
let mut outer_wrapper = RandomChunkWriter {
rng: thread_rng(),
w: &mut stream_writer,
};
outer_wrapper.write_all(&buf).unwrap();
}
let mut encoder = encoder.write_header().unwrap();
let mut stream_writer = encoder.stream_writer().unwrap();
let mut outer_wrapper = RandomChunkWriter {
rng: thread_rng(),
w: &mut stream_writer,
};
outer_wrapper.write_all(&buf).unwrap();
// Decode encoded decoded image
let decoder = Decoder::new(Cursor::new(&*out));
let mut reader = decoder.read_info().unwrap();
let mut buf2 = vec![0; reader.output_buffer_size()];
reader.next_frame(&mut buf2).unwrap();
// check if the encoded image is ok:
assert_eq!(buf, buf2);
}
// Decode encoded decoded image
let decoder = Decoder::new(&*out);
let mut reader = decoder.read_info().unwrap();
let mut buf2 = vec![0; reader.output_buffer_size()];
reader.next_frame(&mut buf2).unwrap();
// check if the encoded image is ok:
assert_eq!(buf, buf2);
}
}
}
@ -1839,7 +1864,7 @@ mod tests {
for &bit_depth in &[1u8, 2, 4, 8] {
// Do a reference decoding, choose a fitting palette image from pngsuite
let path = format!("tests/pngsuite/basn3p0{}.png", bit_depth);
let decoder = Decoder::new(File::open(&path).unwrap());
let decoder = Decoder::new(BufReader::new(File::open(&path).unwrap()));
let mut reader = decoder.read_info().unwrap();
let mut decoded_pixels = vec![0; reader.output_buffer_size()];
@ -1864,7 +1889,7 @@ mod tests {
}
// Decode re-encoded image
let decoder = Decoder::new(&*out);
let decoder = Decoder::new(Cursor::new(&*out));
let mut reader = decoder.read_info().unwrap();
let mut redecoded = vec![0; reader.output_buffer_size()];
reader.next_frame(&mut redecoded).unwrap();
@ -2056,7 +2081,7 @@ mod tests {
fn all_filters_roundtrip() -> io::Result<()> {
let pixel: Vec<_> = (0..48).collect();
let roundtrip = |filter: FilterType| -> io::Result<()> {
let roundtrip = |filter: Filter| -> io::Result<()> {
let mut buffer = vec![];
let mut encoder = Encoder::new(&mut buffer, 4, 4);
encoder.set_depth(BitDepth::Eight);
@ -2076,11 +2101,11 @@ mod tests {
Ok(())
};
roundtrip(FilterType::NoFilter)?;
roundtrip(FilterType::Sub)?;
roundtrip(FilterType::Up)?;
roundtrip(FilterType::Avg)?;
roundtrip(FilterType::Paeth)?;
roundtrip(Filter::NoFilter)?;
roundtrip(Filter::Sub)?;
roundtrip(Filter::Up)?;
roundtrip(Filter::Avg)?;
roundtrip(Filter::Paeth)?;
Ok(())
}
@ -2094,7 +2119,7 @@ mod tests {
let mut encoder = Encoder::new(&mut buffer, 4, 4);
encoder.set_depth(BitDepth::Eight);
encoder.set_color(ColorType::Rgb);
encoder.set_filter(FilterType::Avg);
encoder.set_filter(Filter::Avg);
if let Some(gamma) = gamma {
encoder.set_source_gamma(gamma);
}
@ -2103,7 +2128,7 @@ mod tests {
let decoder = crate::Decoder::new(Cursor::new(buffer));
let mut reader = decoder.read_info()?;
assert_eq!(
reader.info().source_gamma,
reader.info().gamma(),
gamma,
"Deviation with gamma {:?}",
gamma
@ -2318,7 +2343,7 @@ mod tests {
let mut encoder = Encoder::new(&mut cursor, 8, 8);
encoder.set_color(ColorType::Rgba);
encoder.set_filter(FilterType::Paeth);
encoder.set_filter(Filter::Paeth);
let mut writer = encoder.write_header()?;
let mut stream = writer.stream_writer()?;

@ -1,6 +1,6 @@
use core::convert::TryInto;
use crate::common::BytesPerPixel;
use crate::{common::BytesPerPixel, Compression};
/// SIMD helpers for `fn unfilter`
///
@ -60,7 +60,7 @@ mod simd {
out.into()
}
/// Memory of previous pixels (as needed to unfilter `FilterType::Paeth`).
/// Memory of previous pixels (as needed to unfilter `Filter::Paeth`).
/// See also https://www.w3.org/TR/png/#filter-byte-positions
#[derive(Default)]
struct PaethState<T, const N: usize>
@ -75,7 +75,7 @@ mod simd {
a: Simd<T, N>,
}
/// Mutates `x` as needed to unfilter `FilterType::Paeth`.
/// Mutates `x` as needed to unfilter `Filter::Paeth`.
///
/// `b` is the current pixel in the previous row. `x` is the current pixel in the current row.
/// See also https://www.w3.org/TR/png/#filter-byte-positions
@ -124,7 +124,7 @@ mod simd {
dest[0..3].copy_from_slice(&src.to_array()[0..3])
}
/// Undoes `FilterType::Paeth` for `BytesPerPixel::Three`.
/// Undoes `Filter::Paeth` for `BytesPerPixel::Three`.
pub fn unfilter_paeth3(mut prev_row: &[u8], mut curr_row: &mut [u8]) {
debug_assert_eq!(prev_row.len(), curr_row.len());
debug_assert_eq!(prev_row.len() % 3, 0);
@ -155,7 +155,7 @@ mod simd {
store3(x, curr_row);
}
/// Undoes `FilterType::Paeth` for `BytesPerPixel::Four` and `BytesPerPixel::Eight`.
/// Undoes `Filter::Paeth` for `BytesPerPixel::Four` and `BytesPerPixel::Eight`.
///
/// This function calculates the Paeth predictor entirely in `Simd<u8, N>`
/// without converting to an intermediate `Simd<i16, N>`. Doing so avoids
@ -187,7 +187,7 @@ mod simd {
dest[0..6].copy_from_slice(&src.to_array()[0..6])
}
/// Undoes `FilterType::Paeth` for `BytesPerPixel::Six`.
/// Undoes `Filter::Paeth` for `BytesPerPixel::Six`.
pub fn unfilter_paeth6(mut prev_row: &[u8], mut curr_row: &mut [u8]) {
debug_assert_eq!(prev_row.len(), curr_row.len());
debug_assert_eq!(prev_row.len() % 6, 0);
@ -226,9 +226,53 @@ mod simd {
/// this does not operate on pixels but on raw bytes of a scanline.
///
/// Details on how each filter works can be found in the [PNG Book](http://www.libpng.org/pub/png/book/chapter09.html).
///
/// The default filter is `Adaptive`, which uses heuristics to select the best filter for every row.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Filter {
NoFilter,
Sub,
Up,
Avg,
Paeth,
Adaptive,
}
impl Default for Filter {
fn default() -> Self {
Filter::Adaptive
}
}
impl From<RowFilter> for Filter {
fn from(value: RowFilter) -> Self {
match value {
RowFilter::NoFilter => Filter::NoFilter,
RowFilter::Sub => Filter::Sub,
RowFilter::Up => Filter::Up,
RowFilter::Avg => Filter::Avg,
RowFilter::Paeth => Filter::Paeth,
}
}
}
impl Filter {
pub(crate) fn from_simple(compression: Compression) -> Self {
match compression {
Compression::NoCompression => Filter::NoFilter, // with no DEFLATE filtering would only waste time
Compression::Fastest => Filter::Up, // pairs well with FdeflateUltraFast, producing much smaller files while being very fast
Compression::Fast => Filter::Adaptive,
Compression::Balanced => Filter::Adaptive,
Compression::High => Filter::Adaptive,
}
}
}
/// Unlike the public [Filter], does not include the "Adaptive" option
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum FilterType {
pub(crate) enum RowFilter {
NoFilter = 0,
Sub = 1,
Up = 2,
@ -236,44 +280,33 @@ pub enum FilterType {
Paeth = 4,
}
impl Default for FilterType {
impl Default for RowFilter {
fn default() -> Self {
FilterType::Sub
RowFilter::Up
}
}
impl FilterType {
/// u8 -> Self. Temporary solution until Rust provides a canonical one.
pub fn from_u8(n: u8) -> Option<FilterType> {
impl RowFilter {
pub fn from_u8(n: u8) -> Option<Self> {
match n {
0 => Some(FilterType::NoFilter),
1 => Some(FilterType::Sub),
2 => Some(FilterType::Up),
3 => Some(FilterType::Avg),
4 => Some(FilterType::Paeth),
0 => Some(Self::NoFilter),
1 => Some(Self::Sub),
2 => Some(Self::Up),
3 => Some(Self::Avg),
4 => Some(Self::Paeth),
_ => None,
}
}
}
/// Adaptive filtering tries every possible filter for each row and uses a heuristic to select the best one.
/// This improves compression ratio, but makes encoding slightly slower.
///
/// It is recommended to use `Adaptive` whenever you care about compression ratio.
/// Filtering is quite cheap compared to other parts of encoding, but can contribute
/// to the compression ratio significantly.
///
/// `NonAdaptive` filtering is the default.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum AdaptiveFilterType {
Adaptive,
NonAdaptive,
}
impl Default for AdaptiveFilterType {
fn default() -> Self {
AdaptiveFilterType::NonAdaptive
pub fn from_method(strat: Filter) -> Option<Self> {
match strat {
Filter::NoFilter => Some(Self::NoFilter),
Filter::Sub => Some(Self::Sub),
Filter::Up => Some(Self::Up),
Filter::Avg => Some(Self::Avg),
Filter::Paeth => Some(Self::Paeth),
Filter::Adaptive => None,
}
}
}
@ -313,7 +346,7 @@ fn filter_paeth_stbi(a: u8, b: u8, c: u8) -> u8 {
let hi = a.max(b);
let t0 = if hi as i16 <= thresh { lo } else { c };
let t1 = if thresh <= lo as i16 { hi } else { t0 };
return t1;
t1
}
#[cfg(any(test, all(feature = "unstable", target_arch = "x86_64")))]
@ -370,12 +403,12 @@ fn filter_paeth_fpnge(a: u8, b: u8, c: u8) -> u8 {
}
pub(crate) fn unfilter(
mut filter: FilterType,
mut filter: RowFilter,
tbpp: BytesPerPixel,
previous: &[u8],
current: &mut [u8],
) {
use self::FilterType::*;
use self::RowFilter::*;
// If the previous row is empty, then treat it as if it were filled with zeros.
if previous.is_empty() {
@ -771,11 +804,9 @@ pub(crate) fn unfilter(
}
}
BytesPerPixel::Four => {
#[cfg(all(feature = "unstable", target_arch = "x86_64"))]
{
simd::unfilter_paeth_u8::<4>(previous, current);
return;
}
// Using the `simd` module here has no effect on Linux
// and appears to regress performance on Windows, so we don't use it here.
// See https://github.com/image-rs/image-png/issues/567
let mut a_bpp = [0; 4];
let mut c_bpp = [0; 4];
@ -866,14 +897,14 @@ pub(crate) fn unfilter(
}
fn filter_internal(
method: FilterType,
method: RowFilter,
bpp: usize,
len: usize,
previous: &[u8],
current: &[u8],
output: &mut [u8],
) -> FilterType {
use self::FilterType::*;
) -> RowFilter {
use self::RowFilter::*;
// This value was chosen experimentally based on what achieved the best performance. The
// Rust compiler does auto-vectorization, and 32-bytes per loop iteration seems to enable
@ -1005,24 +1036,20 @@ fn filter_internal(
}
pub(crate) fn filter(
method: FilterType,
adaptive: AdaptiveFilterType,
method: Filter,
bpp: BytesPerPixel,
previous: &[u8],
current: &[u8],
output: &mut [u8],
) -> FilterType {
use FilterType::*;
) -> RowFilter {
use RowFilter::*;
let bpp = bpp.into_usize();
let len = current.len();
match adaptive {
AdaptiveFilterType::NonAdaptive => {
filter_internal(method, bpp, len, previous, current, output)
}
AdaptiveFilterType::Adaptive => {
match method {
Filter::Adaptive => {
let mut min_sum: u64 = u64::MAX;
let mut filter_choice = FilterType::NoFilter;
let mut filter_choice = RowFilter::NoFilter;
for &filter in [Sub, Up, Avg, Paeth].iter() {
filter_internal(filter, bpp, len, previous, current, output);
let sum = sum_buffer(output);
@ -1037,6 +1064,10 @@ pub(crate) fn filter(
}
filter_choice
}
_ => {
let filter = RowFilter::from_method(method).unwrap();
filter_internal(filter, bpp, len, previous, current, output)
}
}
}
@ -1076,11 +1107,10 @@ mod test {
let previous: Vec<_> = iter::repeat(1).take(LEN.into()).collect();
let current: Vec<_> = (0..LEN).collect();
let expected = current.clone();
let adaptive = AdaptiveFilterType::NonAdaptive;
let roundtrip = |kind, bpp: BytesPerPixel| {
let roundtrip = |kind: RowFilter, bpp: BytesPerPixel| {
let mut output = vec![0; LEN.into()];
filter(kind, adaptive, bpp, &previous, &current, &mut output);
filter(kind.into(), bpp, &previous, &current, &mut output);
unfilter(kind, bpp, &previous, &mut output);
assert_eq!(
output, expected,
@ -1090,11 +1120,11 @@ mod test {
};
let filters = [
FilterType::NoFilter,
FilterType::Sub,
FilterType::Up,
FilterType::Avg,
FilterType::Paeth,
RowFilter::NoFilter,
RowFilter::Sub,
RowFilter::Up,
RowFilter::Avg,
RowFilter::Paeth,
];
let bpps = [
@ -1139,11 +1169,10 @@ mod test {
let previous: Vec<_> = (0..LEN).collect();
let current: Vec<_> = (0..LEN).collect();
let expected = current.clone();
let adaptive = AdaptiveFilterType::NonAdaptive;
let roundtrip = |kind, bpp: BytesPerPixel| {
let roundtrip = |kind: RowFilter, bpp: BytesPerPixel| {
let mut output = vec![0; LEN.into()];
filter(kind, adaptive, bpp, &previous, &current, &mut output);
filter(kind.into(), bpp, &previous, &current, &mut output);
unfilter(kind, bpp, &previous, &mut output);
assert_eq!(
output, expected,
@ -1153,11 +1182,11 @@ mod test {
};
let filters = [
FilterType::NoFilter,
FilterType::Sub,
FilterType::Up,
FilterType::Avg,
FilterType::Paeth,
RowFilter::NoFilter,
RowFilter::Sub,
RowFilter::Up,
RowFilter::Avg,
RowFilter::Paeth,
];
let bpps = [

@ -12,9 +12,10 @@
//! ### Using the decoder
//! ```
//! use std::fs::File;
//! use std::io::BufReader;
//! // The decoder is a build for reader and can be used to set various decoding options
//! // via `Transformations`. The default output transformation is `Transformations::IDENTITY`.
//! let decoder = png::Decoder::new(File::open("tests/pngsuite/basi0g01.png").unwrap());
//! let decoder = png::Decoder::new(BufReader::new(File::open("tests/pngsuite/basi0g01.png").unwrap()));
//! let mut reader = decoder.read_info().unwrap();
//! // Allocate the output buffer.
//! let mut buf = vec![0; reader.output_buffer_size()];
@ -77,7 +78,7 @@ pub use crate::common::*;
pub use crate::decoder::stream::{DecodeOptions, Decoded, DecodingError, StreamingDecoder};
pub use crate::decoder::{Decoder, InterlaceInfo, InterlacedRow, Limits, OutputInfo, Reader};
pub use crate::encoder::{Encoder, EncodingError, StreamWriter, Writer};
pub use crate::filter::{AdaptiveFilterType, FilterType};
pub use crate::filter::Filter;
#[cfg(test)]
pub(crate) mod test_utils;

@ -5,9 +5,9 @@
//! chunks. There are three kinds of text chunks.
//! - `tEXt`: This has a `keyword` and `text` field, and is ISO 8859-1 encoded.
//! - `zTXt`: This is semantically the same as `tEXt`, i.e. it has the same fields and
//! encoding, but the `text` field is compressed before being written into the PNG file.
//! encoding, but the `text` field is compressed before being written into the PNG file.
//! - `iTXt`: This chunk allows for its `text` field to be any valid UTF-8, and supports
//! compression of the text field as well.
//! compression of the text field as well.
//!
//! The `ISO 8859-1` encoding technically doesn't allow any control characters
//! to be used, but in practice these values are encountered anyway. This can
@ -24,17 +24,13 @@
//!
//! ```
//! use std::fs::File;
//! use std::io::BufReader;
//! use std::iter::FromIterator;
//! use std::path::PathBuf;
//!
//! // Opening a png file that has a zTXt chunk
//! let decoder = png::Decoder::new(
//! File::open(PathBuf::from_iter([
//! "tests",
//! "text_chunk_examples",
//! "ztxt_example.png",
//! ]))
//! .unwrap(),
//! BufReader::new(File::open("tests/text_chunk_examples/ztxt_example.png").unwrap())
//! );
//! let mut reader = decoder.read_info().unwrap();
//! // If the text chunk is before the image data frames, `reader.info()` already contains the text.

@ -1,69 +0,0 @@
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# @generated from third_party/rust/chromium_crates_io/BUILD.gn.hbs by
# tools/crates/gnrt.
# Do not edit!
import("//build/rust/cargo_crate.gni")
cargo_crate("lib") {
crate_name = "png"
epoch = "0.17"
crate_type = "rlib"
crate_root =
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/lib.rs"
sources = [
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/adam7.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/benchable_apis.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/chunk.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/common.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/decoder/interlace_info.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/decoder/mod.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/decoder/read_decoder.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/decoder/stream.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/decoder/transform.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/decoder/transform/palette.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/decoder/unfiltering_buffer.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/decoder/zlib.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/encoder.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/filter.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/lib.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/srgb.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/test_utils.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/text_metadata.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.17.16/src/traits.rs",
]
inputs = []
build_native_rust_unit_tests = false
edition = "2018"
cargo_pkg_version = "0.17.16"
cargo_pkg_authors = "The image-rs Developers"
cargo_pkg_name = "png"
cargo_pkg_description = "PNG decoding and encoding library in pure Rust"
library_configs -= [ "//build/config/coverage:default_coverage" ]
library_configs -= [ "//build/config/compiler:chromium_code" ]
library_configs += [ "//build/config/compiler:no_chromium_code" ]
executable_configs -= [ "//build/config/compiler:chromium_code" ]
executable_configs += [ "//build/config/compiler:no_chromium_code" ]
proc_macro_configs -= [ "//build/config/compiler:chromium_code" ]
proc_macro_configs += [ "//build/config/compiler:no_chromium_code" ]
deps = [
"//third_party/rust/bitflags/v1:lib",
"//third_party/rust/crc32fast/v1:lib",
"//third_party/rust/fdeflate/v0_3:lib",
"//third_party/rust/flate2/v1:lib",
"//third_party/rust/miniz_oxide/v0_8:lib",
]
library_configs -= [ "//build/config/compiler:default_optimization" ]
executable_configs -= [ "//build/config/compiler:default_optimization" ]
proc_macro_configs -= [ "//build/config/compiler:default_optimization" ]
library_configs += [ "//build/config/compiler:optimize" ]
executable_configs += [ "//build/config/compiler:optimize" ]
proc_macro_configs += [ "//build/config/compiler:optimize" ]
rustflags = [
"--cap-lints=allow", # Suppress all warnings in crates.io crates
]
}

69
third_party/rust/png/v0_18/BUILD.gn vendored Normal file

@ -0,0 +1,69 @@
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# @generated from third_party/rust/chromium_crates_io/BUILD.gn.hbs by
# tools/crates/gnrt.
# Do not edit!
import("//build/rust/cargo_crate.gni")
cargo_crate("lib") {
crate_name = "png"
epoch = "0.18"
crate_type = "rlib"
crate_root =
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/lib.rs"
sources = [
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/adam7.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/benchable_apis.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/chunk.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/common.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/decoder/interlace_info.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/decoder/mod.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/decoder/read_decoder.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/decoder/stream.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/decoder/transform.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/decoder/transform/palette.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/decoder/unfiltering_buffer.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/decoder/zlib.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/encoder.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/filter.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/lib.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/srgb.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/test_utils.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/text_metadata.rs",
"//third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/src/traits.rs",
]
inputs = []
build_native_rust_unit_tests = false
edition = "2021"
cargo_pkg_version = "0.18.0-rc"
cargo_pkg_authors = "The image-rs Developers"
cargo_pkg_name = "png"
cargo_pkg_description = "PNG decoding and encoding library in pure Rust"
library_configs -= [ "//build/config/coverage:default_coverage" ]
library_configs -= [ "//build/config/compiler:chromium_code" ]
library_configs += [ "//build/config/compiler:no_chromium_code" ]
executable_configs -= [ "//build/config/compiler:chromium_code" ]
executable_configs += [ "//build/config/compiler:no_chromium_code" ]
proc_macro_configs -= [ "//build/config/compiler:chromium_code" ]
proc_macro_configs += [ "//build/config/compiler:no_chromium_code" ]
deps = [
"//third_party/rust/bitflags/v2:lib",
"//third_party/rust/crc32fast/v1:lib",
"//third_party/rust/fdeflate/v0_3:lib",
"//third_party/rust/flate2/v1:lib",
"//third_party/rust/miniz_oxide/v0_8:lib",
]
library_configs -= [ "//build/config/compiler:default_optimization" ]
executable_configs -= [ "//build/config/compiler:default_optimization" ]
proc_macro_configs -= [ "//build/config/compiler:default_optimization" ]
library_configs += [ "//build/config/compiler:optimize" ]
executable_configs += [ "//build/config/compiler:optimize" ]
proc_macro_configs += [ "//build/config/compiler:optimize" ]
rustflags = [
"--cap-lints=allow", # Suppress all warnings in crates.io crates
]
}

@ -1,9 +1,9 @@
Name: png
URL: https://crates.io/crates/png
Version: 0.17.16
Revision: fbf256669ff23594bf4c618b61fde6a52b79e088
Version: 0.18.0-rc
Revision: eb9b5d7f371b88f15aaca6a8d21c58b86c400d76
License: Apache-2.0
License File: //third_party/rust/chromium_crates_io/vendor/png-0.17.16/LICENSE-APACHE
License File: //third_party/rust/chromium_crates_io/vendor/png-0.18.0-rc/LICENSE-APACHE
Shipped: yes
Security Critical: yes