image-0.24.7/.cargo_vcs_info.json0000644000000001360000000000100122100ustar { "git": { "sha1": "e5580ec13c56e1303bec2e1b94d71da5efc6dcf8" }, "path_in_vcs": "" }image-0.24.7/.gitattributes000064400000000000000000000000501046102023000136660ustar 00000000000000*.jpg binary -delta *.png binary -delta image-0.24.7/.github/ISSUE_TEMPLATE/bug_template.md000064400000000000000000000004141046102023000175140ustar 00000000000000--- name: Bug report about: Reporting a bug labels: bug title: '' assignees: '' --- This happens in ,... ## Expected What behaviour should have happened ## Actual behaviour What did happen ## Reproduction steps Provide source code, a repository link, or steps image-0.24.7/.github/ISSUE_TEMPLATE/feature_request.md000064400000000000000000000012251046102023000202500ustar 00000000000000--- name: Feature request about: Enabling something that is not yet possible labels: enhancement title: '' assignees: '' --- I would like to be able ... My specific use case for this functionality is ... This is more generally applicable to ... ## Draft image-0.24.7/.github/ISSUE_TEMPLATE/question.md000064400000000000000000000007221046102023000167150ustar 00000000000000--- name: Question about: Unsure how to get something to work labels: question title: '' assignees: '' --- How does one ... ? Overall, I'm trying to achieve ... image-0.24.7/.github/PULL_REQUEST_TEMPLATE.md000064400000000000000000000004231046102023000161400ustar 00000000000000 image-0.24.7/.github/workflows/rust.yml000064400000000000000000000145161046102023000161240ustar 00000000000000name: Rust CI on: push: branches: [ master, main, next ] pull_request: branches: [ master, main, next ] schedule: - cron: '5 16 * * 6' jobs: test_features: runs-on: ubuntu-latest strategy: fail-fast: false matrix: features: ['', default, gif, jpeg, png, tiff, ico, pnm, tga, webp, bmp, hdr, dxt, dds, farbfeld, openexr, jpeg_rayon, webp-encoder] steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable - name: Cache Cargo Dependencies uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - name: build run: cargo build -v --no-default-features --features "$FEATURES" env: FEATURES: ${{ matrix.features }} - name: test run: > cargo test -v --no-default-features --features "$FEATURES" && cargo doc -v --no-default-features --features "$FEATURES" env: FEATURES: ${{ matrix.features }} test_toolchains: runs-on: ubuntu-latest strategy: fail-fast: false matrix: rust: ["1.61.0", nightly, beta] steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@v1 with: toolchain: ${{ matrix.rust }} - name: Cache Cargo Dependencies uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - name: Install MSRV Cargo.lock if: ${{ matrix.rust == '1.61.0' }} run: mv Cargo.lock.msrv Cargo.lock - name: build run: cargo build -v --features webp,webp-encoder - name: test if: ${{ matrix.rust != '1.61.0' }} run: > cargo test -v --features webp,webp-encoder && cargo doc -v --features webp,webp-encoder test_big_endian: # github actions does not support big endian systems directly, but it does support QEMU. # so we install qemu, then build and run the tests in an emulated mips system. # note: you can also use this approach to test for big endian locally. runs-on: ubuntu-latest strategy: matrix: features: ['', default, webp, webp-encoder] # we are using the cross project for cross compilation to mips: # https://github.com/cross-rs/cross steps: - uses: actions/checkout@v2 - name: Install or use cached cross-rs/cross uses: baptiste0928/cargo-install@v1 with: crate: cross - name: Cache Cargo Dependencies uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - name: Start Docker (required for cross-rs) run: sudo systemctl start docker - name: Cross-Compile project to mips-unknown-linux-gnu run: | cross build --target=mips-unknown-linux-gnu --verbose -v --no-default-features --features "$FEATURES" env: FEATURES: ${{ matrix.features }} # https://github.com/cross-rs/cross#supported-targets - name: Cross-Run Tests in mips-unknown-linux-gnu using Qemu continue-on-error: true run: | cross test --target mips-unknown-linux-gnu --verbose -v --no-default-features --features "$FEATURES" env: FEATURES: ${{ matrix.features }} test_avif: runs-on: ubuntu-latest steps: - name: install-dependencies run: sudo apt update && sudo apt install nasm - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable - name: build run: cargo build -v --no-default-features --features="avif" test_avif_decoding: runs-on: ubuntu-latest steps: - name: install-dependencies run: sudo apt update && sudo apt install ninja-build meson nasm - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable - name: build run: cargo build -v --no-default-features --features="avif-decoder" env: SYSTEM_DEPS_DAV1D_BUILD_INTERNAL: always clippy: runs-on: ubuntu-latest steps: - name: install-dependencies run: sudo apt update && sudo apt install ninja-build meson nasm - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable with: components: clippy - run: cargo clippy --all-features -- -D warnings env: SYSTEM_DEPS_DAV1D_BUILD_INTERNAL: always build_fuzz_afl: name: "Fuzz targets (afl)" runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@nightly - name: install-deps run: sudo apt-get -y install clang llvm - name: build run: | cargo install afl cd fuzz-afl cargo check --bin reproduce_webp cargo check --bin reproduce_pnm cargo afl check --bin fuzz_webp cargo afl check --bin fuzz_pnm env: RUSTFLAGS: "" build_fuzz_cargo-fuzz: name: "Fuzz targets (cargo-fuzz)" runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@nightly - name: build run: | cargo install cargo-fuzz cargo fuzz build - name: fuzz run: | for format in $(cargo fuzz list); do cargo fuzz run "$format" -- -runs=0; done public_private_dependencies: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@nightly - name: build run: | mv ./Cargo.toml.public-private-dependencies ./Cargo.toml echo "#![deny(exported_private_dependencies)]" | cat - src/lib.rs > src/lib.rs.0 mv src/lib.rs.0 src/lib.rs cargo check build_benchmarks: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@nightly - name: build run: cargo build -v --benches --features=benchmarks rustfmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - name: Run rustfmt check run: cargo fmt -- --check cargo-deny: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: EmbarkStudios/cargo-deny-action@v1 verify_msrv: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install or use cached `cargo-msrv` uses: baptiste0928/cargo-install@v1 with: crate: cargo-msrv - name: Install MSRV Cargo.lock run: mv Cargo.lock.msrv Cargo.lock - name: Verify Minimum Rust Version run: cargo-msrv verify image-0.24.7/.gitignore000064400000000000000000000002621046102023000127700ustar 00000000000000.DS_Store *~ *# *.o *.so *.swp *.dylib *.dSYM *.dll *.rlib *.dummy *.exe *-test /bin/main /bin/test-internal /bin/test-external /doc/ /target /build/ /.rust/ rusti.sh Cargo.lock image-0.24.7/CHANGES.md000064400000000000000000000627441046102023000124070ustar 00000000000000# Release Notes ## Known issues - Many decoders will panic on malicous input. In most cases, this is caused by not enforcing memory limits, though other panics have been seen from fuzzing. - The color space information of pixels is not clearly communicated. ## Changes ### Unreleased - More convenient to use buffers will be added in the future. In particular, improving initialization, passing of output buffers, and adding a more complete representation for layouts. The plan is for these to interact with the rest of the library through a byte-based interface similar to `ImageDecoder`. See ongoing work on [`image-canvas`](https://github.com/image-rs/canvas) if you want to participate. ### Version 0.24.7 New features: - Added `{ImageBuffer, DynamicImage}::write_with_encoder` to simplify writing images with custom settings. - Expose ICC profiles stored in tiff and wepb files. - Added option to set the background color of animated webp images. - New methods for sampling and interpolation of `GenericImageView`s Bug fixes: - Fix panic on empty dxt. - Fix several panics in webp decoder. - Allow unknown chunks at the end of webp files. ### Version 0.24.6 - Add support for QOI. - ImageDecoders now expose ICC profiles on supported formats. - Add support for BMPs without a file header. - Improved AVIF encoder. - WebP decoding fixes. ### Version 0.24.5 Structural changes: - Increased the minimum supported Rust version (MSRV) to 1.61. - Increased the version requirement for the `tiff` crate to 0.8.0. - Increased the version requirement for the `jpeg` crate to 0.3.0. Bug fixes: - The `as_rgb32f` function of `DynamicImage` is now correctly documented. - Fixed a crash when decoding ICO images. Added a regression test. - Fixed a panic when transforming webp images. Added a regression test. - Added a check to prevent integer overflow when calculating file size for BMP images. The missing check could panic in debug mode or else set an incorrect file size in release mode. - Upgraded the PNG image encoder to use the newer `PngEncoder::write_image` instead of the deprecated `PngEncoder::encode` which did not account for byte order and could result in images with incorrect colors. - Fixed `InsufficientMemory` error when trying to decode a PNG image. - Fix warnings and CI issues. - Typos and links in the documentation have been corrected. Performance: - Added check for dynamic image dimensions before resizing. This improves performance in cases where the image does not need to be resized or has already been resized. ### Version 0.24.4 New Features: - Encoding for `webp` is now available with the native library. This needs to be activate explicitly with the `web-encoder` feature. - `exr` decoding has gained basic limit support. Bug fixes: - The `Iterator::size_hint` implementation of pixel iterators has been fixed to return the current length indicated by its `ExactSizeIterator` hint. - Typos and bad references in the documentation have been removed. Performance: - `ImageBuffer::get_pixel{,_mut}` is now marked inline. - `resize` now short-circuits when image dimensions are unchanged. ### Version 0.24.3 New Features: - `TiffDecoder` now supports setting resource limits. Bug fixes: - Fix compile issues on little endian systems. - Various panics discovered by fuzzing. ### Version 0.24.2 Structural changes: - CI now runs `cargo-deny`, checking dependent crates to an OSS license list and against RUSTSEC advisories. New Features: - The WebP decoder recognizes and decodes images with `VP8X` header. - The DDS decoder recognizes and decodes images with `DX10` headers. Bug fixes: - Calling `DynamicImage`/`ImageBuffer`'s methods `write_to` and `save` will now work properly even if the backing container is larger than the image layout requires. Only the relevant slice of pixel data is passed to the encoder. - Fixed a OOM-panic caused by malformed images in the `gif` decoder. ### Version 0.24.1 Bug Fixes: - ImageBuffer::get_pixel_checked would sometimes return the incorrect pixel. - PNG encoding would sometimes not recognize unsupported color. ### Version 0.24.0 Breaking changes Structural changes: - Minimum Rust version is now `1.56` and may change in minor versions until further notice. It is now tracked in the library's `Cargo.toml`, instead, by the standard `[package.rust-version]` field. Note: this applies _to the library itself_. You may need different version resolutions for dependencies when using a non-stable version of Rust. - The `math::utils::{nq, utils}` modules have been removed. These are better served through the `color_quant` crate and the standard library respectively. - All codecs are now available through `image::codecs`, no longer top-level. - `ExtendedColorType` and `DynamicImage` have been made `#[non_exhaustive]`, providing more methods instead of exhaustive matching. - Reading images through the generic `io::Reader`, as well as generic convenience interfaces, now requires the underlying reader to be `BufRead + Seek`. This allows more efficient support more formats. Similarly, writing now requires writers to be `Write + Seek`. - The `Bgra*` variants of buffers, which were only half-supported, have been removed. The owning buffer types `ImageBuffer` and `DynamicImage` fundamentally already make a choice in supported pixel representations. This allows for more consistent internal behavior. Callers are expected to convert formats when using those buffers, which they are required to do in any case already, and which is routinely performed by decoders. Trait reworks: - The `Pixel` trait is no longer implemented quite as liberally for structs defined in the crate. Instead, it is now restricted to a set of known channel which ensures accuracy in computations involving those channels. - The `ImageDecoderExt` trait has been renamed to `ImageDecoderRect`, according to its actual functionality. - The `Pixel` trait and its `Subpixel` field no longer require (or provide) a `'static` lifetime bound. - The `Pixel` trait no longer requires specifying an associated, constant `ColorType`. This was of little relevance to computation but made it much harder to implement and extend correctly. Instead, the _private_ `PixelWithColorType` extension is added for interfaces that require a properly known variant. - Reworked how `SubImage` interacts with the `GenericImage` trait. It is now a default implementation. Note that `SubImage` now has _inherent_ methods that avoid double-indirection, the trait's method will no longer avoid this. - The `Primitive` trait now requires implementations to provide a minimum and maximum logical bound for the purpose of converting to other primitive representations. Additions Image formats: - Reading lossless WebP is now supported. - The OpenEXR format is now supported. - The `jpeg` decoder has been upgraded to Lossless JPEG. - The `AvifEncoder` now correctly handles alpha-less images. Some additional color formats are converted to RGBA as well. - The `Bmp` codec now decodes more valid images. It can decode a raw image without performing the palette mapping. It provides a method to access the palette. The encoder provides the inverse capabilities. - `Tiff` is now an output format. Buffers and Operations: - The channel / primitive type `f32` is now supported. Currently only the OpenEXR codec makes full use of it but this is expected to change. - `ImageBuffer::{get_pixel_checked, get_pixel_mut_checked}` provide panic-free access to pixels and channels by returning `Option<&P>` and `Option<&mut P>`. - `ImageBuffer::write_to` has been added, encoding the buffer to a writer. This method already existed on `DynamicImage`. - `DynamicImage` now implements `From<_>` for all supported buffer types. - `DynamicImage` now implements `Default`, an empty `Rgba8` image. - `imageops::overlay` now takes coordinates as `i64`. Limits: - Added `Limits` and `LimitSupport`, utilized in `io::Reader`. These can be configured for rudimentary protection against resource exhaustion (images pretending to require a very large buffer). These types are not yet exhaustive by design, and more and stricter limits may be added in the future. - Encoders that do provide inherent support for limits, or reserve a significant amount of internal memory, are urged to implement the `set_limits` extension to `ImageDecoder`. Some strict limit are opt-in, which may cause decoding to fail if not supported. Miscellaneous: - `PNMSubtype` has been renamed to `PnmSubtype`, by Rust's naming scheme. - Several incorrectly capitalized `PNM*` aliases have been removed. - Several `enum` types that had previously used a hidden variant now use the official `#[non_exhaustive]` attribute instead. ### Version 0.23.14 - Unified gif blending in different decode methods, fixing out-of-bounds checks in a number of weirdly positioned frames. - Hardened TGA decoder against a number of malicious inputs. - Fix forward incompatible usage of the panic macro. - Fix load_rect for gif reaching `unreachable!()` code. - Added `ExtendedColorType::A8`. - Allow TGA to load alpha-only images. - Optimized load_rect to avoid unnecessary seeks. ### Version 0.23.13 - Fix an inconsistency in supported formats of different methods for encoding an image. - Fix `thumbnail` choosing an empty image. It now always prefer non-empty image dimensions. - Fix integer overflow in calculating requires bytes for decoded image buffers for farbfeld, hdr, and pnm decoders. These will now error early. - Fix a panic decoding certain `jpeg` image without frames or meta data. - Optimized the `jpeg` encoder. - Optimized `GenericImage::copy_from` default impl in various cases. - Add `avif` decoders. You must enable it explicitly and it is not covered by our usual MSRV policy of Rust 1.34. Instead, only latest stable is supported. - Add `ImageFormat::{can_read, can_write}` - Add `Frame::buffer_mut` - Add speed and quality options on `avif` encoder. - Add speed parameter to `gif` encoder. - Expose control over sequence repeat to the `gif` encoder. - Add `{contrast,brighten,huerotate}_in_place` functions in imageproc. - Relax `Default` impl of `ImageBuffer`, removing the bound on the color type. - Derive Debug, Hash, PartialEq, Eq for DynamicImage ### Version 0.23.12 - Fix a soundness issue affecting the impls of `Pixel::from_slice_mut`. This would previously reborrow the mutable input reference as a shared one but then proceed to construct the mutable result reference from it. While UB according to Rust's memory model, we're fairly certain that no miscompilation can happen with the LLVM codegen in practice. See 5cbe1e6767d11aff3f14c7ad69a06b04e8d583c7 for more details. - Fix `imageops::blur` panicking when `sigma = 0.0`. It now defaults to `1.0` as all negative values. - Fix re-exporting `png::{CompressionType, FilterType}` to maintain SemVer compatibility with the `0.23` releases. - Add `ImageFormat::from_extension` - Add copyless DynamicImage to byte slice/vec conversion. - Add bit-depth specific `into_` and `to_` DynamicImage conversion methods. ### Version 0.23.11 - The `NeuQuant` implementation is now supplied by `color_quant`. Use of the type defined by this library is discouraged. - The `jpeg` decoder can now downscale images that are decoded by 1,2,4,8. - Optimized the jpeg encoding ~5-15%. - Deprecated the `clamp` function. Use `num-traits` instead. - The ICO decoder now accepts an empty mask. - Fixed an overflow in ICO mask decoding potentially leading to panic. - Added `ImageOutputFormat` for `AVIF` - Updated `tiff` to `0.6` with lzw performance improvements. ### Version 0.23.10 - Added AVIF encoding capabilities using the `ravif` crate. Please note that the feature targets the latest stable compiler and is not enabled by default. - Added `ImageBuffer::as_raw` to inspect the underlying container. - Updated `gif` to `0.11` with large performance improvements. ### Version 0.23.9 - Introduced correctly capitalized aliases for some scream case types - Introduced `imageops::{vertical_gradient, horizontal_gradient}` for writing simple color gradients into an image. - Sped up methods iterating over `Pixels`, `PixelsMut`, etc. by using exact chunks internally. This should auto-vectorize `ImageBuffer::from_pixel`. - Adjusted `Clone` impls of iterators to not require a bound on the pixel. - Add `Debug` impls for iterators where the pixel's channel implements it. - Add comparison impls for `FilterType` ### Version 0.23.8 - `flat::Error` now implements the standard `Error` trait - The type parameter of `Map` has been relaxed to `?Sized` - Added the `imageops::tile` function that repeats one image across another ### Version 0.23.7 - Iterators over immutable pixels of `ImageBuffer` can now be cloned - Added a `tga` encoder - Added `ColorMap::lookup`, an optional reversal of the map - The `EncodableLayout` trait is now exported ### Version 0.23.6 - Added `png::ApngDecoder`, an adapter decoding the animation in an APNG. - Fixed a bug in `jpeg` encoding that would darken output colors. - Added a utility constructor `FlatSamples::with_monocolor`. - Added `ImageBuffer::as_flat_samples_mut` which is a mutable variant of the existing ffi-helper `ImageBuffer::as_flat_samples`. ### Version 0.23.5 - The `png` encoder now allows configuring compression and filter type. The output is not part of stability guarantees, see its documentation. - The `jpeg` encoder now accepts any implementor of `GenericImageView`. This allows images that are only partially present in memory to be encoded. - `ImageBuffer` now derives `Hash`, `PartialEq`, `Eq`. - The `Pixels`/`PixelsMut` iterator no longer yields out-of-bounds pixels when the underlying buffer is larger than required. - The `pbm` decoder correctly decodes ascii data again, fixing a regression where it would use the sample value `1` as white instead of `255`. - Fix encoding of RGBA data in `gif` frames. - Constructing a `Rows`/`RowsMut` iterator no longer panics when the image has a width or height of `0`. ### Version 0.23.4 - Improved the performance of decoding animated gifs - Added `crop_imm` which functions like `crop` but on a shared reference - The gif `DisposalMethod::Any` is treated as `Keep`, consistent with browsers - Most errors no longer allocate a string, instead implement Display. - Add some implementations of `Error::source` ### Version 0.23.3 - Added `ColorType::has_alpha` to facilitate lossless conversion - Recognize extended WebP formats for decoding - Added decoding and encoding for the `farbfeld` format - Export named iterator types created from various `ImageBuffer` methods - Error in jpeg encoder for images larger than 65536 pixels, fixes panic ### Version 0.23.2 - The dependency on `jpeg-decoder` now reflects minimum requirements. ### Version 0.23.1 - Fix cmyk_to_rgb (jpeg) causing off by one rounding errors. - A number of performance improvements for jpeg (encode and decode), bmp, vp8 - Added more details to errors for many formats ### Version 0.23.0 This major release intends to improve the interface with regards to handling of color format data and errors for both decoding and encoding. This necessitated many breaking changes anyways so it was used to improve the compliance to the interface guidelines such as outstanding renaming. It is not yet perfect with regards to color spaces but it was designed mainly as an improvement over the current interface with regards to in-memory color formats, first. We'll get to color spaces in a later major version. - Heavily reworked `ColorType`: - This type is now used for denoting formats for which we support operations on buffers in these memory representations. Particularly, all channels in pixel types are assumed to be an integer number of bytes (In terms of the Rust type system, these are `Sized` and one can crate slices of channel values). - An `ExtendedColorType` is used to express more generic color formats for which the library has limited support but can be converted/scaled/mapped into a `ColorType` buffer. This operation might be fallible but, for example, includes sources with 1/2/4-bit components. - Both types are non-exhaustive to add more formats in a minor release. - A work-in-progress (#1085) will further separate the color model from the specific channel instantiation, e.g. both `8-bit RGB` and `16-bit BGR` are instantiations of `RGB` color model. - Heavily rework `ImageError`: - The top-level enum type now serves to differentiate cause with multiple opaque representations for the actual error. These are no longer simple Strings but contains useful types. Third-party decoders that have no variant in `ImageFormat` have also been considered. - Support for `Error::source` that can be downcast to an error from a matching version of the underlying decoders. Note that the version is not part of the stable interface guarantees, this should not be relied upon for correctness and only be used as an optimization. - Added image format indications to errors. - The error values produced by decoder will be upgraded incrementally. See something that still produces plain old String messages? Feel free to send a PR. - Reworked the `ImageDecoder` trait: - `read_image` takes an output buffer argument instead of allocating all memory on its own. - The return type of `dimensions` now aligns with `GenericImage` sizes. - The `colortype` method was renamed to `color_type` for conformity. - The enums `ColorType`, `DynamicImage`, `imageops::FilterType`, `ImageFormat` no longer re-export all of their variants in the top-level of the crate. This removes the growing pollution in the documentation and usage. You can still insert the equivalent statement on your own: `use image::ImageFormat::{self, *};` - The result of `encode` operations is now uniformly an `ImageResult<()>`. - Removed public converters from some `tiff`, `png`, `gif`, `jpeg` types, mainly such as error conversion. This allows upgrading the dependency across major versions without a major release in `image` itself. - On that note, the public interface of `gif` encoder no longer takes a `gif::Frame` but rather deals with `image::Frame` only. If you require to specify the disposal method, transparency, etc. then you may want to wait with upgrading but (see next change). - The `gif` encoder now errors on invalid dimensions or unsupported color formats. It would previously silently reinterpret bytes as RGB/RGBA. - The capitalization of `ImageFormat` and other enum variants has been adjusted to adhere to the API guidelines. These variants are now spelled `Gif`, `Png`, etc. The same change has been made to the name of types such as `HDRDecoder`. - The `Progress` type has finally received public accessor method. Strange that no one reported them missing. - Introduced `PixelDensity` and `PixelDensityUnit` to store DPI information in formats that support encoding this form of meta data (e.g. in `jpeg`). ### Version 0.22.5 - Added `GenericImage::copy_within`, specialized for `ImageBuffer` - Fixed decoding of interlaced `gif` files - Prepare for future compatibility of array `IntoIterator` in example code ### Version 0.22.4 - Added in-place variants for flip and rotate operations. - The bmp encoder now checks if dimensions are valid for the format. It would previously write a subset or panic. - Removed deprecated implementations of `Error::description` - Added `DynamicImage::into_*` which convert without an additional allocation. - The PNG encoder errors on unsupported color types where it had previously silently swapped color channels. - Enabled saving images as `gif` with `save_buffer`. ### Version 0.22.3 - Added a new module `io` containing a configurable `Reader`. It can replace the bunch of free functions: `image::{load_*, open, image_dimensions}` while enabling new combinations such as `open` but with format deduced from content instead of file path. - Fixed `const_err` lint in the macro expanded implementations of `Pixel`. This can only affect your crate if `image` is used as a path dependency. ### Version 0.22.2 - Undeprecate `unsafe` trait accessors. Further evaluation showed that their deprecation should be delayed until trait `impl` specialization is available. - Fixed magic bytes used to detect `tiff` images. - Added `DynamicImage::from_decoder`. - Fixed a bug in the `PNGReader` that caused an infinite loop. - Added `ColorType::{bits_per_pixel, num_components}`. - Added `ImageFormat::from_path`, same format deduction as the `open` method. - Fixed a panic in the gif decoder. - Aligned background color handling of `gif` to web browser implementations. - Fixed handling of partial frames in animated `gif`. - Removed unused direct `lzw` dependency, an indirect dependency in `tiff`. ### Version 0.22.1 - Fixed build without no features enabled ### Version 0.22 - The required Rust version is now `1.34.2`. - Note the website and blog: [image-rs.org][1] and [blog.image-rs.org][2] - `PixelMut` now only on `ImageBuffer` and removed from `GenericImage` interface. Prefer iterating manually in the generic case. - Replaced an unsafe interface in the hdr decoder with a safe variant. - Support loading 2-bit BMP images - Add method to save an `ImageBuffer`/`DynamicImage` with specified format - Update tiff to `0.3` with a writer - Update png to `0.15`, fixes reading of interlaced sub-byte pixels - Always use custom struct for `ImageDecoder::Reader` - Added `apply_without_alpha` and `map_without_alpha` to `Pixel` trait - Pixel information now with associated constants instead of static methods - Changed color structs to tuple types with single component. Improves ergonomics of destructuring assignment and construction. - Add lifetime parameter on `ImageDecoder` trait. - Remove unnecessary `'static` bounds on affine operations - Add function to retrieve image dimensions without loading full image - Allow different image types in overlay and replace - Iterators over rows of `ImageBuffer`, mutable variants [1]: https://www.image-rs.org [2]: https://blog.image-rs.org ### Version 0.21.2 - Fixed a variety of crashes and opaque errors in webp - Updated the png limits to be less restrictive - Reworked even more `unsafe` operations into safe alternatives - Derived Debug on FilterType and Deref on Pixel - Removed a restriction on DXT to always require power of two dimensions - Change the encoding of RGBA in bmp using bitfields - Corrected various urls ### Version 0.21.1 - A fairly important bugfix backport - Fixed a potentially memory safety issue in the hdr and tiff decoders, see #885 - See [the full advisory](docs/2019-04-23-memory-unsafety.md) for an analysis - Fixes `ImageBuffer` index calculation for very, very large images - Fix some crashes while parsing specific incomplete pnm images - Added comprehensive fuzzing for the pam image types ### Version 0.21 - Updated README to use `GenericImageView` - Removed outdated version number from CHANGES - Compiles now with wasm-unknown-emscripten target - Restructured `ImageDecoder` trait - Updated README with a more colorful example for the Julia fractal - Use Rust 1.24.1 as minimum supported version - Support for loading GIF frames one at a time with `animation::Frames` - The TGA decoder now recognizes 32 bpp as RGBA(8) - Fixed `to_bgra` document comment - Added release test script - Removed unsafe code blocks several places - Fixed overlay overflow bug issues with documented proofs ### Version 0.20 - Clippy lint pass - Updated num-rational dependency - Added BGRA and BGR color types - Improved performance of image resizing - Improved PBM decoding - PNM P4 decoding now returns bits instead of bytes - Fixed move of overlapping buffers in BMP decoder - Fixed some document comments - `GenericImage` and `GenericImageView` is now object-safe - Moved TIFF code to its own library - Fixed README examples - Fixed ordering of interpolated parameters in TIFF decode error string - Thumbnail now handles upscaling - GIF encoding for multiple frames - Improved subimages API - Cargo fmt fixes ### Version 0.19 - Fixed panic when blending with alpha zero. - Made `save` consistent. - Consistent size calculation. - Fixed bug in `apply_with_alpha`. - Implemented `TGADecoder::read_scanline`. - Use deprecated attribute for `pixels_mut`. - Fixed bug in JPEG grayscale encoding. - Fixed multi image TIFF. - PNM encoder. - Added `#[derive(Hash)]` for `ColorType`. - Use `num-derive` for `#[derive(FromPrimitive)]`. - Added `into_frames` implementation for GIF. - Made rayon an optional dependency. - Fixed issue where resizing image did not give exact width/height. - Improved downscale. - Added a way to expose options when saving files. - Fixed some compiler warnings. - Switched to lzw crate instead of using built-in version. - Added `ExactSizeIterator` implementations to buffer structs. - Added `resize_to_fill` method. - DXT encoding support. - Applied clippy suggestions. ### Version 0.4 - Various improvements. - Additional supported image formats (BMP and ICO). - GIF and PNG codec moved into separate crates. ### Version 0.3 - Replace `std::old_io` with `std::io`. ### Version 0.2 - Support for interlaced PNG images. - Writing support for GIF images (full color and paletted). - Color quantizer that converts 32bit images to paletted including the alpha channel. - Initial support for reading TGA images. - Reading support for TIFF images (packbits and FAX compression not supported). - Various bug fixes and improvements. ### Version 0.1 - Initial release - Basic reading support for png, jpeg, gif, ppm and webp. - Basic writing support for png and jpeg. - A collection of basic imaging processing function like `blur` or `invert` image-0.24.7/Cargo.lock0000644000001646200000000000100101740ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", "once_cell", "version_check", ] [[package]] name = "aho-corasick" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "ansi_term" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ "winapi", ] [[package]] name = "anyhow" version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "arbitrary" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db55d72333851e17d572bec876e390cd3b11eb1ef53ae821dd9f3b653d2b4569" [[package]] name = "arg_enum_proc_macro" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c29b43ee8654590587cd033b3eca2f9c4f8cdff945ec0e6ee91ceb057d87f3" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" dependencies = [ "serde", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi 0.1.19", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "av-metrics" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13638b394190295622c0d2493d0c8c39210b92c2110895bfb14c58db213c2b39" dependencies = [ "crossbeam", "itertools", "lab", "num-traits", "rayon", "thiserror", "v_frame", ] [[package]] name = "av1-grain" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f6ca6f0c18c02c2fbfc119df551b8aeb8a385f6d5980f1475ba0255f1e97f1e" dependencies = [ "anyhow", "arrayvec", "itertools", "log", "nom", "num-rational", "serde", "v_frame", ] [[package]] name = "avif-serialize" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" dependencies = [ "arrayvec", ] [[package]] name = "bindgen" version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" dependencies = [ "bitflags 1.3.2", "cexpr", "clang-sys", "clap 2.34.0", "env_logger 0.9.3", "lazy_static", "lazycell", "log", "peeking_take_while", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "which", ] [[package]] name = "bit_field" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" [[package]] name = "bitreader" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f10043e4864d975e7f197f993ec4018636ad93946724b2571c4474d51845869b" dependencies = [ "cfg-if", ] [[package]] name = "bitstream-io" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82704769cb85a22df2c54d6bdd6a158b7931d256cf3248a07d6ecbe9d58b31d7" [[package]] name = "built" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b9c056b9ed43aee5e064b683aa1ec783e19c6acec7559e3ae931b7490472fbe" dependencies = [ "cargo-lock", "git2", ] [[package]] name = "bumpalo" version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytemuck" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cargo-lock" version = "8.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "031718ddb8f78aa5def78a09e90defe30151d1f6c672f937af4dd916429ed996" dependencies = [ "semver", "serde", "toml 0.5.11", "url", ] [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0" dependencies = [ "jobserver", "libc", ] [[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom", ] [[package]] name = "cfg-expr" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b40ccee03b5175c18cde8f37e7d2a33bcef6f8ec8f7cc0d81090d1bb380949c9" dependencies = [ "smallvec", "target-lexicon", ] [[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.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" [[package]] name = "ciborium-ll" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", "half 1.8.2", ] [[package]] name = "clang-sys" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", "libloading", ] [[package]] name = "clap" version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", "bitflags 1.3.2", "strsim", "textwrap 0.11.0", "unicode-width", "vec_map", ] [[package]] name = "clap" version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "bitflags 1.3.2", "clap_lex 0.2.4", "indexmap 1.9.3", "textwrap 0.16.0", ] [[package]] name = "clap" version = "4.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" dependencies = [ "bitflags 1.3.2", "clap_derive", "clap_lex 0.3.3", "is-terminal", "once_cell", "termcolor", "terminal_size", ] [[package]] name = "clap_complete" version = "4.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10861370d2ba66b0f5989f83ebf35db6421713fd92351790e7fdd6c36774c56b" dependencies = [ "clap 4.0.32", ] [[package]] name = "clap_derive" version = "4.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" 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 = "clap_lex" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646" dependencies = [ "os_str_bytes", ] [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "console" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", "windows-sys 0.45.0", ] [[package]] name = "crc32fast" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 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 3.2.25", "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" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" dependencies = [ "cfg-if", "crossbeam-channel", "crossbeam-deque", "crossbeam-epoch", "crossbeam-queue", "crossbeam-utils", ] [[package]] name = "crossbeam-channel" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-queue" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] [[package]] name = "crunchy" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "dav1d" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7284148338177cb1cd0d0cdd7bf26440f8326999063eed294aa7d77b46a7e263" dependencies = [ "dav1d-sys", ] [[package]] name = "dav1d-sys" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88e40c4c77d141a3b70113ee45a1502b9c80e24f176958d39a8361abcf30c883" dependencies = [ "bindgen", "system-deps", ] [[package]] name = "dcv-color-primitives" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1457f4dd8395fef9f61996b5783b82ed7b234b4b55e1843d04e07fded0538005" dependencies = [ "paste", "wasm-bindgen", ] [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "env_logger" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime", "log", "regex", "termcolor", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", "windows-sys 0.48.0", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "exr" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1e481eb11a482815d3e9d618db8c42a93207134662873809335a92327440c18" dependencies = [ "bit_field", "flume", "half 2.2.1", "lebe", "miniz_oxide", "rayon-core", "smallvec", "zune-inflate", ] [[package]] name = "fallible_collections" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a88c69768c0a15262df21899142bc6df9b9b823546d4b4b9a7bc2d6c448ec6fd" dependencies = [ "hashbrown 0.13.2", ] [[package]] name = "fdeflate" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" dependencies = [ "simd-adler32", ] [[package]] name = "fern" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" dependencies = [ "log", ] [[package]] name = "flate2" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "flume" version = "0.10.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" dependencies = [ "futures-core", "futures-sink", "nanorand", "pin-project", "spin", ] [[package]] name = "form_urlencoded" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] [[package]] name = "futures-core" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-sink" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "getrandom" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "js-sys", "libc", "wasi", "wasm-bindgen", ] [[package]] name = "gif" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" dependencies = [ "color_quant", "weezl", ] [[package]] name = "git2" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1" dependencies = [ "bitflags 1.3.2", "libc", "libgit2-sys", "log", "url", ] [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "half" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" dependencies = [ "crunchy", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ "ahash", ] [[package]] name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" [[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 = "hermit-abi" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "idna" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "image" version = "0.24.7" dependencies = [ "bytemuck", "byteorder", "color_quant", "crc32fast", "criterion", "dav1d", "dcv-color-primitives", "exr", "gif", "glob", "jpeg-decoder", "mp4parse", "num-complex", "num-rational", "num-traits", "png", "qoi", "quickcheck", "ravif", "rgb", "tiff", "webp", ] [[package]] name = "imgref" version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cf49df1085dcfb171460e4592597b84abe50d900fb83efb6e41b20fefd6c2c" [[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", ] [[package]] name = "indexmap" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", "hashbrown 0.14.0", ] [[package]] name = "interpolate_name" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4b35f4a811037cfdcd44c5db40678464b2d5d248fc1abeeaaa125b370d47f17" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "io-lifetimes" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.2", "libc", "windows-sys 0.48.0", ] [[package]] name = "is-terminal" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.2", "rustix 0.38.6", "windows-sys 0.48.0", ] [[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.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "ivf" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fb01c64361a3a67b511439f0dcd54fa3aa5581c861a17e2ede76e46b9c5b7e2" dependencies = [ "bitstream-io", ] [[package]] name = "jobserver" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" dependencies = [ "libc", ] [[package]] name = "jpeg-decoder" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" dependencies = [ "rayon", ] [[package]] name = "js-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] name = "lab" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lazycell" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lebe" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libfuzzer-sys" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcf184a4b6b274f82a5df6b357da6055d3e82272327bba281c28bbba6f1664ef" dependencies = [ "arbitrary", "cc", ] [[package]] name = "libgit2-sys" version = "0.14.2+1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4" dependencies = [ "cc", "libc", "libz-sys", "pkg-config", ] [[package]] name = "libloading" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", ] [[package]] name = "libwebp-sys" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5df1e76f0acef0058aa2164ccf74e610e716e7f9eeb3ee2283de7d43659d823" dependencies = [ "cc", "glob", ] [[package]] name = "libz-sys" version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "lock_api" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "loop9" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a703804431e5927454bcaf2b2a162595e95db931130c2728c18d050090f69940" dependencies = [ "imgref", ] [[package]] name = "maybe-rayon" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ "cfg-if", "rayon", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", "simd-adler32", ] [[package]] name = "mp4parse" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63a35203d3c6ce92d5251c77520acb2e57108c88728695aa883f70023624c570" dependencies = [ "bitreader", "byteorder", "fallible_collections", "log", "num-traits", "static_assertions", ] [[package]] name = "nanorand" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ "getrandom", ] [[package]] name = "nasm-rs" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4d98d0065f4b1daf164b3eafb11974c94662e5e2396cf03f32d0bb5c17da51" dependencies = [ "rayon", ] [[package]] name = "new_debug_unreachable" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "noop_proc_macro" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "num-bigint" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-complex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" dependencies = [ "num-traits", ] [[package]] name = "num-derive" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "num-derive" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e" dependencies = [ "proc-macro2", "quote", "syn 2.0.28", ] [[package]] name = "num-integer" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", ] [[package]] name = "num-rational" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", "num-bigint", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi 0.3.2", "libc", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "oorandom" version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "os_str_bytes" version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "paste" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "peeking_take_while" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "percent-encoding" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", "syn 2.0.28", ] [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plotters" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] [[package]] name = "png" version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[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.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "qoi" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" dependencies = [ "bytemuck", ] [[package]] name = "quick-error" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quickcheck" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ "env_logger 0.8.4", "log", "rand", ] [[package]] name = "quote" version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" 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 = "rav1e" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16c383692a5e7abd9f6d1eddb1a5e0269f859392387883361bb09e5555852ec1" dependencies = [ "arbitrary", "arg_enum_proc_macro", "arrayvec", "av-metrics", "av1-grain", "bitstream-io", "built", "cc", "cfg-if", "clap 4.0.32", "clap_complete", "console", "fern", "interpolate_name", "itertools", "ivf", "libc", "libfuzzer-sys", "log", "maybe-rayon", "nasm-rs", "new_debug_unreachable", "nom", "noop_proc_macro", "num-derive 0.3.3", "num-traits", "once_cell", "paste", "rand", "rand_chacha", "rust_hawktracer", "rustc_version", "scan_fmt", "signal-hook", "simd_helpers", "system-deps", "thiserror", "v_frame", "wasm-bindgen", "y4m", ] [[package]] name = "ravif" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cd36aa2bc280b60619e0c4386a73ff2ac343551dcf400168562ce08cc0c32e0" dependencies = [ "avif-serialize", "imgref", "loop9", "quick-error", "rav1e", "rayon", "rgb", ] [[package]] name = "rayon" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", "num_cpus", ] [[package]] name = "regex" version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "rgb" version = "0.8.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" dependencies = [ "bytemuck", ] [[package]] name = "rust_hawktracer" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3480a29b927f66c6e06527be7f49ef4d291a01d694ec1fe85b0de71d6b02ac1" dependencies = [ "rust_hawktracer_normal_macro", "rust_hawktracer_proc_macro", ] [[package]] name = "rust_hawktracer_normal_macro" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a570059949e1dcdc6f35228fa389f54c2c84dfe0c94c05022baacd56eacd2e9" [[package]] name = "rust_hawktracer_proc_macro" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb626abdbed5e93f031baae60d72032f56bc964e11ac2ff65f2ba3ed98d6d3e1" [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ "semver", ] [[package]] name = "rustix" version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ "bitflags 1.3.2", "errno", "io-lifetimes", "libc", "linux-raw-sys 0.3.8", "windows-sys 0.48.0", ] [[package]] name = "rustix" version = "0.38.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ee020b1716f0a80e2ace9b03441a749e402e86712f15f16fe8a8f75afac732f" dependencies = [ "bitflags 2.3.3", "errno", "libc", "linux-raw-sys 0.4.5", "windows-sys 0.48.0", ] [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[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 = "scan_fmt" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b53b0a5db882a8e2fdaae0a43f7b39e7e9082389e978398bdf223a55b581248" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" dependencies = [ "serde", ] [[package]] name = "serde" version = "1.0.181" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d3e73c93c3240c0bda063c239298e633114c69a888c3e37ca8bb33f343e9890" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.181" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be02f6cb0cd3a5ec20bbcfbcbd749f57daddb1a0882dc2e46a6c236c90b977ed" dependencies = [ "proc-macro2", "quote", "syn 2.0.28", ] [[package]] name = "serde_json" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" dependencies = [ "serde", ] [[package]] name = "shlex" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-registry" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simd_helpers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" dependencies = [ "quote", ] [[package]] name = "smallvec" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ "lock_api", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[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.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "system-deps" version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" dependencies = [ "cfg-expr", "heck", "pkg-config", "toml 0.7.6", "version-compare", ] [[package]] name = "target-lexicon" version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "termcolor" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "terminal_size" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ "rustix 0.37.23", "windows-sys 0.48.0", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "unicode-width", ] [[package]] name = "textwrap" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", "syn 2.0.28", ] [[package]] name = "tiff" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" dependencies = [ "flate2", "jpeg-decoder", "weezl", ] [[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 = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] [[package]] name = "toml" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ "indexmap 2.0.0", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "unicode-bidi" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "url" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "v_frame" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85db69f33d00031c1b07f7292e56317d5aa9475bdbd3d27ef18f3633438a697e" dependencies = [ "cfg-if", "noop_proc_macro", "num-derive 0.4.0", "num-traits", "rust_hawktracer", ] [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version-compare" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" 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.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 2.0.28", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", "syn 2.0.28", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "webp" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12ff0ebb440d1db63b778cb609db8a8abfda825a7841664a76a70b628502c7e1" dependencies = [ "libwebp-sys", ] [[package]] name = "weezl" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" [[package]] name = "which" version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" dependencies = [ "either", "libc", "once_cell", ] [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] [[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.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets 0.42.2", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.1", ] [[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", "windows_i686_gnu 0.48.0", "windows_i686_msvc 0.48.0", "windows_x86_64_gnu 0.48.0", "windows_x86_64_gnullvm 0.48.0", "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46aab759304e4d7b2075a9aecba26228bb073ee8c50db796b2c72c676b5d807" dependencies = [ "memchr", ] [[package]] name = "y4m" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" [[package]] name = "zune-inflate" version = "0.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] image-0.24.7/Cargo.lock.msrv000064400000000000000000001635271046102023000137110ustar 00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", "once_cell", "version_check", ] [[package]] name = "aho-corasick" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "ansi_term" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ "winapi", ] [[package]] name = "anyhow" version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "arbitrary" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db55d72333851e17d572bec876e390cd3b11eb1ef53ae821dd9f3b653d2b4569" [[package]] name = "arg_enum_proc_macro" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c29b43ee8654590587cd033b3eca2f9c4f8cdff945ec0e6ee91ceb057d87f3" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" dependencies = [ "serde", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi 0.1.19", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "av-metrics" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13638b394190295622c0d2493d0c8c39210b92c2110895bfb14c58db213c2b39" dependencies = [ "crossbeam", "itertools", "lab", "num-traits", "rayon", "thiserror", "v_frame", ] [[package]] name = "av1-grain" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f6ca6f0c18c02c2fbfc119df551b8aeb8a385f6d5980f1475ba0255f1e97f1e" dependencies = [ "anyhow", "arrayvec", "itertools", "log", "nom", "num-rational", "serde", "v_frame", ] [[package]] name = "avif-serialize" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" dependencies = [ "arrayvec", ] [[package]] name = "bindgen" version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" dependencies = [ "bitflags 1.3.2", "cexpr", "clang-sys", "clap 2.34.0", "env_logger 0.9.3", "lazy_static", "lazycell", "log", "peeking_take_while", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "which", ] [[package]] name = "bit_field" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" [[package]] name = "bitreader" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f10043e4864d975e7f197f993ec4018636ad93946724b2571c4474d51845869b" dependencies = [ "cfg-if", ] [[package]] name = "bitstream-io" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d28070975aaf4ef1fd0bd1f29b739c06c2cdd9972e090617fb6dca3b2cb564e" [[package]] name = "built" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b9c056b9ed43aee5e064b683aa1ec783e19c6acec7559e3ae931b7490472fbe" dependencies = [ "cargo-lock", "git2", ] [[package]] name = "bumpalo" version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytemuck" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cargo-lock" version = "8.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "031718ddb8f78aa5def78a09e90defe30151d1f6c672f937af4dd916429ed996" dependencies = [ "semver", "serde", "toml 0.5.11", "url", ] [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" dependencies = [ "jobserver", ] [[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom", ] [[package]] name = "cfg-expr" version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "215c0072ecc28f92eeb0eea38ba63ddfcb65c2828c46311d646f1a3ff5f9841c" dependencies = [ "smallvec", "target-lexicon", ] [[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.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" [[package]] name = "ciborium-ll" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", "half 1.8.2", ] [[package]] name = "clang-sys" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", "libloading", ] [[package]] name = "clap" version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", "bitflags 1.3.2", "strsim", "textwrap 0.11.0", "unicode-width", "vec_map", ] [[package]] name = "clap" version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "bitflags 1.3.2", "clap_lex 0.2.4", "indexmap 1.9.3", "textwrap 0.16.0", ] [[package]] name = "clap" version = "4.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" dependencies = [ "bitflags 1.3.2", "clap_derive", "clap_lex 0.3.3", "is-terminal", "once_cell", "termcolor", "terminal_size", ] [[package]] name = "clap_complete" version = "4.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10861370d2ba66b0f5989f83ebf35db6421713fd92351790e7fdd6c36774c56b" dependencies = [ "clap 4.0.32", ] [[package]] name = "clap_derive" version = "4.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" 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 = "clap_lex" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646" dependencies = [ "os_str_bytes", ] [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "console" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", "windows-sys 0.45.0", ] [[package]] name = "crc32fast" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 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 3.2.25", "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" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" dependencies = [ "cfg-if", "crossbeam-channel", "crossbeam-deque", "crossbeam-epoch", "crossbeam-queue", "crossbeam-utils", ] [[package]] name = "crossbeam-channel" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-queue" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] [[package]] name = "crunchy" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "dav1d" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7284148338177cb1cd0d0cdd7bf26440f8326999063eed294aa7d77b46a7e263" dependencies = [ "dav1d-sys", ] [[package]] name = "dav1d-sys" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88e40c4c77d141a3b70113ee45a1502b9c80e24f176958d39a8361abcf30c883" dependencies = [ "bindgen", "system-deps", ] [[package]] name = "dcv-color-primitives" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1457f4dd8395fef9f61996b5783b82ed7b234b4b55e1843d04e07fded0538005" dependencies = [ "paste", "wasm-bindgen", ] [[package]] name = "either" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "env_logger" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime", "log", "regex", "termcolor", ] [[package]] name = "equivalent" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" [[package]] name = "errno" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", "windows-sys 0.48.0", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "exr" version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279d3efcc55e19917fff7ab3ddd6c14afb6a90881a0078465196fe2f99d08c56" dependencies = [ "bit_field", "flume", "half 2.2.1", "lebe", "miniz_oxide", "rayon-core", "smallvec", "zune-inflate", ] [[package]] name = "fallible_collections" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "618bf220e692a59c50e7b281149f53c3fe93e0cf0b40c050fc2af8c9ecb28505" dependencies = [ "hashbrown 0.13.2", ] [[package]] name = "fdeflate" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" dependencies = [ "simd-adler32", ] [[package]] name = "fern" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" dependencies = [ "log", ] [[package]] name = "flate2" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "flume" version = "0.10.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" dependencies = [ "futures-core", "futures-sink", "nanorand", "pin-project", "spin", ] [[package]] name = "form_urlencoded" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] [[package]] name = "futures-core" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-sink" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "getrandom" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "js-sys", "libc", "wasi", "wasm-bindgen", ] [[package]] name = "gif" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" dependencies = [ "color_quant", "weezl", ] [[package]] name = "git2" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1" dependencies = [ "bitflags 1.3.2", "libc", "libgit2-sys", "log", "url", ] [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "half" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" dependencies = [ "crunchy", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ "ahash", ] [[package]] name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" [[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 = "hermit-abi" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "idna" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "image" version = "0.24.6" dependencies = [ "bytemuck", "byteorder", "color_quant", "crc32fast", "criterion", "dav1d", "dcv-color-primitives", "exr", "gif", "glob", "jpeg-decoder", "mp4parse", "num-complex", "num-rational", "num-traits", "png", "qoi", "quickcheck", "ravif", "rgb", "tiff", "webp", ] [[package]] name = "imgref" version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cf49df1085dcfb171460e4592597b84abe50d900fb83efb6e41b20fefd6c2c" [[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", ] [[package]] name = "indexmap" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", "hashbrown 0.14.0", ] [[package]] name = "interpolate_name" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4b35f4a811037cfdcd44c5db40678464b2d5d248fc1abeeaaa125b370d47f17" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "io-lifetimes" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", "windows-sys 0.48.0", ] [[package]] name = "is-terminal" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb" dependencies = [ "hermit-abi 0.3.1", "rustix 0.38.1", "windows-sys 0.48.0", ] [[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "ivf" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fb01c64361a3a67b511439f0dcd54fa3aa5581c861a17e2ede76e46b9c5b7e2" dependencies = [ "bitstream-io", ] [[package]] name = "jobserver" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" dependencies = [ "libc", ] [[package]] name = "jpeg-decoder" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" dependencies = [ "rayon", ] [[package]] name = "js-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] name = "lab" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lazycell" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lebe" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libfuzzer-sys" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcf184a4b6b274f82a5df6b357da6055d3e82272327bba281c28bbba6f1664ef" dependencies = [ "arbitrary", "cc", ] [[package]] name = "libgit2-sys" version = "0.14.2+1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4" dependencies = [ "cc", "libc", "libz-sys", "pkg-config", ] [[package]] name = "libloading" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", ] [[package]] name = "libwebp-sys" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439fd1885aa28937e7edcd68d2e793cb4a22f8733460d2519fbafd2b215672bf" dependencies = [ "cc", ] [[package]] name = "libz-sys" version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" [[package]] name = "lock_api" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "loop9" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a703804431e5927454bcaf2b2a162595e95db931130c2728c18d050090f69940" dependencies = [ "imgref", ] [[package]] name = "maybe-rayon" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ "cfg-if", "rayon", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", "simd-adler32", ] [[package]] name = "mp4parse" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63a35203d3c6ce92d5251c77520acb2e57108c88728695aa883f70023624c570" dependencies = [ "bitreader", "byteorder", "fallible_collections", "log", "num-traits", "static_assertions", ] [[package]] name = "nanorand" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ "getrandom", ] [[package]] name = "nasm-rs" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4d98d0065f4b1daf164b3eafb11974c94662e5e2396cf03f32d0bb5c17da51" dependencies = [ "rayon", ] [[package]] name = "new_debug_unreachable" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "noop_proc_macro" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "num-bigint" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-complex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" dependencies = [ "num-traits", ] [[package]] name = "num-derive" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "num-integer" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", ] [[package]] name = "num-rational" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", "num-bigint", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi 0.3.1", "libc", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "oorandom" version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "os_str_bytes" version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "paste" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "peeking_take_while" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "percent-encoding" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e138fdd8263907a2b0e1b4e80b7e58c721126479b6e6eedfb1b402acea7b9bd" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1fef411b303e3e12d534fb6e7852de82da56edd937d895125821fb7c09436c7" dependencies = [ "proc-macro2", "quote", "syn 2.0.22", ] [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plotters" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] [[package]] name = "png" version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[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.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" dependencies = [ "unicode-ident", ] [[package]] name = "qoi" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" dependencies = [ "bytemuck", ] [[package]] name = "quick-error" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quickcheck" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ "env_logger 0.8.4", "log", "rand", ] [[package]] name = "quote" version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" 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 = "rav1e" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16c383692a5e7abd9f6d1eddb1a5e0269f859392387883361bb09e5555852ec1" dependencies = [ "arbitrary", "arg_enum_proc_macro", "arrayvec", "av-metrics", "av1-grain", "bitstream-io", "built", "cc", "cfg-if", "clap 4.0.32", "clap_complete", "console", "fern", "interpolate_name", "itertools", "ivf", "libc", "libfuzzer-sys", "log", "maybe-rayon", "nasm-rs", "new_debug_unreachable", "nom", "noop_proc_macro", "num-derive", "num-traits", "once_cell", "paste", "rand", "rand_chacha", "rust_hawktracer", "rustc_version", "scan_fmt", "signal-hook", "simd_helpers", "system-deps", "thiserror", "v_frame", "wasm-bindgen", "y4m", ] [[package]] name = "ravif" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cd36aa2bc280b60619e0c4386a73ff2ac343551dcf400168562ce08cc0c32e0" dependencies = [ "avif-serialize", "imgref", "loop9", "quick-error", "rav1e", "rayon", "rgb", ] [[package]] name = "rayon" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", "num_cpus", ] [[package]] name = "regex" version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "rgb" version = "0.8.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" dependencies = [ "bytemuck", ] [[package]] name = "rust_hawktracer" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3480a29b927f66c6e06527be7f49ef4d291a01d694ec1fe85b0de71d6b02ac1" dependencies = [ "rust_hawktracer_normal_macro", "rust_hawktracer_proc_macro", ] [[package]] name = "rust_hawktracer_normal_macro" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a570059949e1dcdc6f35228fa389f54c2c84dfe0c94c05022baacd56eacd2e9" [[package]] name = "rust_hawktracer_proc_macro" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb626abdbed5e93f031baae60d72032f56bc964e11ac2ff65f2ba3ed98d6d3e1" [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ "semver", ] [[package]] name = "rustix" version = "0.37.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f25693a73057a1b4cb56179dd3c7ea21a7c6c5ee7d85781f5749b46f34b79c" dependencies = [ "bitflags 1.3.2", "errno", "io-lifetimes", "libc", "linux-raw-sys 0.3.8", "windows-sys 0.48.0", ] [[package]] name = "rustix" version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbc6396159432b5c8490d4e301d8c705f61860b8b6c863bf79942ce5401968f3" dependencies = [ "bitflags 2.3.3", "errno", "libc", "linux-raw-sys 0.4.3", "windows-sys 0.48.0", ] [[package]] name = "ryu" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[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 = "scan_fmt" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b53b0a5db882a8e2fdaae0a43f7b39e7e9082389e978398bdf223a55b581248" [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" dependencies = [ "serde", ] [[package]] name = "serde" version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", "syn 2.0.22", ] [[package]] name = "serde_json" version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" dependencies = [ "serde", ] [[package]] name = "shlex" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-registry" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "simd-adler32" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" [[package]] name = "simd_helpers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" dependencies = [ "quote", ] [[package]] name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ "lock_api", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[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.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "system-deps" version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" dependencies = [ "cfg-expr", "heck", "pkg-config", "toml 0.7.5", "version-compare", ] [[package]] name = "target-lexicon" version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1c7f239eb94671427157bd93b3694320f3668d4e1eff08c7285366fd777fac" [[package]] name = "termcolor" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "terminal_size" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ "rustix 0.37.21", "windows-sys 0.48.0", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "unicode-width", ] [[package]] name = "textwrap" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", "syn 2.0.22", ] [[package]] name = "tiff" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471" dependencies = [ "flate2", "jpeg-decoder", "weezl", ] [[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 = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] [[package]] name = "toml" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebafdf5ad1220cb59e7d17cf4d2c72015297b75b19a10472f99b89225089240" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.19.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7" dependencies = [ "indexmap 2.0.0", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "unicode-bidi" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "url" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "v_frame" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3753f70d50a77f5d381103ba2693a889fed0d84273dd5cbdf4eb8bda720f0c6" dependencies = [ "cfg-if", "noop_proc_macro", "num-derive", "num-traits", "rust_hawktracer", ] [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version-compare" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" 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.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 2.0.22", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", "syn 2.0.22", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "webp" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf022f821f166079a407d000ab57e84de020e66ffbbf4edde999bc7d6e371cae" dependencies = [ "libwebp-sys", ] [[package]] name = "weezl" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" [[package]] name = "which" version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" dependencies = [ "either", "libc", "once_cell", ] [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] [[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.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets 0.42.2", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.1", ] [[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", "windows_i686_gnu 0.48.0", "windows_i686_msvc 0.48.0", "windows_x86_64_gnu 0.48.0", "windows_x86_64_gnullvm 0.48.0", "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448" dependencies = [ "memchr", ] [[package]] name = "y4m" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" [[package]] name = "zune-inflate" version = "0.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] image-0.24.7/Cargo.toml0000644000000062750000000000100102200ustar # 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 are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.61.0" name = "image" version = "0.24.7" authors = ["The image-rs Developers"] exclude = [ "src/png/testdata/*", "examples/*", "tests/*", ] description = "Imaging library. Provides basic image processing and encoders/decoders for common image formats." homepage = "https://github.com/image-rs/image" documentation = "https://docs.rs/image" readme = "README.md" categories = [ "multimedia::images", "multimedia::encoding", ] license = "MIT" repository = "https://github.com/image-rs/image" resolver = "2" [lib] name = "image" path = "./src/lib.rs" [[bench]] name = "decode" path = "benches/decode.rs" harness = false [[bench]] name = "encode" path = "benches/encode.rs" harness = false [[bench]] name = "copy_from" harness = false [dependencies.bytemuck] version = "1.7.0" features = ["extern_crate_alloc"] [dependencies.byteorder] version = "1.3.2" [dependencies.color_quant] version = "1.1" [dependencies.dav1d] version = "0.6.0" optional = true [dependencies.dcv-color-primitives] version = "0.4.0" optional = true [dependencies.exr] version = "1.5.0" optional = true [dependencies.gif] version = "0.12" optional = true [dependencies.jpeg] version = "0.3.0" optional = true default-features = false package = "jpeg-decoder" [dependencies.libwebp] version = "0.2.2" optional = true default-features = false package = "webp" [dependencies.mp4parse] version = "0.17.0" optional = true [dependencies.num-rational] version = "0.4" default-features = false [dependencies.num-traits] version = "0.2.0" [dependencies.png] version = "0.17.6" optional = true [dependencies.qoi] version = "0.4" optional = true [dependencies.ravif] version = "0.11.0" optional = true [dependencies.rgb] version = "0.8.25" optional = true [dependencies.tiff] version = "0.9.0" optional = true [dev-dependencies.crc32fast] version = "1.2.0" [dev-dependencies.criterion] version = "0.4" [dev-dependencies.glob] version = "0.3" [dev-dependencies.jpeg] version = "0.3.0" features = ["platform_independent"] default-features = false package = "jpeg-decoder" [dev-dependencies.num-complex] version = "0.4" [dev-dependencies.quickcheck] version = "1" [features] avif = ["avif-encoder"] avif-decoder = [ "mp4parse", "dcv-color-primitives", "dav1d", ] avif-encoder = [ "ravif", "rgb", ] benchmarks = [] bmp = [] dds = ["dxt"] default = [ "gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "farbfeld", "jpeg_rayon", "openexr", "qoi", ] dxt = [] farbfeld = [] hdr = [] ico = [ "bmp", "png", ] jpeg_rayon = ["jpeg/rayon"] openexr = ["exr"] pnm = [] qoi = ["dep:qoi"] tga = [] webp = [] webp-encoder = ["libwebp"] image-0.24.7/Cargo.toml.orig000064400000000000000000000062201046102023000136670ustar 00000000000000[package] name = "image" version = "0.24.7" edition = "2018" resolver = "2" # note: when changed, also update test runner in `.github/workflows/rust.yml` rust-version = "1.61.0" license = "MIT" description = "Imaging library. Provides basic image processing and encoders/decoders for common image formats." authors = ["The image-rs Developers"] readme = "README.md" # crates.io metadata documentation = "https://docs.rs/image" repository = "https://github.com/image-rs/image" homepage = "https://github.com/image-rs/image" categories = ["multimedia::images", "multimedia::encoding"] # Crate build related exclude = [ "src/png/testdata/*", "examples/*", "tests/*", ] [lib] name = "image" path = "./src/lib.rs" [dependencies] bytemuck = { version = "1.7.0", features = ["extern_crate_alloc"] } # includes cast_vec byteorder = "1.3.2" num-rational = { version = "0.4", default-features = false } num-traits = "0.2.0" gif = { version = "0.12", optional = true } jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false, optional = true } png = { version = "0.17.6", optional = true } tiff = { version = "0.9.0", optional = true } ravif = { version = "0.11.0", optional = true } rgb = { version = "0.8.25", optional = true } mp4parse = { version = "0.17.0", optional = true } dav1d = { version = "0.6.0", optional = true } dcv-color-primitives = { version = "0.4.0", optional = true } color_quant = "1.1" exr = { version = "1.5.0", optional = true } qoi = { version = "0.4", optional = true } libwebp = { package = "webp", version = "0.2.2", default-features = false, optional = true } [dev-dependencies] crc32fast = "1.2.0" num-complex = "0.4" glob = "0.3" quickcheck = "1" criterion = "0.4" # Keep this in sync with the jpeg dependency above. This is used to enable the platform_independent # feature when testing, so `cargo test` works correctly. jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false, features = ["platform_independent"] } [features] # TODO: Add "avif" to this list while preparing for 0.24.0 default = ["gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "farbfeld", "jpeg_rayon", "openexr", "qoi"] ico = ["bmp", "png"] pnm = [] tga = [] bmp = [] hdr = [] dxt = [] dds = ["dxt"] farbfeld = [] openexr = ["exr"] qoi = ["dep:qoi"] # Enables WebP decoder support. webp = [] # Non-default, not included in `webp`. Requires native dependency libwebp. webp-encoder = ["libwebp"] # Enables multi-threading. # Requires latest stable Rust. jpeg_rayon = ["jpeg/rayon"] # Non-default, enables avif support. # Requires latest stable Rust. avif = ["avif-encoder"] # Requires latest stable Rust and recent nasm (>= 2.14). avif-encoder = ["ravif", "rgb"] # Non-default, even in `avif`. Requires stable Rust and native dependency libdav1d. avif-decoder = ["mp4parse", "dcv-color-primitives", "dav1d"] # Build some inline benchmarks. Useful only during development. # Requires rustc nightly for feature test. benchmarks = [] [[bench]] path = "benches/decode.rs" name = "decode" harness = false [[bench]] path = "benches/encode.rs" name = "encode" harness = false [[bench]] name = "copy_from" harness = false image-0.24.7/Cargo.toml.public-private-dependencies000064400000000000000000000051711046102023000203050ustar 00000000000000cargo-features = ["public-dependency"] [package] name = "image" version = "0.24.0-alpha" edition = "2018" rust-version = "1.56" license = "MIT" description = "Imaging library written in Rust. Provides basic filters and decoders for the most common image formats." authors = ["The image-rs Developers"] readme = "README.md" # crates.io metadata documentation = "https://docs.rs/image" repository = "https://github.com/image-rs/image" homepage = "https://github.com/image-rs/image" categories = ["multimedia::images", "multimedia::encoding"] # Crate build related exclude = [ "src/png/testdata/*", "examples/*", "tests/*", ] [lib] name = "image" path = "./src/lib.rs" [dependencies] bytemuck = { version = "1.7.0", features = ["extern_crate_alloc"] } # includes cast_vec byteorder = "1.3.2" num-iter = "0.1.32" num-rational = { version = "0.4", default-features = false } num-traits = { version = "0.2.0", public = true } gif = { version = "0.11.1", optional = true } jpeg = { package = "jpeg-decoder", version = "0.2.1", default-features = false, optional = true } png = { version = "0.17.0", optional = true } tiff = { version = "0.9.0", optional = true } ravif = { version = "0.8.0", optional = true } rgb = { version = "0.8.25", optional = true } mp4parse = { version = "0.12.0", optional = true } dav1d = { version = "0.6.0", optional = true } dcv-color-primitives = { version = "0.4.0", optional = true } exr = { version = "1.4.1", optional = true } color_quant = { version = "1.1", public = true } [dev-dependencies] crc32fast = "1.2.0" num-complex = "0.4" glob = "0.3" quickcheck = "1" criterion = "0.3" [features] # TODO: Add "avif" to this list while preparing for 0.24.0 default = ["gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "farbfeld", "jpeg_rayon", "openexr"] ico = ["bmp", "png"] pnm = [] tga = [] webp = [] bmp = [] hdr = [] dxt = [] dds = ["dxt"] farbfeld = [] openexr = ["exr"] # Enables multi-threading. # Requires latest stable Rust. jpeg_rayon = ["jpeg/rayon"] # Non-default, enables avif support. # Requires latest stable Rust. avif = ["avif-encoder"] # Requires latest stable Rust and recent nasm (>= 2.14). avif-encoder = ["ravif", "rgb"] # Non-default, even in `avif`. Requires stable Rust and native dependency libdav1d. avif-decoder = ["mp4parse", "dcv-color-primitives", "dav1d"] # Build some inline benchmarks. Useful only during development. # Requires rustc nightly for feature test. benchmarks = [] [[bench]] path = "benches/decode.rs" name = "decode" harness = false [[bench]] path = "benches/encode.rs" name = "encode" harness = false [[bench]] name = "copy_from" harness = false image-0.24.7/LICENSE000064400000000000000000000020721046102023000120060ustar 00000000000000The MIT License (MIT) Copyright (c) 2014 PistonDevelopers 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.image-0.24.7/README.md000064400000000000000000000232141046102023000122610ustar 00000000000000# Image [![crates.io](https://img.shields.io/crates/v/image.svg)](https://crates.io/crates/image) [![Documentation](https://docs.rs/image/badge.svg)](https://docs.rs/image) [![Build Status](https://github.com/image-rs/image/workflows/Rust%20CI/badge.svg)](https://github.com/image-rs/image/actions) [![Gitter](https://badges.gitter.im/image-rs/image.svg)](https://gitter.im/image-rs/image?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) Maintainers: [@HeroicKatora](https://github.com/HeroicKatora), [@fintelia](https://github.com/fintelia) [How to contribute](https://github.com/image-rs/organization/blob/master/CONTRIBUTING.md) ## An Image Processing Library This crate provides basic image processing functions and methods for converting to and from various image formats. All image processing functions provided operate on types that implement the `GenericImageView` and `GenericImage` traits and return an `ImageBuffer`. ## Supported Image Formats `image` provides implementations of common image format encoders and decoders. | Format | Decoding | Encoding | | ------ | -------- | -------- | | AVIF | Only 8-bit \*\* | Lossy | | BMP | Yes | Rgb8, Rgba8, Gray8, GrayA8 | | DDS | DXT1, DXT3, DXT5 | No | | Farbfeld | Yes | Yes | | GIF | Yes | Yes | | ICO | Yes | Yes | | JPEG | Baseline and progressive | Baseline JPEG | | OpenEXR | Rgb32F, Rgba32F (no dwa compression) | Rgb32F, Rgba32F (no dwa compression) | | PNG | All supported color types | Same as decoding | | PNM | PBM, PGM, PPM, standard PAM | Yes | | QOI | Yes | Yes | | TGA | Yes | Rgb8, Rgba8, Bgr8, Bgra8, Gray8, GrayA8 | | TIFF | Baseline(no fax support) + LZW + PackBits | Rgb8, Rgba8, Gray8 | | WebP | Yes | Rgb8, Rgba8 \* | - \* Requires the `webp-encoder` feature, uses the libwebp C library. - \*\* Requires the `avif-decoder` feature, uses the libdav1d C library. ### The [`ImageDecoder`](https://docs.rs/image/*/image/trait.ImageDecoder.html) and [`ImageDecoderRect`](https://docs.rs/image/*/image/trait.ImageDecoderRect.html) Traits All image format decoders implement the `ImageDecoder` trait which provide basic methods for getting image metadata and decoding images. Some formats additionally provide `ImageDecoderRect` implementations which allow for decoding only part of an image at once. The most important methods for decoders are... + **dimensions**: Return a tuple containing the width and height of the image. + **color_type**: Return the color type of the image data produced by this decoder. + **read_image**: Decode the entire image into a slice of bytes. ## Pixels `image` provides the following pixel types: + **Rgb**: RGB pixel + **Rgba**: RGB with alpha (RGBA pixel) + **Luma**: Grayscale pixel + **LumaA**: Grayscale with alpha All pixels are parameterised by their component type. ## Images Individual pixels within images are indexed with (0,0) at the top left corner. ### The [`GenericImageView`](https://docs.rs/image/*/image/trait.GenericImageView.html) and [`GenericImage`](https://docs.rs/image/*/image/trait.GenericImage.html) Traits Traits that provide methods for inspecting (`GenericImageView`) and manipulating (`GenericImage`) images, parameterised over the image's pixel type. Some of these methods for `GenericImageView` are... + **dimensions**: Return a tuple containing the width and height of the image. + **get_pixel**: Returns the pixel located at (x, y). + **pixels**: Returns an Iterator over the pixels of this image. While some of the methods for `GenericImage` are... + **put_pixel**: Put a pixel at location (x, y). + **copy_from**: Copies all of the pixels from another image into this image. ### Representation of Images `image` provides two main ways of representing image data: #### [`ImageBuffer`](https://docs.rs/image/*/image/struct.ImageBuffer.html) An image parameterised by its Pixel types, represented by a width and height and a vector of pixels. It provides direct access to its pixels and implements the `GenericImageView` and `GenericImage` traits. ```rust use image::{GenericImage, GenericImageView, ImageBuffer, RgbImage}; // Construct a new RGB ImageBuffer with the specified width and height. let img: RgbImage = ImageBuffer::new(512, 512); // Construct a new by repeated calls to the supplied closure. let mut img = ImageBuffer::from_fn(512, 512, |x, y| { if x % 2 == 0 { image::Luma([0u8]) } else { image::Luma([255u8]) } }); // Obtain the image's width and height. let (width, height) = img.dimensions(); // Access the pixel at coordinate (100, 100). let pixel = img[(100, 100)]; // Or use the `get_pixel` method from the `GenericImage` trait. let pixel = *img.get_pixel(100, 100); // Put a pixel at coordinate (100, 100). img.put_pixel(100, 100, pixel); // Iterate over all pixels in the image. for pixel in img.pixels() { // Do something with pixel. } ``` #### [`DynamicImage`](https://docs.rs/image/*/image/enum.DynamicImage.html) A `DynamicImage` is an enumeration over all supported `ImageBuffer

` types. Its exact image type is determined at runtime. It is the type returned when opening an image. For convenience `DynamicImage` reimplements all image processing functions. `DynamicImage` implement the `GenericImageView` and `GenericImage` traits for RGBA pixels. #### [`SubImage`](https://docs.rs/image/*/image/struct.SubImage.html) A view into another image, delimited by the coordinates of a rectangle. The coordinates given set the position of the top left corner of the rectangle. This is used to perform image processing functions on a subregion of an image. ```rust use image::{GenericImageView, ImageBuffer, RgbImage, imageops}; let mut img: RgbImage = ImageBuffer::new(512, 512); let subimg = imageops::crop(&mut img, 0, 0, 100, 100); assert!(subimg.dimensions() == (100, 100)); ``` ## Image Processing Functions These are the functions defined in the `imageops` module. All functions operate on types that implement the `GenericImage` trait. Note that some of the functions are very slow in debug mode. Make sure to use release mode if you experience any performance issues. + **blur**: Performs a Gaussian blur on the supplied image. + **brighten**: Brighten the supplied image. + **huerotate**: Hue rotate the supplied image by degrees. + **contrast**: Adjust the contrast of the supplied image. + **crop**: Return a mutable view into an image. + **filter3x3**: Perform a 3x3 box filter on the supplied image. + **flip_horizontal**: Flip an image horizontally. + **flip_vertical**: Flip an image vertically. + **grayscale**: Convert the supplied image to grayscale. + **invert**: Invert each pixel within the supplied image This function operates in place. + **resize**: Resize the supplied image to the specified dimensions. + **rotate180**: Rotate an image 180 degrees clockwise. + **rotate270**: Rotate an image 270 degrees clockwise. + **rotate90**: Rotate an image 90 degrees clockwise. + **unsharpen**: Performs an unsharpen mask on the supplied image. For more options, see the [`imageproc`](https://crates.io/crates/imageproc) crate. ## Examples ### Opening and Saving Images `image` provides the `open` function for opening images from a path. The image format is determined from the path's file extension. An `io` module provides a reader which offer some more control. ```rust,no_run use image::GenericImageView; fn main() { // Use the open function to load an image from a Path. // `open` returns a `DynamicImage` on success. let img = image::open("tests/images/jpg/progressive/cat.jpg").unwrap(); // The dimensions method returns the images width and height. println!("dimensions {:?}", img.dimensions()); // The color method returns the image's `ColorType`. println!("{:?}", img.color()); // Write the contents of this image to the Writer in PNG format. img.save("test.png").unwrap(); } ``` ### Generating Fractals ```rust,no_run //! An example of generating julia fractals. fn main() { let imgx = 800; let imgy = 800; let scalex = 3.0 / imgx as f32; let scaley = 3.0 / imgy as f32; // Create a new ImgBuf with width: imgx and height: imgy let mut imgbuf = image::ImageBuffer::new(imgx, imgy); // Iterate over the coordinates and pixels of the image for (x, y, pixel) in imgbuf.enumerate_pixels_mut() { let r = (0.3 * x as f32) as u8; let b = (0.3 * y as f32) as u8; *pixel = image::Rgb([r, 0, b]); } // A redundant loop to demonstrate reading image data for x in 0..imgx { for y in 0..imgy { let cx = y as f32 * scalex - 1.5; let cy = x as f32 * scaley - 1.5; let c = num_complex::Complex::new(-0.4, 0.6); let mut z = num_complex::Complex::new(cx, cy); let mut i = 0; while i < 255 && z.norm() <= 2.0 { z = z * z + c; i += 1; } let pixel = imgbuf.get_pixel_mut(x, y); let image::Rgb(data) = *pixel; *pixel = image::Rgb([data[0], i as u8, data[2]]); } } // Save the image as “fractal.png”, the format is deduced from the path imgbuf.save("fractal.png").unwrap(); } ``` Example output: A Julia Fractal, c: -0.4 + 0.6i ### Writing raw buffers If the high level interface is not needed because the image was obtained by other means, `image` provides the function `save_buffer` to save a buffer to a file. ```rust,no_run fn main() { let buffer: &[u8] = unimplemented!(); // Generate the image data // Save the buffer as "image.png" image::save_buffer("image.png", buffer, 800, 600, image::ColorType::Rgb8).unwrap() } ``` image-0.24.7/benches/README.md000064400000000000000000000002461046102023000136700ustar 00000000000000# Getting started with benchmarking To run the benchmarks you need a nightly rust toolchain. Then you launch it with cargo +nightly bench --features=benchmarks image-0.24.7/benches/copy_from.rs000064400000000000000000000007611046102023000147560ustar 00000000000000use criterion::{black_box, criterion_group, criterion_main, Criterion}; use image::{GenericImage, ImageBuffer, Rgba}; pub fn bench_copy_from(c: &mut Criterion) { let src = ImageBuffer::from_pixel(2048, 2048, Rgba([255u8, 0, 0, 255])); let mut dst = ImageBuffer::from_pixel(2048, 2048, Rgba([0u8, 0, 0, 255])); c.bench_function("copy_from", |b| { b.iter(|| dst.copy_from(black_box(&src), 0, 0)) }); } criterion_group!(benches, bench_copy_from); criterion_main!(benches); image-0.24.7/benches/decode.rs000064400000000000000000000060671046102023000142110ustar 00000000000000use std::{fs, iter, path}; use criterion::{criterion_group, criterion_main, Criterion}; use image::ImageFormat; #[derive(Clone, Copy)] struct BenchDef { dir: &'static [&'static str], files: &'static [&'static str], format: ImageFormat, } fn load_all(c: &mut Criterion) { const BENCH_DEFS: &'static [BenchDef] = &[ BenchDef { dir: &["bmp", "images"], files: &[ "Core_1_Bit.bmp", "Core_4_Bit.bmp", "Core_8_Bit.bmp", "rgb16.bmp", "rgb24.bmp", "rgb32.bmp", "pal4rle.bmp", "pal8rle.bmp", "rgb16-565.bmp", "rgb32bf.bmp", ], format: ImageFormat::Bmp, }, BenchDef { dir: &["gif", "simple"], files: &["alpha_gif_a.gif", "sample_1.gif"], format: ImageFormat::Gif, }, BenchDef { dir: &["hdr", "images"], files: &["image1.hdr", "rgbr4x4.hdr"], format: ImageFormat::Hdr, }, BenchDef { dir: &["ico", "images"], files: &[ "bmp-24bpp-mask.ico", "bmp-32bpp-alpha.ico", "png-32bpp-alpha.ico", "smile.ico", ], format: ImageFormat::Ico, }, BenchDef { dir: &["jpg", "progressive"], files: &["3.jpg", "cat.jpg", "test.jpg"], format: ImageFormat::Jpeg, }, // TODO: pnm // TODO: png BenchDef { dir: &["tga", "testsuite"], files: &["cbw8.tga", "ctc24.tga", "ubw8.tga", "utc24.tga"], format: ImageFormat::Tga, }, BenchDef { dir: &["tiff", "testsuite"], files: &[ "hpredict.tiff", "hpredict_packbits.tiff", "mandrill.tiff", "rgb-3c-16b.tiff", ], format: ImageFormat::Tiff, }, BenchDef { dir: &["webp", "images"], files: &[ "simple-gray.webp", "simple-rgb.webp", "vp8x-gray.webp", "vp8x-rgb.webp", ], format: ImageFormat::WebP, }, ]; for bench in BENCH_DEFS { bench_load(c, bench); } } criterion_group!(benches, load_all); criterion_main!(benches); fn bench_load(c: &mut Criterion, def: &BenchDef) { let group_name = format!("load-{:?}", def.format); let mut group = c.benchmark_group(&group_name); let paths = IMAGE_DIR.iter().chain(def.dir); for file_name in def.files { let path: path::PathBuf = paths.clone().chain(iter::once(file_name)).collect(); let buf = fs::read(path).unwrap(); group.bench_function(file_name.to_owned(), |b| { b.iter(|| { image::load_from_memory_with_format(&buf, def.format).unwrap(); }) }); } } const IMAGE_DIR: [&'static str; 3] = [".", "tests", "images"]; image-0.24.7/benches/encode.rs000064400000000000000000000100451046102023000142120ustar 00000000000000extern crate criterion; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use image::{codecs::bmp::BmpEncoder, codecs::jpeg::JpegEncoder, ColorType}; use std::fs::File; use std::io::{BufWriter, Seek, SeekFrom, Write}; trait Encoder { fn encode_raw(&self, into: &mut Vec, im: &[u8], dims: u32, color: ColorType); fn encode_bufvec(&self, into: &mut Vec, im: &[u8], dims: u32, color: ColorType); fn encode_file(&self, file: &File, im: &[u8], dims: u32, color: ColorType); } #[derive(Clone, Copy)] struct BenchDef { with: &'static dyn Encoder, name: &'static str, sizes: &'static [u32], colors: &'static [ColorType], } fn encode_all(c: &mut Criterion) { const BENCH_DEFS: &'static [BenchDef] = &[ BenchDef { with: &Bmp, name: "bmp", sizes: &[100u32, 200, 400], colors: &[ColorType::L8, ColorType::Rgb8, ColorType::Rgba8], }, BenchDef { with: &Jpeg, name: "jpeg", sizes: &[64u32, 128, 256], colors: &[ColorType::L8, ColorType::Rgb8, ColorType::Rgba8], }, ]; for definition in BENCH_DEFS { encode_definition(c, definition) } } criterion_group!(benches, encode_all); criterion_main!(benches); type BenchGroup<'a> = criterion::BenchmarkGroup<'a, criterion::measurement::WallTime>; /// Benchmarks encoding a zeroed image. /// /// For compressed formats this is surely not representative of encoding a normal image but it's a /// start for benchmarking. fn encode_zeroed(group: &mut BenchGroup, with: &dyn Encoder, size: u32, color: ColorType) { let bytes = size as usize * usize::from(color.bytes_per_pixel()); let im = vec![0; bytes * bytes]; group.bench_with_input( BenchmarkId::new(format!("zero-{:?}-rawvec", color), size), &im, |b, image| { let mut v = vec![]; with.encode_raw(&mut v, &im, size, color); b.iter(|| with.encode_raw(&mut v, image, size, color)); }, ); group.bench_with_input( BenchmarkId::new(format!("zero-{:?}-bufvec", color), size), &im, |b, image| { let mut v = vec![]; with.encode_raw(&mut v, &im, size, color); b.iter(|| with.encode_bufvec(&mut v, image, size, color)); }, ); group.bench_with_input( BenchmarkId::new(format!("zero-{:?}-file", color), size), &im, |b, image| { let file = File::create("temp.bmp").unwrap(); b.iter(|| with.encode_file(&file, image, size, color)); }, ); } fn encode_definition(criterion: &mut Criterion, def: &BenchDef) { let mut group = criterion.benchmark_group(format!("encode-{}", def.name)); for &color in def.colors { for &size in def.sizes { encode_zeroed(&mut group, def.with, size, color); } } } struct Bmp; struct Jpeg; trait EncoderBase { fn encode(&self, into: impl Write, im: &[u8], dims: u32, color: ColorType); } impl Encoder for T { fn encode_raw(&self, into: &mut Vec, im: &[u8], dims: u32, color: ColorType) { into.clear(); self.encode(into, im, dims, color); } fn encode_bufvec(&self, into: &mut Vec, im: &[u8], dims: u32, color: ColorType) { into.clear(); let buf = BufWriter::new(into); self.encode(buf, im, dims, color); } fn encode_file(&self, mut file: &File, im: &[u8], dims: u32, color: ColorType) { file.seek(SeekFrom::Start(0)).unwrap(); let buf = BufWriter::new(file); self.encode(buf, im, dims, color); } } impl EncoderBase for Bmp { fn encode(&self, mut into: impl Write, im: &[u8], size: u32, color: ColorType) { let mut x = BmpEncoder::new(&mut into); x.encode(im, size, size, color).unwrap(); } } impl EncoderBase for Jpeg { fn encode(&self, mut into: impl Write, im: &[u8], size: u32, color: ColorType) { let mut x = JpegEncoder::new(&mut into); x.encode(im, size, size, color).unwrap(); } } image-0.24.7/deny.toml000064400000000000000000000016011046102023000126320ustar 00000000000000# https://embarkstudios.github.io/cargo-deny/ targets = [ { triple = "aarch64-apple-darwin" }, { triple = "aarch64-linux-android" }, { triple = "x86_64-apple-darwin" }, { triple = "x86_64-pc-windows-msvc" }, { triple = "x86_64-unknown-linux-gnu" }, { triple = "x86_64-unknown-linux-musl" }, ] [advisories] vulnerability = "deny" unmaintained = "warn" yanked = "deny" ignore = [] [bans] multiple-versions = "deny" wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed deny = [] skip = [ { name = "num-derive" } # ravif transatively depends on 0.3 and 0.4. ] skip-tree = [ { name = "criterion" }, # dev-dependency { name = "quickcheck" }, # dev-dependency { name = "dav1d" }, # TODO: needs upgrade { name = "clap" }, ] [licenses] unlicensed = "allow" allow-osi-fsf-free = "either" copyleft = "allow" image-0.24.7/docs/2019-04-23-memory-unsafety.md000064400000000000000000000057151046102023000164620ustar 00000000000000# Advisory about potential memory unsafety issues [While reviewing][i885] some `unsafe Vec::from_raw_parts` operations within the library, trying to justify their existence with stronger reasoning, we noticed that they instead did not meet the required conditions set by the standard library. This unsoundness was quickly removed, but we noted that the same unjustified reasoning had been applied by a dependency introduced in `0.21`. For efficiency reasons, we had tried to reuse the allocations made by decoders for the buffer of the final image. However, that process is error prone. Most image decoding algorithms change the representation type of color samples to some degree. Notably, the output pixel type may have a different size and alignment than the type used in the temporary decoding buffer. In this specific instance, the `ImageBuffer` of the output expects a linear arrangement of `u8` samples while the implementation of the `hdr` decoder uses a pixel representation of `Rgb`, which has three times the size. One of the requirements of `Vec::from_raw_parts` reads: > ptr's T needs to have the same size and alignment as it was allocated with. This requirement is not present on slices `[T]`, as it is motivated by the allocator interface. The validity invariant of a reference and slice only requires the correct alignment here, which was considered in the design of `Rgb<_>` by giving it a well-defined representation, `#[repr(C)]`. But critically, this does not guarantee that we can reuse the existing allocation through effectively transmuting a `Vec<_>`! The actual impact of this issue is, in real world implementations, limited to allocators which handle allocations for types of size `1` and `3`/`4` differently. To the best of my knowledge, this does not apply to `jemalloc` and the `libc` allocator. However, we decided to proceed with caution here. ## Lessons for the future New library dependencies will be under a stricter policy. Not only would they need to be justified by functionality but also require at least some level of reasoning how they solve that problem better than alternatives. Some appearance of maintenance, or the existence of `#[deny(unsafe)]`, will help. We'll additionally look into existing dependencies trying to identify similar issues and minimizing the potential surface for implementation risks. ## Sound and safe buffer reuse It seems that the `Vec` representation is entirely unfit for buffer reuse in the style which an image library requires. In particular, using pixel types of different sizes is likely common to handle either whole (encoded) pixels or individual samples. Thus, we started a new sub-project to address this use case, [image-canvas][image-canvas]. Contributions and review of its safety are very welcome, we ask for the communities help here. The release of `v0.1` will not occur until at least one such review has occurred. [i885]: https://github.com/image-rs/image/pull/885 [image-canvas]: https://github.com/image-rs/canvas image-0.24.7/release.sh000075500000000000000000000014371046102023000127640ustar 00000000000000#!/bin/bash # Checks automatic preconditions for a release determine_new_version() { grep "version = " Cargo.toml | sed -Ee 's/version = "(.*)"/\1/' | head -1 } check_notexists_version() { # Does the api information start with: '{"errors":' [[ $(wget "https://crates.io/api/v1/crates/image/$1" -qO -) == "{\"errors\":"* ]] } check_release_description() { major=${1%%.*} minor_patch=${1#$major.} minor=${minor_patch%%.*} patch=${minor_patch#$minor.} # We just need to find a fitting header line grep -Eq "^### Version ${major}.${minor}$" CHANGES.md } version="$(determine_new_version)" check_release_description $version || { echo "Version does not have a release description"; exit 1; } check_notexists_version $version || { echo "Version $version appears already published"; exit 1; } image-0.24.7/src/animation.rs000064400000000000000000000262051046102023000141210ustar 00000000000000use std::iter::Iterator; use std::time::Duration; use num_rational::Ratio; use crate::error::ImageResult; use crate::RgbaImage; /// An implementation dependent iterator, reading the frames as requested pub struct Frames<'a> { iterator: Box> + 'a>, } impl<'a> Frames<'a> { /// Creates a new `Frames` from an implementation specific iterator. pub fn new(iterator: Box> + 'a>) -> Self { Frames { iterator } } /// Steps through the iterator from the current frame until the end and pushes each frame into /// a `Vec`. /// If en error is encountered that error is returned instead. /// /// Note: This is equivalent to `Frames::collect::>>()` pub fn collect_frames(self) -> ImageResult> { self.collect() } } impl<'a> Iterator for Frames<'a> { type Item = ImageResult; fn next(&mut self) -> Option> { self.iterator.next() } } /// A single animation frame #[derive(Clone)] pub struct Frame { /// Delay between the frames in milliseconds delay: Delay, /// x offset left: u32, /// y offset top: u32, buffer: RgbaImage, } /// The delay of a frame relative to the previous one. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd)] pub struct Delay { ratio: Ratio, } impl Frame { /// Constructs a new frame without any delay. pub fn new(buffer: RgbaImage) -> Frame { Frame { delay: Delay::from_ratio(Ratio::from_integer(0)), left: 0, top: 0, buffer, } } /// Constructs a new frame pub fn from_parts(buffer: RgbaImage, left: u32, top: u32, delay: Delay) -> Frame { Frame { delay, left, top, buffer, } } /// Delay of this frame pub fn delay(&self) -> Delay { self.delay } /// Returns the image buffer pub fn buffer(&self) -> &RgbaImage { &self.buffer } /// Returns a mutable image buffer pub fn buffer_mut(&mut self) -> &mut RgbaImage { &mut self.buffer } /// Returns the image buffer pub fn into_buffer(self) -> RgbaImage { self.buffer } /// Returns the x offset pub fn left(&self) -> u32 { self.left } /// Returns the y offset pub fn top(&self) -> u32 { self.top } } impl Delay { /// Create a delay from a ratio of milliseconds. /// /// # Examples /// /// ``` /// use image::Delay; /// let delay_10ms = Delay::from_numer_denom_ms(10, 1); /// ``` pub fn from_numer_denom_ms(numerator: u32, denominator: u32) -> Self { Delay { ratio: Ratio::new_raw(numerator, denominator), } } /// Convert from a duration, clamped between 0 and an implemented defined maximum. /// /// The maximum is *at least* `i32::MAX` milliseconds. It should be noted that the accuracy of /// the result may be relative and very large delays have a coarse resolution. /// /// # Examples /// /// ``` /// use std::time::Duration; /// use image::Delay; /// /// let duration = Duration::from_millis(20); /// let delay = Delay::from_saturating_duration(duration); /// ``` pub fn from_saturating_duration(duration: Duration) -> Self { // A few notes: The largest number we can represent as a ratio is u32::MAX but we can // sometimes represent much smaller numbers. // // We can represent duration as `millis+a/b` (where a < b, b > 0). // We must thus bound b with `b·millis + (b-1) <= u32::MAX` or // > `0 < b <= (u32::MAX + 1)/(millis + 1)` // Corollary: millis <= u32::MAX const MILLIS_BOUND: u128 = u32::max_value() as u128; let millis = duration.as_millis().min(MILLIS_BOUND); let submillis = (duration.as_nanos() % 1_000_000) as u32; let max_b = if millis > 0 { ((MILLIS_BOUND + 1) / (millis + 1)) as u32 } else { MILLIS_BOUND as u32 }; let millis = millis as u32; let (a, b) = Self::closest_bounded_fraction(max_b, submillis, 1_000_000); Self::from_numer_denom_ms(a + b * millis, b) } /// The numerator and denominator of the delay in milliseconds. /// /// This is guaranteed to be an exact conversion if the `Delay` was previously created with the /// `from_numer_denom_ms` constructor. pub fn numer_denom_ms(self) -> (u32, u32) { (*self.ratio.numer(), *self.ratio.denom()) } pub(crate) fn from_ratio(ratio: Ratio) -> Self { Delay { ratio } } pub(crate) fn into_ratio(self) -> Ratio { self.ratio } /// Given some fraction, compute an approximation with denominator bounded. /// /// Note that `denom_bound` bounds nominator and denominator of all intermediate /// approximations and the end result. fn closest_bounded_fraction(denom_bound: u32, nom: u32, denom: u32) -> (u32, u32) { use std::cmp::Ordering::{self, *}; assert!(0 < denom); assert!(0 < denom_bound); assert!(nom < denom); // Avoid a few type troubles. All intermediate results are bounded by `denom_bound` which // is in turn bounded by u32::MAX. Representing with u64 allows multiplication of any two // values without fears of overflow. // Compare two fractions whose parts fit into a u32. fn compare_fraction((an, ad): (u64, u64), (bn, bd): (u64, u64)) -> Ordering { (an * bd).cmp(&(bn * ad)) } // Computes the nominator of the absolute difference between two such fractions. fn abs_diff_nom((an, ad): (u64, u64), (bn, bd): (u64, u64)) -> u64 { let c0 = an * bd; let c1 = ad * bn; let d0 = c0.max(c1); let d1 = c0.min(c1); d0 - d1 } let exact = (u64::from(nom), u64::from(denom)); // The lower bound fraction, numerator and denominator. let mut lower = (0u64, 1u64); // The upper bound fraction, numerator and denominator. let mut upper = (1u64, 1u64); // The closest approximation for now. let mut guess = (u64::from(nom * 2 > denom), 1u64); // loop invariant: ad, bd <= denom_bound // iterates the Farey sequence. loop { // Break if we are done. if compare_fraction(guess, exact) == Equal { break; } // Break if next Farey number is out-of-range. if u64::from(denom_bound) - lower.1 < upper.1 { break; } // Next Farey approximation n between a and b let next = (lower.0 + upper.0, lower.1 + upper.1); // if F < n then replace the upper bound, else replace lower. if compare_fraction(exact, next) == Less { upper = next; } else { lower = next; } // Now correct the closest guess. // In other words, if |c - f| > |n - f| then replace it with the new guess. // This favors the guess with smaller denominator on equality. // |g - f| = |g_diff_nom|/(gd*fd); let g_diff_nom = abs_diff_nom(guess, exact); // |n - f| = |n_diff_nom|/(nd*fd); let n_diff_nom = abs_diff_nom(next, exact); // The difference |n - f| is smaller than |g - f| if either the integral part of the // fraction |n_diff_nom|/nd is smaller than the one of |g_diff_nom|/gd or if they are // the same but the fractional part is larger. if match (n_diff_nom / next.1).cmp(&(g_diff_nom / guess.1)) { Less => true, Greater => false, // Note that the nominator for the fractional part is smaller than its denominator // which is smaller than u32 and can't overflow the multiplication with the other // denominator, that is we can compare these fractions by multiplication with the // respective other denominator. Equal => { compare_fraction( (n_diff_nom % next.1, next.1), (g_diff_nom % guess.1, guess.1), ) == Less } } { guess = next; } } (guess.0 as u32, guess.1 as u32) } } impl From for Duration { fn from(delay: Delay) -> Self { let ratio = delay.into_ratio(); let ms = ratio.to_integer(); let rest = ratio.numer() % ratio.denom(); let nanos = (u64::from(rest) * 1_000_000) / u64::from(*ratio.denom()); Duration::from_millis(ms.into()) + Duration::from_nanos(nanos) } } #[cfg(test)] mod tests { use super::{Delay, Duration, Ratio}; #[test] fn simple() { let second = Delay::from_numer_denom_ms(1000, 1); assert_eq!(Duration::from(second), Duration::from_secs(1)); } #[test] fn fps_30() { let thirtieth = Delay::from_numer_denom_ms(1000, 30); let duration = Duration::from(thirtieth); assert_eq!(duration.as_secs(), 0); assert_eq!(duration.subsec_millis(), 33); assert_eq!(duration.subsec_nanos(), 33_333_333); } #[test] fn duration_outlier() { let oob = Duration::from_secs(0xFFFF_FFFF); let delay = Delay::from_saturating_duration(oob); assert_eq!(delay.numer_denom_ms(), (0xFFFF_FFFF, 1)); } #[test] fn duration_approx() { let oob = Duration::from_millis(0xFFFF_FFFF) + Duration::from_micros(1); let delay = Delay::from_saturating_duration(oob); assert_eq!(delay.numer_denom_ms(), (0xFFFF_FFFF, 1)); let inbounds = Duration::from_millis(0xFFFF_FFFF) - Duration::from_micros(1); let delay = Delay::from_saturating_duration(inbounds); assert_eq!(delay.numer_denom_ms(), (0xFFFF_FFFF, 1)); let fine = Duration::from_millis(0xFFFF_FFFF / 1000) + Duration::from_micros(0xFFFF_FFFF % 1000); let delay = Delay::from_saturating_duration(fine); // Funnily, 0xFFFF_FFFF is divisble by 5, thus we compare with a `Ratio`. assert_eq!(delay.into_ratio(), Ratio::new(0xFFFF_FFFF, 1000)); } #[test] fn precise() { // The ratio has only 32 bits in the numerator, too imprecise to get more than 11 digits // correct. But it may be expressed as 1_000_000/3 instead. let exceed = Duration::from_secs(333) + Duration::from_nanos(333_333_333); let delay = Delay::from_saturating_duration(exceed); assert_eq!(Duration::from(delay), exceed); } #[test] fn small() { // Not quite a delay of `1 ms`. let delay = Delay::from_numer_denom_ms(1 << 16, (1 << 16) + 1); let duration = Duration::from(delay); assert_eq!(duration.as_millis(), 0); // Not precisely the original but should be smaller than 0. let delay = Delay::from_saturating_duration(duration); assert_eq!(delay.into_ratio().to_integer(), 0); } } image-0.24.7/src/buffer.rs000064400000000000000000001470261046102023000134200ustar 00000000000000//! Contains the generic `ImageBuffer` struct. use num_traits::Zero; use std::fmt; use std::marker::PhantomData; use std::ops::{Deref, DerefMut, Index, IndexMut, Range}; use std::path::Path; use std::slice::{ChunksExact, ChunksExactMut}; use crate::color::{FromColor, Luma, LumaA, Rgb, Rgba}; use crate::dynimage::{save_buffer, save_buffer_with_format, write_buffer_with_format}; use crate::error::ImageResult; use crate::flat::{FlatSamples, SampleLayout}; use crate::image::{GenericImage, GenericImageView, ImageEncoder, ImageFormat, ImageOutputFormat}; use crate::math::Rect; use crate::traits::{EncodableLayout, Pixel, PixelWithColorType}; use crate::utils::expand_packed; /// Iterate over pixel refs. pub struct Pixels<'a, P: Pixel + 'a> where P::Subpixel: 'a, { chunks: ChunksExact<'a, P::Subpixel>, } impl<'a, P: Pixel + 'a> Iterator for Pixels<'a, P> where P::Subpixel: 'a, { type Item = &'a P; #[inline(always)] fn next(&mut self) -> Option<&'a P> { self.chunks.next().map(|v|

::from_slice(v)) } #[inline(always)] fn size_hint(&self) -> (usize, Option) { let len = self.len(); (len, Some(len)) } } impl<'a, P: Pixel + 'a> ExactSizeIterator for Pixels<'a, P> where P::Subpixel: 'a, { fn len(&self) -> usize { self.chunks.len() } } impl<'a, P: Pixel + 'a> DoubleEndedIterator for Pixels<'a, P> where P::Subpixel: 'a, { #[inline(always)] fn next_back(&mut self) -> Option<&'a P> { self.chunks.next_back().map(|v|

::from_slice(v)) } } impl Clone for Pixels<'_, P> { fn clone(&self) -> Self { Pixels { chunks: self.chunks.clone(), } } } impl fmt::Debug for Pixels<'_, P> where P::Subpixel: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Pixels") .field("chunks", &self.chunks) .finish() } } /// Iterate over mutable pixel refs. pub struct PixelsMut<'a, P: Pixel + 'a> where P::Subpixel: 'a, { chunks: ChunksExactMut<'a, P::Subpixel>, } impl<'a, P: Pixel + 'a> Iterator for PixelsMut<'a, P> where P::Subpixel: 'a, { type Item = &'a mut P; #[inline(always)] fn next(&mut self) -> Option<&'a mut P> { self.chunks.next().map(|v|

::from_slice_mut(v)) } #[inline(always)] fn size_hint(&self) -> (usize, Option) { let len = self.len(); (len, Some(len)) } } impl<'a, P: Pixel + 'a> ExactSizeIterator for PixelsMut<'a, P> where P::Subpixel: 'a, { fn len(&self) -> usize { self.chunks.len() } } impl<'a, P: Pixel + 'a> DoubleEndedIterator for PixelsMut<'a, P> where P::Subpixel: 'a, { #[inline(always)] fn next_back(&mut self) -> Option<&'a mut P> { self.chunks .next_back() .map(|v|

::from_slice_mut(v)) } } impl fmt::Debug for PixelsMut<'_, P> where P::Subpixel: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("PixelsMut") .field("chunks", &self.chunks) .finish() } } /// Iterate over rows of an image /// /// This iterator is created with [`ImageBuffer::rows`]. See its document for details. /// /// [`ImageBuffer::rows`]: ../struct.ImageBuffer.html#method.rows pub struct Rows<'a, P: Pixel + 'a> where

::Subpixel: 'a, { pixels: ChunksExact<'a, P::Subpixel>, } impl<'a, P: Pixel + 'a> Rows<'a, P> { /// Construct the iterator from image pixels. This is not public since it has a (hidden) panic /// condition. The `pixels` slice must be large enough so that all pixels are addressable. fn with_image(pixels: &'a [P::Subpixel], width: u32, height: u32) -> Self { let row_len = (width as usize) * usize::from(

::CHANNEL_COUNT); if row_len == 0 { Rows { pixels: [].chunks_exact(1), } } else { let pixels = pixels .get(..row_len * height as usize) .expect("Pixel buffer has too few subpixels"); // Rows are physically present. In particular, height is smaller than `usize::MAX` as // all subpixels can be indexed. Rows { pixels: pixels.chunks_exact(row_len), } } } } impl<'a, P: Pixel + 'a> Iterator for Rows<'a, P> where P::Subpixel: 'a, { type Item = Pixels<'a, P>; #[inline(always)] fn next(&mut self) -> Option> { let row = self.pixels.next()?; Some(Pixels { // Note: this is not reached when CHANNEL_COUNT is 0. chunks: row.chunks_exact(

::CHANNEL_COUNT as usize), }) } #[inline(always)] fn size_hint(&self) -> (usize, Option) { let len = self.len(); (len, Some(len)) } } impl<'a, P: Pixel + 'a> ExactSizeIterator for Rows<'a, P> where P::Subpixel: 'a, { fn len(&self) -> usize { self.pixels.len() } } impl<'a, P: Pixel + 'a> DoubleEndedIterator for Rows<'a, P> where P::Subpixel: 'a, { #[inline(always)] fn next_back(&mut self) -> Option> { let row = self.pixels.next_back()?; Some(Pixels { // Note: this is not reached when CHANNEL_COUNT is 0. chunks: row.chunks_exact(

::CHANNEL_COUNT as usize), }) } } impl Clone for Rows<'_, P> { fn clone(&self) -> Self { Rows { pixels: self.pixels.clone(), } } } impl fmt::Debug for Rows<'_, P> where P::Subpixel: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Rows") .field("pixels", &self.pixels) .finish() } } /// Iterate over mutable rows of an image /// /// This iterator is created with [`ImageBuffer::rows_mut`]. See its document for details. /// /// [`ImageBuffer::rows_mut`]: ../struct.ImageBuffer.html#method.rows_mut pub struct RowsMut<'a, P: Pixel + 'a> where

::Subpixel: 'a, { pixels: ChunksExactMut<'a, P::Subpixel>, } impl<'a, P: Pixel + 'a> RowsMut<'a, P> { /// Construct the iterator from image pixels. This is not public since it has a (hidden) panic /// condition. The `pixels` slice must be large enough so that all pixels are addressable. fn with_image(pixels: &'a mut [P::Subpixel], width: u32, height: u32) -> Self { let row_len = (width as usize) * usize::from(

::CHANNEL_COUNT); if row_len == 0 { RowsMut { pixels: [].chunks_exact_mut(1), } } else { let pixels = pixels .get_mut(..row_len * height as usize) .expect("Pixel buffer has too few subpixels"); // Rows are physically present. In particular, height is smaller than `usize::MAX` as // all subpixels can be indexed. RowsMut { pixels: pixels.chunks_exact_mut(row_len), } } } } impl<'a, P: Pixel + 'a> Iterator for RowsMut<'a, P> where P::Subpixel: 'a, { type Item = PixelsMut<'a, P>; #[inline(always)] fn next(&mut self) -> Option> { let row = self.pixels.next()?; Some(PixelsMut { // Note: this is not reached when CHANNEL_COUNT is 0. chunks: row.chunks_exact_mut(

::CHANNEL_COUNT as usize), }) } #[inline(always)] fn size_hint(&self) -> (usize, Option) { let len = self.len(); (len, Some(len)) } } impl<'a, P: Pixel + 'a> ExactSizeIterator for RowsMut<'a, P> where P::Subpixel: 'a, { fn len(&self) -> usize { self.pixels.len() } } impl<'a, P: Pixel + 'a> DoubleEndedIterator for RowsMut<'a, P> where P::Subpixel: 'a, { #[inline(always)] fn next_back(&mut self) -> Option> { let row = self.pixels.next_back()?; Some(PixelsMut { // Note: this is not reached when CHANNEL_COUNT is 0. chunks: row.chunks_exact_mut(

::CHANNEL_COUNT as usize), }) } } impl fmt::Debug for RowsMut<'_, P> where P::Subpixel: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("RowsMut") .field("pixels", &self.pixels) .finish() } } /// Enumerate the pixels of an image. pub struct EnumeratePixels<'a, P: Pixel + 'a> where

::Subpixel: 'a, { pixels: Pixels<'a, P>, x: u32, y: u32, width: u32, } impl<'a, P: Pixel + 'a> Iterator for EnumeratePixels<'a, P> where P::Subpixel: 'a, { type Item = (u32, u32, &'a P); #[inline(always)] fn next(&mut self) -> Option<(u32, u32, &'a P)> { if self.x >= self.width { self.x = 0; self.y += 1; } let (x, y) = (self.x, self.y); self.x += 1; self.pixels.next().map(|p| (x, y, p)) } #[inline(always)] fn size_hint(&self) -> (usize, Option) { let len = self.len(); (len, Some(len)) } } impl<'a, P: Pixel + 'a> ExactSizeIterator for EnumeratePixels<'a, P> where P::Subpixel: 'a, { fn len(&self) -> usize { self.pixels.len() } } impl Clone for EnumeratePixels<'_, P> { fn clone(&self) -> Self { EnumeratePixels { pixels: self.pixels.clone(), ..*self } } } impl fmt::Debug for EnumeratePixels<'_, P> where P::Subpixel: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("EnumeratePixels") .field("pixels", &self.pixels) .field("x", &self.x) .field("y", &self.y) .field("width", &self.width) .finish() } } /// Enumerate the rows of an image. pub struct EnumerateRows<'a, P: Pixel + 'a> where

::Subpixel: 'a, { rows: Rows<'a, P>, y: u32, width: u32, } impl<'a, P: Pixel + 'a> Iterator for EnumerateRows<'a, P> where P::Subpixel: 'a, { type Item = (u32, EnumeratePixels<'a, P>); #[inline(always)] fn next(&mut self) -> Option<(u32, EnumeratePixels<'a, P>)> { let y = self.y; self.y += 1; self.rows.next().map(|r| { ( y, EnumeratePixels { x: 0, y, width: self.width, pixels: r, }, ) }) } #[inline(always)] fn size_hint(&self) -> (usize, Option) { let len = self.len(); (len, Some(len)) } } impl<'a, P: Pixel + 'a> ExactSizeIterator for EnumerateRows<'a, P> where P::Subpixel: 'a, { fn len(&self) -> usize { self.rows.len() } } impl Clone for EnumerateRows<'_, P> { fn clone(&self) -> Self { EnumerateRows { rows: self.rows.clone(), ..*self } } } impl fmt::Debug for EnumerateRows<'_, P> where P::Subpixel: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("EnumerateRows") .field("rows", &self.rows) .field("y", &self.y) .field("width", &self.width) .finish() } } /// Enumerate the pixels of an image. pub struct EnumeratePixelsMut<'a, P: Pixel + 'a> where

::Subpixel: 'a, { pixels: PixelsMut<'a, P>, x: u32, y: u32, width: u32, } impl<'a, P: Pixel + 'a> Iterator for EnumeratePixelsMut<'a, P> where P::Subpixel: 'a, { type Item = (u32, u32, &'a mut P); #[inline(always)] fn next(&mut self) -> Option<(u32, u32, &'a mut P)> { if self.x >= self.width { self.x = 0; self.y += 1; } let (x, y) = (self.x, self.y); self.x += 1; self.pixels.next().map(|p| (x, y, p)) } #[inline(always)] fn size_hint(&self) -> (usize, Option) { let len = self.len(); (len, Some(len)) } } impl<'a, P: Pixel + 'a> ExactSizeIterator for EnumeratePixelsMut<'a, P> where P::Subpixel: 'a, { fn len(&self) -> usize { self.pixels.len() } } impl fmt::Debug for EnumeratePixelsMut<'_, P> where P::Subpixel: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("EnumeratePixelsMut") .field("pixels", &self.pixels) .field("x", &self.x) .field("y", &self.y) .field("width", &self.width) .finish() } } /// Enumerate the rows of an image. pub struct EnumerateRowsMut<'a, P: Pixel + 'a> where

::Subpixel: 'a, { rows: RowsMut<'a, P>, y: u32, width: u32, } impl<'a, P: Pixel + 'a> Iterator for EnumerateRowsMut<'a, P> where P::Subpixel: 'a, { type Item = (u32, EnumeratePixelsMut<'a, P>); #[inline(always)] fn next(&mut self) -> Option<(u32, EnumeratePixelsMut<'a, P>)> { let y = self.y; self.y += 1; self.rows.next().map(|r| { ( y, EnumeratePixelsMut { x: 0, y, width: self.width, pixels: r, }, ) }) } #[inline(always)] fn size_hint(&self) -> (usize, Option) { let len = self.len(); (len, Some(len)) } } impl<'a, P: Pixel + 'a> ExactSizeIterator for EnumerateRowsMut<'a, P> where P::Subpixel: 'a, { fn len(&self) -> usize { self.rows.len() } } impl fmt::Debug for EnumerateRowsMut<'_, P> where P::Subpixel: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("EnumerateRowsMut") .field("rows", &self.rows) .field("y", &self.y) .field("width", &self.width) .finish() } } /// Generic image buffer /// /// This is an image parameterised by its Pixel types, represented by a width and height and a /// container of channel data. It provides direct access to its pixels and implements the /// [`GenericImageView`] and [`GenericImage`] traits. In many ways, this is the standard buffer /// implementing those traits. Using this concrete type instead of a generic type parameter has /// been shown to improve performance. /// /// The crate defines a few type aliases with regularly used pixel types for your convenience, such /// as [`RgbImage`], [`GrayImage`] etc. /// /// [`GenericImage`]: trait.GenericImage.html /// [`GenericImageView`]: trait.GenericImageView.html /// [`RgbImage`]: type.RgbImage.html /// [`GrayImage`]: type.GrayImage.html /// /// To convert between images of different Pixel types use [`DynamicImage`]. /// /// You can retrieve a complete description of the buffer's layout and contents through /// [`as_flat_samples`] and [`as_flat_samples_mut`]. This can be handy to also use the contents in /// a foreign language, map it as a GPU host buffer or other similar tasks. /// /// [`DynamicImage`]: enum.DynamicImage.html /// [`as_flat_samples`]: #method.as_flat_samples /// [`as_flat_samples_mut`]: #method.as_flat_samples_mut /// /// ## Examples /// /// Create a simple canvas and paint a small cross. /// /// ``` /// use image::{RgbImage, Rgb}; /// /// let mut img = RgbImage::new(32, 32); /// /// for x in 15..=17 { /// for y in 8..24 { /// img.put_pixel(x, y, Rgb([255, 0, 0])); /// img.put_pixel(y, x, Rgb([255, 0, 0])); /// } /// } /// ``` /// /// Overlays an image on top of a larger background raster. /// /// ```no_run /// use image::{GenericImage, GenericImageView, ImageBuffer, open}; /// /// let on_top = open("path/to/some.png").unwrap().into_rgb8(); /// let mut img = ImageBuffer::from_fn(512, 512, |x, y| { /// if (x + y) % 2 == 0 { /// image::Rgb([0, 0, 0]) /// } else { /// image::Rgb([255, 255, 255]) /// } /// }); /// /// image::imageops::overlay(&mut img, &on_top, 128, 128); /// ``` /// /// Convert an RgbaImage to a GrayImage. /// /// ```no_run /// use image::{open, DynamicImage}; /// /// let rgba = open("path/to/some.png").unwrap().into_rgba8(); /// let gray = DynamicImage::ImageRgba8(rgba).into_luma8(); /// ``` #[derive(Debug, Hash, PartialEq, Eq)] pub struct ImageBuffer { width: u32, height: u32, _phantom: PhantomData

, data: Container, } // generic implementation, shared along all image buffers impl ImageBuffer where P: Pixel, Container: Deref, { /// Constructs a buffer from a generic container /// (for example a `Vec` or a slice) /// /// Returns `None` if the container is not big enough (including when the image dimensions /// necessitate an allocation of more bytes than supported by the container). pub fn from_raw(width: u32, height: u32, buf: Container) -> Option> { if Self::check_image_fits(width, height, buf.len()) { Some(ImageBuffer { data: buf, width, height, _phantom: PhantomData, }) } else { None } } /// Returns the underlying raw buffer pub fn into_raw(self) -> Container { self.data } /// Returns the underlying raw buffer pub fn as_raw(&self) -> &Container { &self.data } /// The width and height of this image. pub fn dimensions(&self) -> (u32, u32) { (self.width, self.height) } /// The width of this image. pub fn width(&self) -> u32 { self.width } /// The height of this image. pub fn height(&self) -> u32 { self.height } // TODO: choose name under which to expose. pub(crate) fn inner_pixels(&self) -> &[P::Subpixel] { let len = Self::image_buffer_len(self.width, self.height).unwrap(); &self.data[..len] } /// Returns an iterator over the pixels of this image. /// The iteration order is x = 0 to width then y = 0 to height pub fn pixels(&self) -> Pixels

{ Pixels { chunks: self .inner_pixels() .chunks_exact(

::CHANNEL_COUNT as usize), } } /// Returns an iterator over the rows of this image. /// /// Only non-empty rows can be iterated in this manner. In particular the iterator will not /// yield any item when the width of the image is `0` or a pixel type without any channels is /// used. This ensures that its length can always be represented by `usize`. pub fn rows(&self) -> Rows

{ Rows::with_image(&self.data, self.width, self.height) } /// Enumerates over the pixels of the image. /// The iterator yields the coordinates of each pixel /// along with a reference to them. /// The iteration order is x = 0 to width then y = 0 to height /// Starting from the top left. pub fn enumerate_pixels(&self) -> EnumeratePixels

{ EnumeratePixels { pixels: self.pixels(), x: 0, y: 0, width: self.width, } } /// Enumerates over the rows of the image. /// The iterator yields the y-coordinate of each row /// along with a reference to them. pub fn enumerate_rows(&self) -> EnumerateRows

{ EnumerateRows { rows: self.rows(), y: 0, width: self.width, } } /// Gets a reference to the pixel at location `(x, y)` /// /// # Panics /// /// Panics if `(x, y)` is out of the bounds `(width, height)`. #[inline] #[track_caller] pub fn get_pixel(&self, x: u32, y: u32) -> &P { match self.pixel_indices(x, y) { None => panic!( "Image index {:?} out of bounds {:?}", (x, y), (self.width, self.height) ), Some(pixel_indices) =>

::from_slice(&self.data[pixel_indices]), } } /// Gets a reference to the pixel at location `(x, y)` or returns `None` if /// the index is out of the bounds `(width, height)`. pub fn get_pixel_checked(&self, x: u32, y: u32) -> Option<&P> { if x >= self.width { return None; } let num_channels =

::CHANNEL_COUNT as usize; let i = (y as usize) .saturating_mul(self.width as usize) .saturating_add(x as usize) .saturating_mul(num_channels); self.data .get(i..i + num_channels) .map(|pixel_indices|

::from_slice(pixel_indices)) } /// Test that the image fits inside the buffer. /// /// Verifies that the maximum image of pixels inside the bounds is smaller than the provided /// length. Note that as a corrolary we also have that the index calculation of pixels inside /// the bounds will not overflow. fn check_image_fits(width: u32, height: u32, len: usize) -> bool { let checked_len = Self::image_buffer_len(width, height); checked_len.map(|min_len| min_len <= len).unwrap_or(false) } fn image_buffer_len(width: u32, height: u32) -> Option { Some(

::CHANNEL_COUNT as usize) .and_then(|size| size.checked_mul(width as usize)) .and_then(|size| size.checked_mul(height as usize)) } #[inline(always)] fn pixel_indices(&self, x: u32, y: u32) -> Option> { if x >= self.width || y >= self.height { return None; } Some(self.pixel_indices_unchecked(x, y)) } #[inline(always)] fn pixel_indices_unchecked(&self, x: u32, y: u32) -> Range { let no_channels =

::CHANNEL_COUNT as usize; // If in bounds, this can't overflow as we have tested that at construction! let min_index = (y as usize * self.width as usize + x as usize) * no_channels; min_index..min_index + no_channels } /// Get the format of the buffer when viewed as a matrix of samples. pub fn sample_layout(&self) -> SampleLayout { // None of these can overflow, as all our memory is addressable. SampleLayout::row_major_packed(

::CHANNEL_COUNT, self.width, self.height) } /// Return the raw sample buffer with its stride an dimension information. /// /// The returned buffer is guaranteed to be well formed in all cases. It is laid out by /// colors, width then height, meaning `channel_stride <= width_stride <= height_stride`. All /// strides are in numbers of elements but those are mostly `u8` in which case the strides are /// also byte strides. pub fn into_flat_samples(self) -> FlatSamples where Container: AsRef<[P::Subpixel]>, { // None of these can overflow, as all our memory is addressable. let layout = self.sample_layout(); FlatSamples { samples: self.data, layout, color_hint: None, // TODO: the pixel type might contain P::COLOR_TYPE if it satisfies PixelWithColorType } } /// Return a view on the raw sample buffer. /// /// See [`into_flat_samples`](#method.into_flat_samples) for more details. pub fn as_flat_samples(&self) -> FlatSamples<&[P::Subpixel]> where Container: AsRef<[P::Subpixel]>, { let layout = self.sample_layout(); FlatSamples { samples: self.data.as_ref(), layout, color_hint: None, // TODO: the pixel type might contain P::COLOR_TYPE if it satisfies PixelWithColorType } } /// Return a mutable view on the raw sample buffer. /// /// See [`into_flat_samples`](#method.into_flat_samples) for more details. pub fn as_flat_samples_mut(&mut self) -> FlatSamples<&mut [P::Subpixel]> where Container: AsMut<[P::Subpixel]>, { let layout = self.sample_layout(); FlatSamples { samples: self.data.as_mut(), layout, color_hint: None, // TODO: the pixel type might contain P::COLOR_TYPE if it satisfies PixelWithColorType } } } impl ImageBuffer where P: Pixel, Container: Deref + DerefMut, { // TODO: choose name under which to expose. fn inner_pixels_mut(&mut self) -> &mut [P::Subpixel] { let len = Self::image_buffer_len(self.width, self.height).unwrap(); &mut self.data[..len] } /// Returns an iterator over the mutable pixels of this image. pub fn pixels_mut(&mut self) -> PixelsMut

{ PixelsMut { chunks: self .inner_pixels_mut() .chunks_exact_mut(

::CHANNEL_COUNT as usize), } } /// Returns an iterator over the mutable rows of this image. /// /// Only non-empty rows can be iterated in this manner. In particular the iterator will not /// yield any item when the width of the image is `0` or a pixel type without any channels is /// used. This ensures that its length can always be represented by `usize`. pub fn rows_mut(&mut self) -> RowsMut

{ RowsMut::with_image(&mut self.data, self.width, self.height) } /// Enumerates over the pixels of the image. /// The iterator yields the coordinates of each pixel /// along with a mutable reference to them. pub fn enumerate_pixels_mut(&mut self) -> EnumeratePixelsMut

{ let width = self.width; EnumeratePixelsMut { pixels: self.pixels_mut(), x: 0, y: 0, width, } } /// Enumerates over the rows of the image. /// The iterator yields the y-coordinate of each row /// along with a mutable reference to them. pub fn enumerate_rows_mut(&mut self) -> EnumerateRowsMut

{ let width = self.width; EnumerateRowsMut { rows: self.rows_mut(), y: 0, width, } } /// Gets a reference to the mutable pixel at location `(x, y)` /// /// # Panics /// /// Panics if `(x, y)` is out of the bounds `(width, height)`. #[inline] #[track_caller] pub fn get_pixel_mut(&mut self, x: u32, y: u32) -> &mut P { match self.pixel_indices(x, y) { None => panic!( "Image index {:?} out of bounds {:?}", (x, y), (self.width, self.height) ), Some(pixel_indices) =>

::from_slice_mut(&mut self.data[pixel_indices]), } } /// Gets a reference to the mutable pixel at location `(x, y)` or returns /// `None` if the index is out of the bounds `(width, height)`. pub fn get_pixel_mut_checked(&mut self, x: u32, y: u32) -> Option<&mut P> { if x >= self.width { return None; } let num_channels =

::CHANNEL_COUNT as usize; let i = (y as usize) .saturating_mul(self.width as usize) .saturating_add(x as usize) .saturating_mul(num_channels); self.data .get_mut(i..i + num_channels) .map(|pixel_indices|

::from_slice_mut(pixel_indices)) } /// Puts a pixel at location `(x, y)` /// /// # Panics /// /// Panics if `(x, y)` is out of the bounds `(width, height)`. #[inline] #[track_caller] pub fn put_pixel(&mut self, x: u32, y: u32, pixel: P) { *self.get_pixel_mut(x, y) = pixel } } impl ImageBuffer where P: Pixel, [P::Subpixel]: EncodableLayout, Container: Deref, { /// Saves the buffer to a file at the path specified. /// /// The image format is derived from the file extension. pub fn save(&self, path: Q) -> ImageResult<()> where Q: AsRef, P: PixelWithColorType, { save_buffer( path, self.inner_pixels().as_bytes(), self.width(), self.height(),

::COLOR_TYPE, ) } } impl ImageBuffer where P: Pixel, [P::Subpixel]: EncodableLayout, Container: Deref, { /// Saves the buffer to a file at the specified path in /// the specified format. /// /// See [`save_buffer_with_format`](fn.save_buffer_with_format.html) for /// supported types. pub fn save_with_format(&self, path: Q, format: ImageFormat) -> ImageResult<()> where Q: AsRef, P: PixelWithColorType, { // This is valid as the subpixel is u8. save_buffer_with_format( path, self.inner_pixels().as_bytes(), self.width(), self.height(),

::COLOR_TYPE, format, ) } } impl ImageBuffer where P: Pixel, [P::Subpixel]: EncodableLayout, Container: Deref, { /// Writes the buffer to a writer in the specified format. /// /// Assumes the writer is buffered. In most cases, /// you should wrap your writer in a `BufWriter` for best performance. /// /// See [`ImageOutputFormat`](enum.ImageOutputFormat.html) for /// supported types. pub fn write_to(&self, writer: &mut W, format: F) -> ImageResult<()> where W: std::io::Write + std::io::Seek, F: Into, P: PixelWithColorType, { // This is valid as the subpixel is u8. write_buffer_with_format( writer, self.inner_pixels().as_bytes(), self.width(), self.height(),

::COLOR_TYPE, format, ) } } impl ImageBuffer where P: Pixel, [P::Subpixel]: EncodableLayout, Container: Deref, { /// Writes the buffer with the given encoder. pub fn write_with_encoder(&self, encoder: E) -> ImageResult<()> where E: ImageEncoder, P: PixelWithColorType, { // This is valid as the subpixel is u8. encoder.write_image( self.inner_pixels().as_bytes(), self.width(), self.height(),

::COLOR_TYPE, ) } } impl Default for ImageBuffer where P: Pixel, Container: Default, { fn default() -> Self { Self { width: 0, height: 0, _phantom: PhantomData, data: Default::default(), } } } impl Deref for ImageBuffer where P: Pixel, Container: Deref, { type Target = [P::Subpixel]; fn deref(&self) -> &::Target { &self.data } } impl DerefMut for ImageBuffer where P: Pixel, Container: Deref + DerefMut, { fn deref_mut(&mut self) -> &mut ::Target { &mut self.data } } impl Index<(u32, u32)> for ImageBuffer where P: Pixel, Container: Deref, { type Output = P; fn index(&self, (x, y): (u32, u32)) -> &P { self.get_pixel(x, y) } } impl IndexMut<(u32, u32)> for ImageBuffer where P: Pixel, Container: Deref + DerefMut, { fn index_mut(&mut self, (x, y): (u32, u32)) -> &mut P { self.get_pixel_mut(x, y) } } impl Clone for ImageBuffer where P: Pixel, Container: Deref + Clone, { fn clone(&self) -> ImageBuffer { ImageBuffer { data: self.data.clone(), width: self.width, height: self.height, _phantom: PhantomData, } } } impl GenericImageView for ImageBuffer where P: Pixel, Container: Deref + Deref, { type Pixel = P; fn dimensions(&self) -> (u32, u32) { self.dimensions() } fn bounds(&self) -> (u32, u32, u32, u32) { (0, 0, self.width, self.height) } fn get_pixel(&self, x: u32, y: u32) -> P { *self.get_pixel(x, y) } /// Returns the pixel located at (x, y), ignoring bounds checking. #[inline(always)] unsafe fn unsafe_get_pixel(&self, x: u32, y: u32) -> P { let indices = self.pixel_indices_unchecked(x, y); *

::from_slice(self.data.get_unchecked(indices)) } } impl GenericImage for ImageBuffer where P: Pixel, Container: Deref + DerefMut, { fn get_pixel_mut(&mut self, x: u32, y: u32) -> &mut P { self.get_pixel_mut(x, y) } fn put_pixel(&mut self, x: u32, y: u32, pixel: P) { *self.get_pixel_mut(x, y) = pixel } /// Puts a pixel at location (x, y), ignoring bounds checking. #[inline(always)] unsafe fn unsafe_put_pixel(&mut self, x: u32, y: u32, pixel: P) { let indices = self.pixel_indices_unchecked(x, y); let p =

::from_slice_mut(self.data.get_unchecked_mut(indices)); *p = pixel } /// Put a pixel at location (x, y), taking into account alpha channels /// /// DEPRECATED: This method will be removed. Blend the pixel directly instead. fn blend_pixel(&mut self, x: u32, y: u32, p: P) { self.get_pixel_mut(x, y).blend(&p) } fn copy_within(&mut self, source: Rect, x: u32, y: u32) -> bool { let Rect { x: sx, y: sy, width, height, } = source; let dx = x; let dy = y; assert!(sx < self.width() && dx < self.width()); assert!(sy < self.height() && dy < self.height()); if self.width() - dx.max(sx) < width || self.height() - dy.max(sy) < height { return false; } if sy < dy { for y in (0..height).rev() { let sy = sy + y; let dy = dy + y; let Range { start, .. } = self.pixel_indices_unchecked(sx, sy); let Range { end, .. } = self.pixel_indices_unchecked(sx + width - 1, sy); let dst = self.pixel_indices_unchecked(dx, dy).start; self.data.copy_within(start..end, dst); } } else { for y in 0..height { let sy = sy + y; let dy = dy + y; let Range { start, .. } = self.pixel_indices_unchecked(sx, sy); let Range { end, .. } = self.pixel_indices_unchecked(sx + width - 1, sy); let dst = self.pixel_indices_unchecked(dx, dy).start; self.data.copy_within(start..end, dst); } } true } } // concrete implementation for `Vec`-backed buffers // TODO: I think that rustc does not "see" this impl any more: the impl with // Container meets the same requirements. At least, I got compile errors that // there is no such function as `into_vec`, whereas `into_raw` did work, and // `into_vec` is redundant anyway, because `into_raw` will give you the vector, // and it is more generic. impl ImageBuffer> { /// Creates a new image buffer based on a `Vec`. /// /// # Panics /// /// Panics when the resulting image is larger the the maximum size of a vector. pub fn new(width: u32, height: u32) -> ImageBuffer> { let size = Self::image_buffer_len(width, height) .expect("Buffer length in `ImageBuffer::new` overflows usize"); ImageBuffer { data: vec![Zero::zero(); size], width, height, _phantom: PhantomData, } } /// Constructs a new ImageBuffer by copying a pixel /// /// # Panics /// /// Panics when the resulting image is larger the the maximum size of a vector. pub fn from_pixel(width: u32, height: u32, pixel: P) -> ImageBuffer> { let mut buf = ImageBuffer::new(width, height); for p in buf.pixels_mut() { *p = pixel } buf } /// Constructs a new ImageBuffer by repeated application of the supplied function. /// /// The arguments to the function are the pixel's x and y coordinates. /// /// # Panics /// /// Panics when the resulting image is larger the the maximum size of a vector. pub fn from_fn(width: u32, height: u32, mut f: F) -> ImageBuffer> where F: FnMut(u32, u32) -> P, { let mut buf = ImageBuffer::new(width, height); for (x, y, p) in buf.enumerate_pixels_mut() { *p = f(x, y) } buf } /// Creates an image buffer out of an existing buffer. /// Returns None if the buffer is not big enough. pub fn from_vec( width: u32, height: u32, buf: Vec, ) -> Option>> { ImageBuffer::from_raw(width, height, buf) } /// Consumes the image buffer and returns the underlying data /// as an owned buffer pub fn into_vec(self) -> Vec { self.into_raw() } } /// Provides color conversions for whole image buffers. pub trait ConvertBuffer { /// Converts `self` to a buffer of type T /// /// A generic implementation is provided to convert any image buffer to a image buffer /// based on a `Vec`. fn convert(&self) -> T; } // concrete implementation Luma -> Rgba impl GrayImage { /// Expands a color palette by re-using the existing buffer. /// Assumes 8 bit per pixel. Uses an optionally transparent index to /// adjust it's alpha value accordingly. pub fn expand_palette( self, palette: &[(u8, u8, u8)], transparent_idx: Option, ) -> RgbaImage { let (width, height) = self.dimensions(); let mut data = self.into_raw(); let entries = data.len(); data.resize(entries.checked_mul(4).unwrap(), 0); let mut buffer = ImageBuffer::from_vec(width, height, data).unwrap(); expand_packed(&mut buffer, 4, 8, |idx, pixel| { let (r, g, b) = palette[idx as usize]; let a = if let Some(t_idx) = transparent_idx { if t_idx == idx { 0 } else { 255 } } else { 255 }; pixel[0] = r; pixel[1] = g; pixel[2] = b; pixel[3] = a; }); buffer } } // TODO: Equality constraints are not yet supported in where clauses, when they // are, the T parameter should be removed in favor of ToType::Subpixel, which // will then be FromType::Subpixel. impl ConvertBuffer>> for ImageBuffer where Container: Deref, ToType: FromColor, { /// # Examples /// Convert RGB image to gray image. /// ```no_run /// use image::buffer::ConvertBuffer; /// use image::GrayImage; /// /// let image_path = "examples/fractal.png"; /// let image = image::open(&image_path) /// .expect("Open file failed") /// .to_rgba8(); /// /// let gray_image: GrayImage = image.convert(); /// ``` fn convert(&self) -> ImageBuffer> { let mut buffer: ImageBuffer> = ImageBuffer::new(self.width, self.height); for (to, from) in buffer.pixels_mut().zip(self.pixels()) { to.from_color(from) } buffer } } /// Sendable Rgb image buffer pub type RgbImage = ImageBuffer, Vec>; /// Sendable Rgb + alpha channel image buffer pub type RgbaImage = ImageBuffer, Vec>; /// Sendable grayscale image buffer pub type GrayImage = ImageBuffer, Vec>; /// Sendable grayscale + alpha channel image buffer pub type GrayAlphaImage = ImageBuffer, Vec>; /// Sendable 16-bit Rgb image buffer pub(crate) type Rgb16Image = ImageBuffer, Vec>; /// Sendable 16-bit Rgb + alpha channel image buffer pub(crate) type Rgba16Image = ImageBuffer, Vec>; /// Sendable 16-bit grayscale image buffer pub(crate) type Gray16Image = ImageBuffer, Vec>; /// Sendable 16-bit grayscale + alpha channel image buffer pub(crate) type GrayAlpha16Image = ImageBuffer, Vec>; /// An image buffer for 32-bit float RGB pixels, /// where the backing container is a flattened vector of floats. pub type Rgb32FImage = ImageBuffer, Vec>; /// An image buffer for 32-bit float RGBA pixels, /// where the backing container is a flattened vector of floats. pub type Rgba32FImage = ImageBuffer, Vec>; #[cfg(test)] mod test { use super::{GrayImage, ImageBuffer, ImageOutputFormat, RgbImage}; use crate::math::Rect; use crate::GenericImage as _; use crate::{color, Rgb}; #[test] /// Tests if image buffers from slices work fn slice_buffer() { let data = [0; 9]; let buf: ImageBuffer, _> = ImageBuffer::from_raw(3, 3, &data[..]).unwrap(); assert_eq!(&*buf, &data[..]) } #[test] fn get_pixel() { let mut a: RgbImage = ImageBuffer::new(10, 10); { let b = a.get_mut(3 * 10).unwrap(); *b = 255; } assert_eq!(a.get_pixel(0, 1)[0], 255) } #[test] fn get_pixel_checked() { let mut a: RgbImage = ImageBuffer::new(10, 10); a.get_pixel_mut_checked(0, 1).map(|b| b[0] = 255); assert_eq!(a.get_pixel_checked(0, 1), Some(&Rgb([255, 0, 0]))); assert_eq!(a.get_pixel_checked(0, 1).unwrap(), a.get_pixel(0, 1)); assert_eq!(a.get_pixel_checked(10, 0), None); assert_eq!(a.get_pixel_checked(0, 10), None); assert_eq!(a.get_pixel_mut_checked(10, 0), None); assert_eq!(a.get_pixel_mut_checked(0, 10), None); // From image/issues/1672 const WHITE: Rgb = Rgb([255_u8, 255, 255]); let mut a = RgbImage::new(2, 1); a.put_pixel(1, 0, WHITE); assert_eq!(a.get_pixel_checked(1, 0), Some(&WHITE)); assert_eq!(a.get_pixel_checked(1, 0).unwrap(), a.get_pixel(1, 0)); } #[test] fn mut_iter() { let mut a: RgbImage = ImageBuffer::new(10, 10); { let val = a.pixels_mut().next().unwrap(); *val = Rgb([42, 0, 0]); } assert_eq!(a.data[0], 42) } #[test] fn zero_width_zero_height() { let mut image = RgbImage::new(0, 0); assert_eq!(image.rows_mut().count(), 0); assert_eq!(image.pixels_mut().count(), 0); assert_eq!(image.rows().count(), 0); assert_eq!(image.pixels().count(), 0); } #[test] fn zero_width_nonzero_height() { let mut image = RgbImage::new(0, 2); assert_eq!(image.rows_mut().count(), 0); assert_eq!(image.pixels_mut().count(), 0); assert_eq!(image.rows().count(), 0); assert_eq!(image.pixels().count(), 0); } #[test] fn nonzero_width_zero_height() { let mut image = RgbImage::new(2, 0); assert_eq!(image.rows_mut().count(), 0); assert_eq!(image.pixels_mut().count(), 0); assert_eq!(image.rows().count(), 0); assert_eq!(image.pixels().count(), 0); } #[test] fn pixels_on_large_buffer() { let mut image = RgbImage::from_raw(1, 1, vec![0; 6]).unwrap(); assert_eq!(image.pixels().count(), 1); assert_eq!(image.enumerate_pixels().count(), 1); assert_eq!(image.pixels_mut().count(), 1); assert_eq!(image.enumerate_pixels_mut().count(), 1); assert_eq!(image.rows().count(), 1); assert_eq!(image.rows_mut().count(), 1); } #[test] fn default() { let image = ImageBuffer::, Vec>::default(); assert_eq!(image.dimensions(), (0, 0)); } #[test] #[rustfmt::skip] fn test_image_buffer_copy_within_oob() { let mut image: GrayImage = ImageBuffer::from_raw(4, 4, vec![0u8; 16]).unwrap(); assert!(!image.copy_within(Rect { x: 0, y: 0, width: 5, height: 4 }, 0, 0)); assert!(!image.copy_within(Rect { x: 0, y: 0, width: 4, height: 5 }, 0, 0)); assert!(!image.copy_within(Rect { x: 1, y: 0, width: 4, height: 4 }, 0, 0)); assert!(!image.copy_within(Rect { x: 0, y: 0, width: 4, height: 4 }, 1, 0)); assert!(!image.copy_within(Rect { x: 0, y: 1, width: 4, height: 4 }, 0, 0)); assert!(!image.copy_within(Rect { x: 0, y: 0, width: 4, height: 4 }, 0, 1)); assert!(!image.copy_within(Rect { x: 1, y: 1, width: 4, height: 4 }, 0, 0)); } #[test] fn test_image_buffer_copy_within_tl() { let data = &[ 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15, ]; let expected = [ 00, 01, 02, 03, 04, 00, 01, 02, 08, 04, 05, 06, 12, 08, 09, 10, ]; let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); assert!(image.copy_within( Rect { x: 0, y: 0, width: 3, height: 3 }, 1, 1 )); assert_eq!(&image.into_raw(), &expected); } #[test] fn test_image_buffer_copy_within_tr() { let data = &[ 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15, ]; let expected = [ 00, 01, 02, 03, 01, 02, 03, 07, 05, 06, 07, 11, 09, 10, 11, 15, ]; let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); assert!(image.copy_within( Rect { x: 1, y: 0, width: 3, height: 3 }, 0, 1 )); assert_eq!(&image.into_raw(), &expected); } #[test] fn test_image_buffer_copy_within_bl() { let data = &[ 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15, ]; let expected = [ 00, 04, 05, 06, 04, 08, 09, 10, 08, 12, 13, 14, 12, 13, 14, 15, ]; let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); assert!(image.copy_within( Rect { x: 0, y: 1, width: 3, height: 3 }, 1, 0 )); assert_eq!(&image.into_raw(), &expected); } #[test] fn test_image_buffer_copy_within_br() { let data = &[ 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15, ]; let expected = [ 05, 06, 07, 03, 09, 10, 11, 07, 13, 14, 15, 11, 12, 13, 14, 15, ]; let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); assert!(image.copy_within( Rect { x: 1, y: 1, width: 3, height: 3 }, 0, 0 )); assert_eq!(&image.into_raw(), &expected); } #[test] #[cfg(feature = "png")] fn write_to_with_large_buffer() { // A buffer of 1 pixel, padded to 4 bytes as would be common in, e.g. BMP. let img: GrayImage = ImageBuffer::from_raw(1, 1, vec![0u8; 4]).unwrap(); let mut buffer = std::io::Cursor::new(vec![]); assert!(img.write_to(&mut buffer, ImageOutputFormat::Png).is_ok()); } #[test] fn exact_size_iter_size_hint() { // The docs for `std::iter::ExactSizeIterator` requires that the implementation of // `size_hint` on the iterator returns the same value as the `len` implementation. // This test should work for any size image. const N: u32 = 10; let mut image = RgbImage::from_raw(N, N, vec![0; (N * N * 3) as usize]).unwrap(); let iter = image.pixels(); let exact_len = ExactSizeIterator::len(&iter); assert_eq!(iter.size_hint(), (exact_len, Some(exact_len))); let iter = image.pixels_mut(); let exact_len = ExactSizeIterator::len(&iter); assert_eq!(iter.size_hint(), (exact_len, Some(exact_len))); let iter = image.rows(); let exact_len = ExactSizeIterator::len(&iter); assert_eq!(iter.size_hint(), (exact_len, Some(exact_len))); let iter = image.rows_mut(); let exact_len = ExactSizeIterator::len(&iter); assert_eq!(iter.size_hint(), (exact_len, Some(exact_len))); let iter = image.enumerate_pixels(); let exact_len = ExactSizeIterator::len(&iter); assert_eq!(iter.size_hint(), (exact_len, Some(exact_len))); let iter = image.enumerate_rows(); let exact_len = ExactSizeIterator::len(&iter); assert_eq!(iter.size_hint(), (exact_len, Some(exact_len))); let iter = image.enumerate_pixels_mut(); let exact_len = ExactSizeIterator::len(&iter); assert_eq!(iter.size_hint(), (exact_len, Some(exact_len))); let iter = image.enumerate_rows_mut(); let exact_len = ExactSizeIterator::len(&iter); assert_eq!(iter.size_hint(), (exact_len, Some(exact_len))); } } #[cfg(test)] #[cfg(feature = "benchmarks")] mod benchmarks { use super::{ConvertBuffer, GrayImage, ImageBuffer, Pixel, RgbImage}; use crate::GenericImage; use test; #[bench] fn conversion(b: &mut test::Bencher) { let mut a: RgbImage = ImageBuffer::new(1000, 1000); for p in a.pixels_mut() { let rgb = p.channels_mut(); rgb[0] = 255; rgb[1] = 23; rgb[2] = 42; } assert!(a.data[0] != 0); b.iter(|| { let b: GrayImage = a.convert(); assert!(0 != b.data[0]); assert!(a.data[0] != b.data[0]); test::black_box(b); }); b.bytes = 1000 * 1000 * 3 } #[bench] fn image_access_row_by_row(b: &mut test::Bencher) { let mut a: RgbImage = ImageBuffer::new(1000, 1000); for p in a.pixels_mut() { let rgb = p.channels_mut(); rgb[0] = 255; rgb[1] = 23; rgb[2] = 42; } b.iter(move || { let image: &RgbImage = test::black_box(&a); let mut sum: usize = 0; for y in 0..1000 { for x in 0..1000 { let pixel = image.get_pixel(x, y); sum = sum.wrapping_add(pixel[0] as usize); sum = sum.wrapping_add(pixel[1] as usize); sum = sum.wrapping_add(pixel[2] as usize); } } test::black_box(sum) }); b.bytes = 1000 * 1000 * 3; } #[bench] fn image_access_col_by_col(b: &mut test::Bencher) { let mut a: RgbImage = ImageBuffer::new(1000, 1000); for p in a.pixels_mut() { let rgb = p.channels_mut(); rgb[0] = 255; rgb[1] = 23; rgb[2] = 42; } b.iter(move || { let image: &RgbImage = test::black_box(&a); let mut sum: usize = 0; for x in 0..1000 { for y in 0..1000 { let pixel = image.get_pixel(x, y); sum = sum.wrapping_add(pixel[0] as usize); sum = sum.wrapping_add(pixel[1] as usize); sum = sum.wrapping_add(pixel[2] as usize); } } test::black_box(sum) }); b.bytes = 1000 * 1000 * 3; } } image-0.24.7/src/codecs/avif/decoder.rs000064400000000000000000000136551046102023000157410ustar 00000000000000//! Decoding of AVIF images. /// /// The [AVIF] specification defines an image derivative of the AV1 bitstream, an open video codec. /// /// [AVIF]: https://aomediacodec.github.io/av1-avif/ use std::convert::TryFrom; use std::error::Error; use std::io::{self, Cursor, Read}; use std::marker::PhantomData; use std::mem; use crate::error::DecodingError; use crate::{ColorType, ImageDecoder, ImageError, ImageFormat, ImageResult}; use dav1d::{PixelLayout, PlanarImageComponent}; use dcv_color_primitives as dcp; use mp4parse::{read_avif, ParseStrictness}; fn error_map>>(err: E) -> ImageError { ImageError::Decoding(DecodingError::new(ImageFormat::Avif.into(), err)) } /// AVIF Decoder. /// /// Reads one image into the chosen input. pub struct AvifDecoder { inner: PhantomData, picture: dav1d::Picture, alpha_picture: Option, icc_profile: Option>, } impl AvifDecoder { /// Create a new decoder that reads its input from `r`. pub fn new(mut r: R) -> ImageResult { let ctx = read_avif(&mut r, ParseStrictness::Normal).map_err(error_map)?; let coded = ctx.primary_item_coded_data().unwrap_or_default(); let mut primary_decoder = dav1d::Decoder::new(); primary_decoder .send_data(coded, None, None, None) .map_err(error_map)?; let picture = primary_decoder.get_picture().map_err(error_map)?; let alpha_item = ctx.alpha_item_coded_data().unwrap_or_default(); let alpha_picture = if !alpha_item.is_empty() { let mut alpha_decoder = dav1d::Decoder::new(); alpha_decoder .send_data(alpha_item, None, None, None) .map_err(error_map)?; Some(alpha_decoder.get_picture().map_err(error_map)?) } else { None }; let icc_profile = ctx .icc_colour_information() .map(|x| x.ok().unwrap_or_default()) .map(|x| x.to_vec()); assert_eq!(picture.bit_depth(), 8); Ok(AvifDecoder { inner: PhantomData, picture, alpha_picture, icc_profile, }) } } /// Wrapper struct around a `Cursor>` pub struct AvifReader(Cursor>, PhantomData); impl Read for AvifReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { if self.0.position() == 0 && buf.is_empty() { mem::swap(buf, self.0.get_mut()); Ok(buf.len()) } else { self.0.read_to_end(buf) } } } impl<'a, R: 'a + Read> ImageDecoder<'a> for AvifDecoder { type Reader = AvifReader; fn dimensions(&self) -> (u32, u32) { (self.picture.width(), self.picture.height()) } fn color_type(&self) -> ColorType { ColorType::Rgba8 } fn icc_profile(&mut self) -> Option> { self.icc_profile.clone() } fn into_reader(self) -> ImageResult { let plane = self.picture.plane(PlanarImageComponent::Y); Ok(AvifReader( Cursor::new(plane.as_ref().to_vec()), PhantomData, )) } fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); dcp::initialize(); if self.picture.pixel_layout() != PixelLayout::I400 { let pixel_format = match self.picture.pixel_layout() { PixelLayout::I400 => todo!(), PixelLayout::I420 => dcp::PixelFormat::I420, PixelLayout::I422 => dcp::PixelFormat::I422, PixelLayout::I444 => dcp::PixelFormat::I444, PixelLayout::Unknown => panic!("Unknown pixel layout"), }; let src_format = dcp::ImageFormat { pixel_format, color_space: dcp::ColorSpace::Bt601, num_planes: 3, }; let dst_format = dcp::ImageFormat { pixel_format: dcp::PixelFormat::Rgba, color_space: dcp::ColorSpace::Lrgb, num_planes: 1, }; let (width, height) = self.dimensions(); let planes = &[ self.picture.plane(PlanarImageComponent::Y), self.picture.plane(PlanarImageComponent::U), self.picture.plane(PlanarImageComponent::V), ]; let src_buffers = planes.iter().map(AsRef::as_ref).collect::>(); let strides = &[ self.picture.stride(PlanarImageComponent::Y) as usize, self.picture.stride(PlanarImageComponent::U) as usize, self.picture.stride(PlanarImageComponent::V) as usize, ]; let dst_buffers = &mut [&mut buf[..]]; dcp::convert_image( width, height, &src_format, Some(strides), &src_buffers, &dst_format, None, dst_buffers, ) .map_err(error_map)?; } else { let plane = self.picture.plane(PlanarImageComponent::Y); buf.copy_from_slice(plane.as_ref()); } if let Some(picture) = self.alpha_picture { assert_eq!(picture.pixel_layout(), PixelLayout::I400); let stride = picture.stride(PlanarImageComponent::Y) as usize; let plane = picture.plane(PlanarImageComponent::Y); let width = picture.width(); for (buf, slice) in Iterator::zip( buf.chunks_exact_mut(width as usize * 4), plane.as_ref().chunks_exact(stride), ) { for i in 0..width as usize { buf[3 + i * 4] = slice[i]; } } } Ok(()) } } image-0.24.7/src/codecs/avif/encoder.rs000064400000000000000000000251451046102023000157500ustar 00000000000000//! Encoding of AVIF images. /// /// The [AVIF] specification defines an image derivative of the AV1 bitstream, an open video codec. /// /// [AVIF]: https://aomediacodec.github.io/av1-avif/ use std::borrow::Cow; use std::cmp::min; use std::io::Write; use crate::buffer::ConvertBuffer; use crate::color::{FromColor, Luma, LumaA, Rgb, Rgba}; use crate::error::{ EncodingError, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, }; use crate::{ColorType, ImageBuffer, ImageEncoder, ImageFormat, Pixel}; use crate::{ImageError, ImageResult}; use bytemuck::{try_cast_slice, try_cast_slice_mut, Pod, PodCastError}; use num_traits::Zero; use ravif::{Encoder, Img, RGB8, RGBA8}; use rgb::AsPixels; /// AVIF Encoder. /// /// Writes one image into the chosen output. pub struct AvifEncoder { inner: W, encoder: Encoder, } /// An enumeration over supported AVIF color spaces #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum ColorSpace { /// sRGB colorspace Srgb, /// BT.709 colorspace Bt709, } impl ColorSpace { fn to_ravif(self) -> ravif::ColorSpace { match self { Self::Srgb => ravif::ColorSpace::RGB, Self::Bt709 => ravif::ColorSpace::YCbCr, } } } enum RgbColor<'buf> { Rgb8(Img<&'buf [RGB8]>), Rgba8(Img<&'buf [RGBA8]>), } impl AvifEncoder { /// Create a new encoder that writes its output to `w`. pub fn new(w: W) -> Self { AvifEncoder::new_with_speed_quality(w, 4, 80) // `cavif` uses these defaults } /// Create a new encoder with specified speed and quality, that writes its output to `w`. /// `speed` accepts a value in the range 0-10, where 0 is the slowest and 10 is the fastest. /// `quality` accepts a value in the range 0-100, where 0 is the worst and 100 is the best. pub fn new_with_speed_quality(w: W, speed: u8, quality: u8) -> Self { // Clamp quality and speed to range let quality = min(quality, 100); let speed = min(speed, 10); let encoder = Encoder::new() .with_quality(f32::from(quality)) .with_alpha_quality(f32::from(quality)) .with_speed(speed); AvifEncoder { inner: w, encoder } } /// Encode with the specified `color_space`. pub fn with_colorspace(mut self, color_space: ColorSpace) -> Self { self.encoder = self .encoder .with_internal_color_space(color_space.to_ravif()); self } /// Configures `rayon` thread pool size. /// The default `None` is to use all threads in the default `rayon` thread pool. pub fn with_num_threads(mut self, num_threads: Option) -> Self { self.encoder = self.encoder.with_num_threads(num_threads); self } } impl ImageEncoder for AvifEncoder { /// Encode image data with the indicated color type. /// /// The encoder currently requires all data to be RGBA8, it will be converted internally if /// necessary. When data is suitably aligned, i.e. u16 channels to two bytes, then the /// conversion may be more efficient. fn write_image( mut self, data: &[u8], width: u32, height: u32, color: ColorType, ) -> ImageResult<()> { self.set_color(color); // `ravif` needs strongly typed data so let's convert. We can either use a temporarily // owned version in our own buffer or zero-copy if possible by using the input buffer. // This requires going through `rgb`. let mut fallback = vec![]; // This vector is used if we need to do a color conversion. let result = match Self::encode_as_img(&mut fallback, data, width, height, color)? { RgbColor::Rgb8(buffer) => self.encoder.encode_rgb(buffer), RgbColor::Rgba8(buffer) => self.encoder.encode_rgba(buffer), }; let data = result.map_err(|err| { ImageError::Encoding(EncodingError::new(ImageFormat::Avif.into(), err)) })?; self.inner.write_all(&data.avif_file)?; Ok(()) } } impl AvifEncoder { // Does not currently do anything. Mirrors behaviour of old config function. fn set_color(&mut self, _color: ColorType) { // self.config.color_space = ColorSpace::RGB; } fn encode_as_img<'buf>( fallback: &'buf mut Vec, data: &'buf [u8], width: u32, height: u32, color: ColorType, ) -> ImageResult> { // Error wrapping utility for color dependent buffer dimensions. fn try_from_raw( data: &[P::Subpixel], width: u32, height: u32, ) -> ImageResult> { ImageBuffer::from_raw(width, height, data).ok_or_else(|| { ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, )) }) } // Convert to target color type using few buffer allocations. fn convert_into<'buf, P>( buf: &'buf mut Vec, image: ImageBuffer, ) -> Img<&'buf [RGBA8]> where P: Pixel + 'static, Rgba: FromColor

, { let (width, height) = image.dimensions(); // TODO: conversion re-using the target buffer? let image: ImageBuffer, _> = image.convert(); *buf = image.into_raw(); Img::new(buf.as_pixels(), width as usize, height as usize) } // Cast the input slice using few buffer allocations if possible. // In particular try not to allocate if the caller did the infallible reverse. fn cast_buffer(buf: &[u8]) -> ImageResult> where Channel: Pod + Zero, { match try_cast_slice(buf) { Ok(slice) => Ok(Cow::Borrowed(slice)), Err(PodCastError::OutputSliceWouldHaveSlop) => Err(ImageError::Parameter( ParameterError::from_kind(ParameterErrorKind::DimensionMismatch), )), Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) => { // Sad, but let's allocate. // bytemuck checks alignment _before_ slop but size mismatch before this.. if buf.len() % std::mem::size_of::() != 0 { Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))) } else { let len = buf.len() / std::mem::size_of::(); let mut data = vec![Channel::zero(); len]; let view = try_cast_slice_mut::<_, u8>(data.as_mut_slice()).unwrap(); view.copy_from_slice(buf); Ok(Cow::Owned(data)) } } Err(err) => { // Are you trying to encode a ZST?? Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::Generic(format!("{:?}", err)), ))) } } } match color { ColorType::Rgb8 => { // ravif doesn't do any checks but has some asserts, so we do the checks. let img = try_from_raw::>(data, width, height)?; // Now, internally ravif uses u32 but it takes usize. We could do some checked // conversion but instead we use that a non-empty image must be addressable. if img.pixels().len() == 0 { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))); } Ok(RgbColor::Rgb8(Img::new( rgb::AsPixels::as_pixels(data), width as usize, height as usize, ))) } ColorType::Rgba8 => { // ravif doesn't do any checks but has some asserts, so we do the checks. let img = try_from_raw::>(data, width, height)?; // Now, internally ravif uses u32 but it takes usize. We could do some checked // conversion but instead we use that a non-empty image must be addressable. if img.pixels().len() == 0 { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))); } Ok(RgbColor::Rgba8(Img::new( rgb::AsPixels::as_pixels(data), width as usize, height as usize, ))) } // we need a separate buffer.. ColorType::L8 => { let image = try_from_raw::>(data, width, height)?; Ok(RgbColor::Rgba8(convert_into(fallback, image))) } ColorType::La8 => { let image = try_from_raw::>(data, width, height)?; Ok(RgbColor::Rgba8(convert_into(fallback, image))) } // we need to really convert data.. ColorType::L16 => { let buffer = cast_buffer(data)?; let image = try_from_raw::>(&buffer, width, height)?; Ok(RgbColor::Rgba8(convert_into(fallback, image))) } ColorType::La16 => { let buffer = cast_buffer(data)?; let image = try_from_raw::>(&buffer, width, height)?; Ok(RgbColor::Rgba8(convert_into(fallback, image))) } ColorType::Rgb16 => { let buffer = cast_buffer(data)?; let image = try_from_raw::>(&buffer, width, height)?; Ok(RgbColor::Rgba8(convert_into(fallback, image))) } ColorType::Rgba16 => { let buffer = cast_buffer(data)?; let image = try_from_raw::>(&buffer, width, height)?; Ok(RgbColor::Rgba8(convert_into(fallback, image))) } // for cases we do not support at all? _ => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Avif.into(), UnsupportedErrorKind::Color(color.into()), ), )), } } } image-0.24.7/src/codecs/avif/mod.rs000064400000000000000000000006631046102023000151060ustar 00000000000000//! Encoding of AVIF images. /// /// The [AVIF] specification defines an image derivative of the AV1 bitstream, an open video codec. /// /// [AVIF]: https://aomediacodec.github.io/av1-avif/ #[cfg(feature = "avif-decoder")] pub use self::decoder::AvifDecoder; #[cfg(feature = "avif-encoder")] pub use self::encoder::{AvifEncoder, ColorSpace}; #[cfg(feature = "avif-decoder")] mod decoder; #[cfg(feature = "avif-encoder")] mod encoder; image-0.24.7/src/codecs/bmp/decoder.rs000064400000000000000000001464341046102023000155740ustar 00000000000000use std::cmp::{self, Ordering}; use std::convert::TryFrom; use std::io::{self, Cursor, Read, Seek, SeekFrom}; use std::iter::{repeat, Iterator, Rev}; use std::marker::PhantomData; use std::slice::ChunksMut; use std::{error, fmt, mem}; use byteorder::{LittleEndian, ReadBytesExt}; use crate::color::ColorType; use crate::error::{ DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{self, ImageDecoder, ImageDecoderRect, ImageFormat, Progress}; const BITMAPCOREHEADER_SIZE: u32 = 12; const BITMAPINFOHEADER_SIZE: u32 = 40; const BITMAPV2HEADER_SIZE: u32 = 52; const BITMAPV3HEADER_SIZE: u32 = 56; const BITMAPV4HEADER_SIZE: u32 = 108; const BITMAPV5HEADER_SIZE: u32 = 124; static LOOKUP_TABLE_3_BIT_TO_8_BIT: [u8; 8] = [0, 36, 73, 109, 146, 182, 219, 255]; static LOOKUP_TABLE_4_BIT_TO_8_BIT: [u8; 16] = [ 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255, ]; static LOOKUP_TABLE_5_BIT_TO_8_BIT: [u8; 32] = [ 0, 8, 16, 25, 33, 41, 49, 58, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165, 173, 181, 189, 197, 206, 214, 222, 230, 239, 247, 255, ]; static LOOKUP_TABLE_6_BIT_TO_8_BIT: [u8; 64] = [ 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 150, 154, 158, 162, 166, 170, 174, 178, 182, 186, 190, 194, 198, 202, 206, 210, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255, ]; static R5_G5_B5_COLOR_MASK: Bitfields = Bitfields { r: Bitfield { len: 5, shift: 10 }, g: Bitfield { len: 5, shift: 5 }, b: Bitfield { len: 5, shift: 0 }, a: Bitfield { len: 0, shift: 0 }, }; const R8_G8_B8_COLOR_MASK: Bitfields = Bitfields { r: Bitfield { len: 8, shift: 24 }, g: Bitfield { len: 8, shift: 16 }, b: Bitfield { len: 8, shift: 8 }, a: Bitfield { len: 0, shift: 0 }, }; const R8_G8_B8_A8_COLOR_MASK: Bitfields = Bitfields { r: Bitfield { len: 8, shift: 16 }, g: Bitfield { len: 8, shift: 8 }, b: Bitfield { len: 8, shift: 0 }, a: Bitfield { len: 8, shift: 24 }, }; const RLE_ESCAPE: u8 = 0; const RLE_ESCAPE_EOL: u8 = 0; const RLE_ESCAPE_EOF: u8 = 1; const RLE_ESCAPE_DELTA: u8 = 2; /// The maximum width/height the decoder will process. const MAX_WIDTH_HEIGHT: i32 = 0xFFFF; #[derive(PartialEq, Copy, Clone)] enum ImageType { Palette, RGB16, RGB24, RGB32, RGBA32, RLE8, RLE4, Bitfields16, Bitfields32, } #[derive(PartialEq)] enum BMPHeaderType { Core, Info, V2, V3, V4, V5, } #[derive(PartialEq)] enum FormatFullBytes { RGB24, RGB32, RGBA32, Format888, } enum Chunker<'a> { FromTop(ChunksMut<'a, u8>), FromBottom(Rev>), } pub(crate) struct RowIterator<'a> { chunks: Chunker<'a>, } impl<'a> Iterator for RowIterator<'a> { type Item = &'a mut [u8]; #[inline(always)] fn next(&mut self) -> Option<&'a mut [u8]> { match self.chunks { Chunker::FromTop(ref mut chunks) => chunks.next(), Chunker::FromBottom(ref mut chunks) => chunks.next(), } } } /// All errors that can occur when attempting to parse a BMP #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] enum DecoderError { // Failed to decompress RLE data. CorruptRleData, /// The bitfield mask interleaves set and unset bits BitfieldMaskNonContiguous, /// Bitfield mask invalid (e.g. too long for specified type) BitfieldMaskInvalid, /// Bitfield (of the specified width – 16- or 32-bit) mask not present BitfieldMaskMissing(u32), /// Bitfield (of the specified width – 16- or 32-bit) masks not present BitfieldMasksMissing(u32), /// BMP's "BM" signature wrong or missing BmpSignatureInvalid, /// More than the exactly one allowed plane specified by the format MoreThanOnePlane, /// Invalid amount of bits per channel for the specified image type InvalidChannelWidth(ChannelWidthError, u16), /// The width is negative NegativeWidth(i32), /// One of the dimensions is larger than a soft limit ImageTooLarge(i32, i32), /// The height is `i32::min_value()` /// /// General negative heights specify top-down DIBs InvalidHeight, /// Specified image type is invalid for top-down BMPs (i.e. is compressed) ImageTypeInvalidForTopDown(u32), /// Image type not currently recognized by the decoder ImageTypeUnknown(u32), /// Bitmap header smaller than the core header HeaderTooSmall(u32), /// The palette is bigger than allowed by the bit count of the BMP PaletteSizeExceeded { colors_used: u32, bit_count: u16, }, } impl fmt::Display for DecoderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DecoderError::CorruptRleData => f.write_str("Corrupt RLE data"), DecoderError::BitfieldMaskNonContiguous => f.write_str("Non-contiguous bitfield mask"), DecoderError::BitfieldMaskInvalid => f.write_str("Invalid bitfield mask"), DecoderError::BitfieldMaskMissing(bb) => { f.write_fmt(format_args!("Missing {}-bit bitfield mask", bb)) } DecoderError::BitfieldMasksMissing(bb) => { f.write_fmt(format_args!("Missing {}-bit bitfield masks", bb)) } DecoderError::BmpSignatureInvalid => f.write_str("BMP signature not found"), DecoderError::MoreThanOnePlane => f.write_str("More than one plane"), DecoderError::InvalidChannelWidth(tp, n) => { f.write_fmt(format_args!("Invalid channel bit count for {}: {}", tp, n)) } DecoderError::NegativeWidth(w) => f.write_fmt(format_args!("Negative width ({})", w)), DecoderError::ImageTooLarge(w, h) => f.write_fmt(format_args!( "Image too large (one of ({}, {}) > soft limit of {})", w, h, MAX_WIDTH_HEIGHT )), DecoderError::InvalidHeight => f.write_str("Invalid height"), DecoderError::ImageTypeInvalidForTopDown(tp) => f.write_fmt(format_args!( "Invalid image type {} for top-down image.", tp )), DecoderError::ImageTypeUnknown(tp) => { f.write_fmt(format_args!("Unknown image compression type {}", tp)) } DecoderError::HeaderTooSmall(s) => { f.write_fmt(format_args!("Bitmap header too small ({} bytes)", s)) } DecoderError::PaletteSizeExceeded { colors_used, bit_count, } => f.write_fmt(format_args!( "Palette size {} exceeds maximum size for BMP with bit count of {}", colors_used, bit_count )), } } } impl From for ImageError { fn from(e: DecoderError) -> ImageError { ImageError::Decoding(DecodingError::new(ImageFormat::Bmp.into(), e)) } } impl error::Error for DecoderError {} /// Distinct image types whose saved channel width can be invalid #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] enum ChannelWidthError { /// RGB Rgb, /// 8-bit run length encoding Rle8, /// 4-bit run length encoding Rle4, /// Bitfields (16- or 32-bit) Bitfields, } impl fmt::Display for ChannelWidthError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { ChannelWidthError::Rgb => "RGB", ChannelWidthError::Rle8 => "RLE8", ChannelWidthError::Rle4 => "RLE4", ChannelWidthError::Bitfields => "bitfields", }) } } /// Convenience function to check if the combination of width, length and number of /// channels would result in a buffer that would overflow. fn check_for_overflow(width: i32, length: i32, channels: usize) -> ImageResult<()> { num_bytes(width, length, channels) .map(|_| ()) .ok_or_else(|| { ImageError::Unsupported(UnsupportedError::from_format_and_kind( ImageFormat::Bmp.into(), UnsupportedErrorKind::GenericFeature(format!( "Image dimensions ({}x{} w/{} channels) are too large", width, length, channels )), )) }) } /// Calculate how many many bytes a buffer holding a decoded image with these properties would /// require. Returns `None` if the buffer size would overflow or if one of the sizes are negative. fn num_bytes(width: i32, length: i32, channels: usize) -> Option { if width <= 0 || length <= 0 { None } else { match channels.checked_mul(width as usize) { Some(n) => n.checked_mul(length as usize), None => None, } } } /// Call the provided function on each row of the provided buffer, returning Err if the provided /// function returns an error, extends the buffer if it's not large enough. fn with_rows( buffer: &mut [u8], width: i32, height: i32, channels: usize, top_down: bool, mut func: F, ) -> io::Result<()> where F: FnMut(&mut [u8]) -> io::Result<()>, { // An overflow should already have been checked for when this is called, // though we check anyhow, as it somehow seems to increase performance slightly. let row_width = channels.checked_mul(width as usize).unwrap(); let full_image_size = row_width.checked_mul(height as usize).unwrap(); assert_eq!(buffer.len(), full_image_size); if !top_down { for row in buffer.chunks_mut(row_width).rev() { func(row)?; } } else { for row in buffer.chunks_mut(row_width) { func(row)?; } } Ok(()) } fn set_8bit_pixel_run<'a, T: Iterator>( pixel_iter: &mut ChunksMut, palette: &[[u8; 3]], indices: T, n_pixels: usize, ) -> bool { for idx in indices.take(n_pixels) { if let Some(pixel) = pixel_iter.next() { let rgb = palette[*idx as usize]; pixel[0] = rgb[0]; pixel[1] = rgb[1]; pixel[2] = rgb[2]; } else { return false; } } true } fn set_4bit_pixel_run<'a, T: Iterator>( pixel_iter: &mut ChunksMut, palette: &[[u8; 3]], indices: T, mut n_pixels: usize, ) -> bool { for idx in indices { macro_rules! set_pixel { ($i:expr) => { if n_pixels == 0 { break; } if let Some(pixel) = pixel_iter.next() { let rgb = palette[$i as usize]; pixel[0] = rgb[0]; pixel[1] = rgb[1]; pixel[2] = rgb[2]; } else { return false; } n_pixels -= 1; }; } set_pixel!(idx >> 4); set_pixel!(idx & 0xf); } true } #[rustfmt::skip] fn set_2bit_pixel_run<'a, T: Iterator>( pixel_iter: &mut ChunksMut, palette: &[[u8; 3]], indices: T, mut n_pixels: usize, ) -> bool { for idx in indices { macro_rules! set_pixel { ($i:expr) => { if n_pixels == 0 { break; } if let Some(pixel) = pixel_iter.next() { let rgb = palette[$i as usize]; pixel[0] = rgb[0]; pixel[1] = rgb[1]; pixel[2] = rgb[2]; } else { return false; } n_pixels -= 1; }; } set_pixel!((idx >> 6) & 0x3u8); set_pixel!((idx >> 4) & 0x3u8); set_pixel!((idx >> 2) & 0x3u8); set_pixel!( idx & 0x3u8); } true } fn set_1bit_pixel_run<'a, T: Iterator>( pixel_iter: &mut ChunksMut, palette: &[[u8; 3]], indices: T, ) { for idx in indices { let mut bit = 0x80; loop { if let Some(pixel) = pixel_iter.next() { let rgb = palette[((idx & bit) != 0) as usize]; pixel[0] = rgb[0]; pixel[1] = rgb[1]; pixel[2] = rgb[2]; } else { return; } bit >>= 1; if bit == 0 { break; } } } } #[derive(PartialEq, Eq)] struct Bitfield { shift: u32, len: u32, } impl Bitfield { fn from_mask(mask: u32, max_len: u32) -> ImageResult { if mask == 0 { return Ok(Bitfield { shift: 0, len: 0 }); } let mut shift = mask.trailing_zeros(); let mut len = (!(mask >> shift)).trailing_zeros(); if len != mask.count_ones() { return Err(DecoderError::BitfieldMaskNonContiguous.into()); } if len + shift > max_len { return Err(DecoderError::BitfieldMaskInvalid.into()); } if len > 8 { shift += len - 8; len = 8; } Ok(Bitfield { shift, len }) } fn read(&self, data: u32) -> u8 { let data = data >> self.shift; match self.len { 1 => ((data & 0b1) * 0xff) as u8, 2 => ((data & 0b11) * 0x55) as u8, 3 => LOOKUP_TABLE_3_BIT_TO_8_BIT[(data & 0b00_0111) as usize], 4 => LOOKUP_TABLE_4_BIT_TO_8_BIT[(data & 0b00_1111) as usize], 5 => LOOKUP_TABLE_5_BIT_TO_8_BIT[(data & 0b01_1111) as usize], 6 => LOOKUP_TABLE_6_BIT_TO_8_BIT[(data & 0b11_1111) as usize], 7 => ((data & 0x7f) << 1 | (data & 0x7f) >> 6) as u8, 8 => (data & 0xff) as u8, _ => panic!(), } } } #[derive(PartialEq, Eq)] struct Bitfields { r: Bitfield, g: Bitfield, b: Bitfield, a: Bitfield, } impl Bitfields { fn from_mask( r_mask: u32, g_mask: u32, b_mask: u32, a_mask: u32, max_len: u32, ) -> ImageResult { let bitfields = Bitfields { r: Bitfield::from_mask(r_mask, max_len)?, g: Bitfield::from_mask(g_mask, max_len)?, b: Bitfield::from_mask(b_mask, max_len)?, a: Bitfield::from_mask(a_mask, max_len)?, }; if bitfields.r.len == 0 || bitfields.g.len == 0 || bitfields.b.len == 0 { return Err(DecoderError::BitfieldMaskMissing(max_len).into()); } Ok(bitfields) } } /// A bmp decoder pub struct BmpDecoder { reader: R, bmp_header_type: BMPHeaderType, indexed_color: bool, width: i32, height: i32, data_offset: u64, top_down: bool, no_file_header: bool, add_alpha_channel: bool, has_loaded_metadata: bool, image_type: ImageType, bit_count: u16, colors_used: u32, palette: Option>, bitfields: Option, } enum RLEInsn { EndOfFile, EndOfRow, Delta(u8, u8), Absolute(u8, Vec), PixelRun(u8, u8), } impl BmpDecoder { fn new_decoder(reader: R) -> BmpDecoder { BmpDecoder { reader, bmp_header_type: BMPHeaderType::Info, indexed_color: false, width: 0, height: 0, data_offset: 0, top_down: false, no_file_header: false, add_alpha_channel: false, has_loaded_metadata: false, image_type: ImageType::Palette, bit_count: 0, colors_used: 0, palette: None, bitfields: None, } } /// Create a new decoder that decodes from the stream ```r``` pub fn new(reader: R) -> ImageResult> { let mut decoder = Self::new_decoder(reader); decoder.read_metadata()?; Ok(decoder) } /// Create a new decoder that decodes from the stream ```r``` without first /// reading a BITMAPFILEHEADER. This is useful for decoding the CF_DIB format /// directly from the Windows clipboard. pub fn new_without_file_header(reader: R) -> ImageResult> { let mut decoder = Self::new_decoder(reader); decoder.no_file_header = true; decoder.read_metadata()?; Ok(decoder) } #[cfg(feature = "ico")] pub(crate) fn new_with_ico_format(reader: R) -> ImageResult> { let mut decoder = Self::new_decoder(reader); decoder.read_metadata_in_ico_format()?; Ok(decoder) } /// If true, the palette in BMP does not apply to the image even if it is found. /// In other words, the output image is the indexed color. pub fn set_indexed_color(&mut self, indexed_color: bool) { self.indexed_color = indexed_color; } #[cfg(feature = "ico")] pub(crate) fn reader(&mut self) -> &mut R { &mut self.reader } fn read_file_header(&mut self) -> ImageResult<()> { if self.no_file_header { return Ok(()); } let mut signature = [0; 2]; self.reader.read_exact(&mut signature)?; if signature != b"BM"[..] { return Err(DecoderError::BmpSignatureInvalid.into()); } // The next 8 bytes represent file size, followed the 4 reserved bytes // We're not interesting these values self.reader.read_u32::()?; self.reader.read_u32::()?; self.data_offset = u64::from(self.reader.read_u32::()?); Ok(()) } /// Read BITMAPCOREHEADER https://msdn.microsoft.com/en-us/library/vs/alm/dd183372(v=vs.85).aspx /// /// returns Err if any of the values are invalid. fn read_bitmap_core_header(&mut self) -> ImageResult<()> { // As height/width values in BMP files with core headers are only 16 bits long, // they won't be larger than `MAX_WIDTH_HEIGHT`. self.width = i32::from(self.reader.read_u16::()?); self.height = i32::from(self.reader.read_u16::()?); check_for_overflow(self.width, self.height, self.num_channels())?; // Number of planes (format specifies that this should be 1). if self.reader.read_u16::()? != 1 { return Err(DecoderError::MoreThanOnePlane.into()); } self.bit_count = self.reader.read_u16::()?; self.image_type = match self.bit_count { 1 | 4 | 8 => ImageType::Palette, 24 => ImageType::RGB24, _ => { return Err(DecoderError::InvalidChannelWidth( ChannelWidthError::Rgb, self.bit_count, ) .into()) } }; Ok(()) } /// Read BITMAPINFOHEADER https://msdn.microsoft.com/en-us/library/vs/alm/dd183376(v=vs.85).aspx /// or BITMAPV{2|3|4|5}HEADER. /// /// returns Err if any of the values are invalid. fn read_bitmap_info_header(&mut self) -> ImageResult<()> { self.width = self.reader.read_i32::()?; self.height = self.reader.read_i32::()?; // Width can not be negative if self.width < 0 { return Err(DecoderError::NegativeWidth(self.width).into()); } else if self.width > MAX_WIDTH_HEIGHT || self.height > MAX_WIDTH_HEIGHT { // Limit very large image sizes to avoid OOM issues. Images with these sizes are // unlikely to be valid anyhow. return Err(DecoderError::ImageTooLarge(self.width, self.height).into()); } if self.height == i32::min_value() { return Err(DecoderError::InvalidHeight.into()); } // A negative height indicates a top-down DIB. if self.height < 0 { self.height *= -1; self.top_down = true; } check_for_overflow(self.width, self.height, self.num_channels())?; // Number of planes (format specifies that this should be 1). if self.reader.read_u16::()? != 1 { return Err(DecoderError::MoreThanOnePlane.into()); } self.bit_count = self.reader.read_u16::()?; let image_type_u32 = self.reader.read_u32::()?; // Top-down dibs can not be compressed. if self.top_down && image_type_u32 != 0 && image_type_u32 != 3 { return Err(DecoderError::ImageTypeInvalidForTopDown(image_type_u32).into()); } self.image_type = match image_type_u32 { 0 => match self.bit_count { 1 | 2 | 4 | 8 => ImageType::Palette, 16 => ImageType::RGB16, 24 => ImageType::RGB24, 32 if self.add_alpha_channel => ImageType::RGBA32, 32 => ImageType::RGB32, _ => { return Err(DecoderError::InvalidChannelWidth( ChannelWidthError::Rgb, self.bit_count, ) .into()) } }, 1 => match self.bit_count { 8 => ImageType::RLE8, _ => { return Err(DecoderError::InvalidChannelWidth( ChannelWidthError::Rle8, self.bit_count, ) .into()) } }, 2 => match self.bit_count { 4 => ImageType::RLE4, _ => { return Err(DecoderError::InvalidChannelWidth( ChannelWidthError::Rle4, self.bit_count, ) .into()) } }, 3 => match self.bit_count { 16 => ImageType::Bitfields16, 32 => ImageType::Bitfields32, _ => { return Err(DecoderError::InvalidChannelWidth( ChannelWidthError::Bitfields, self.bit_count, ) .into()) } }, 4 => { // JPEG compression is not implemented yet. return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Bmp.into(), UnsupportedErrorKind::GenericFeature("JPEG compression".to_owned()), ), )); } 5 => { // PNG compression is not implemented yet. return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Bmp.into(), UnsupportedErrorKind::GenericFeature("PNG compression".to_owned()), ), )); } 11 | 12 | 13 => { // CMYK types are not implemented yet. return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Bmp.into(), UnsupportedErrorKind::GenericFeature("CMYK format".to_owned()), ), )); } _ => { // Unknown compression type. return Err(DecoderError::ImageTypeUnknown(image_type_u32).into()); } }; // The next 12 bytes represent data array size in bytes, // followed the horizontal and vertical printing resolutions // We will calculate the pixel array size using width & height of image // We're not interesting the horz or vert printing resolutions self.reader.read_u32::()?; self.reader.read_u32::()?; self.reader.read_u32::()?; self.colors_used = self.reader.read_u32::()?; // The next 4 bytes represent number of "important" colors // We're not interested in this value, so we'll skip it self.reader.read_u32::()?; Ok(()) } fn read_bitmasks(&mut self) -> ImageResult<()> { let r_mask = self.reader.read_u32::()?; let g_mask = self.reader.read_u32::()?; let b_mask = self.reader.read_u32::()?; let a_mask = match self.bmp_header_type { BMPHeaderType::V3 | BMPHeaderType::V4 | BMPHeaderType::V5 => { self.reader.read_u32::()? } _ => 0, }; self.bitfields = match self.image_type { ImageType::Bitfields16 => { Some(Bitfields::from_mask(r_mask, g_mask, b_mask, a_mask, 16)?) } ImageType::Bitfields32 => { Some(Bitfields::from_mask(r_mask, g_mask, b_mask, a_mask, 32)?) } _ => None, }; if self.bitfields.is_some() && a_mask != 0 { self.add_alpha_channel = true; } Ok(()) } fn read_metadata(&mut self) -> ImageResult<()> { if !self.has_loaded_metadata { self.read_file_header()?; let bmp_header_offset = self.reader.stream_position()?; let bmp_header_size = self.reader.read_u32::()?; let bmp_header_end = bmp_header_offset + u64::from(bmp_header_size); self.bmp_header_type = match bmp_header_size { BITMAPCOREHEADER_SIZE => BMPHeaderType::Core, BITMAPINFOHEADER_SIZE => BMPHeaderType::Info, BITMAPV2HEADER_SIZE => BMPHeaderType::V2, BITMAPV3HEADER_SIZE => BMPHeaderType::V3, BITMAPV4HEADER_SIZE => BMPHeaderType::V4, BITMAPV5HEADER_SIZE => BMPHeaderType::V5, _ if bmp_header_size < BITMAPCOREHEADER_SIZE => { // Size of any valid header types won't be smaller than core header type. return Err(DecoderError::HeaderTooSmall(bmp_header_size).into()); } _ => { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Bmp.into(), UnsupportedErrorKind::GenericFeature(format!( "Unknown bitmap header type (size={})", bmp_header_size )), ), )) } }; match self.bmp_header_type { BMPHeaderType::Core => { self.read_bitmap_core_header()?; } BMPHeaderType::Info | BMPHeaderType::V2 | BMPHeaderType::V3 | BMPHeaderType::V4 | BMPHeaderType::V5 => { self.read_bitmap_info_header()?; } }; match self.image_type { ImageType::Bitfields16 | ImageType::Bitfields32 => self.read_bitmasks()?, _ => {} }; self.reader.seek(SeekFrom::Start(bmp_header_end))?; match self.image_type { ImageType::Palette | ImageType::RLE4 | ImageType::RLE8 => self.read_palette()?, _ => {} }; if self.no_file_header { // Use the offset of the end of metadata instead of reading a BMP file header. self.data_offset = self.reader.stream_position()?; } self.has_loaded_metadata = true; } Ok(()) } #[cfg(feature = "ico")] #[doc(hidden)] pub fn read_metadata_in_ico_format(&mut self) -> ImageResult<()> { self.no_file_header = true; self.add_alpha_channel = true; self.read_metadata()?; // The height field in an ICO file is doubled to account for the AND mask // (whether or not an AND mask is actually present). self.height /= 2; Ok(()) } fn get_palette_size(&mut self) -> ImageResult { match self.colors_used { 0 => Ok(1 << self.bit_count), _ => { if self.colors_used > 1 << self.bit_count { return Err(DecoderError::PaletteSizeExceeded { colors_used: self.colors_used, bit_count: self.bit_count, } .into()); } Ok(self.colors_used as usize) } } } fn bytes_per_color(&self) -> usize { match self.bmp_header_type { BMPHeaderType::Core => 3, _ => 4, } } fn read_palette(&mut self) -> ImageResult<()> { const MAX_PALETTE_SIZE: usize = 256; // Palette indices are u8. let bytes_per_color = self.bytes_per_color(); let palette_size = self.get_palette_size()?; let max_length = MAX_PALETTE_SIZE * bytes_per_color; let length = palette_size * bytes_per_color; let mut buf = Vec::with_capacity(max_length); // Resize and read the palette entries to the buffer. // We limit the buffer to at most 256 colours to avoid any oom issues as // 8-bit images can't reference more than 256 indexes anyhow. buf.resize(cmp::min(length, max_length), 0); self.reader.by_ref().read_exact(&mut buf)?; // Allocate 256 entries even if palette_size is smaller, to prevent corrupt files from // causing an out-of-bounds array access. match length.cmp(&max_length) { Ordering::Greater => { self.reader .seek(SeekFrom::Current((length - max_length) as i64))?; } Ordering::Less => buf.resize(max_length, 0), Ordering::Equal => (), } let p: Vec<[u8; 3]> = (0..MAX_PALETTE_SIZE) .map(|i| { let b = buf[bytes_per_color * i]; let g = buf[bytes_per_color * i + 1]; let r = buf[bytes_per_color * i + 2]; [r, g, b] }) .collect(); self.palette = Some(p); Ok(()) } /// Get the palette that is embedded in the BMP image, if any. pub fn get_palette(&self) -> Option<&[[u8; 3]]> { self.palette.as_ref().map(|vec| &vec[..]) } fn num_channels(&self) -> usize { if self.indexed_color { 1 } else if self.add_alpha_channel { 4 } else { 3 } } fn rows<'a>(&self, pixel_data: &'a mut [u8]) -> RowIterator<'a> { let stride = self.width as usize * self.num_channels(); if self.top_down { RowIterator { chunks: Chunker::FromTop(pixel_data.chunks_mut(stride)), } } else { RowIterator { chunks: Chunker::FromBottom(pixel_data.chunks_mut(stride).rev()), } } } fn read_palettized_pixel_data(&mut self, buf: &mut [u8]) -> ImageResult<()> { let num_channels = self.num_channels(); let row_byte_length = ((i32::from(self.bit_count) * self.width + 31) / 32 * 4) as usize; let mut indices = vec![0; row_byte_length]; let palette = self.palette.as_ref().unwrap(); let bit_count = self.bit_count; let reader = &mut self.reader; let width = self.width as usize; let skip_palette = self.indexed_color; reader.seek(SeekFrom::Start(self.data_offset))?; if num_channels == 4 { buf.chunks_exact_mut(4).for_each(|c| c[3] = 0xFF); } with_rows( buf, self.width, self.height, num_channels, self.top_down, |row| { reader.read_exact(&mut indices)?; if skip_palette { row.clone_from_slice(&indices[0..width]); } else { let mut pixel_iter = row.chunks_mut(num_channels); match bit_count { 1 => { set_1bit_pixel_run(&mut pixel_iter, palette, indices.iter()); } 2 => { set_2bit_pixel_run(&mut pixel_iter, palette, indices.iter(), width); } 4 => { set_4bit_pixel_run(&mut pixel_iter, palette, indices.iter(), width); } 8 => { set_8bit_pixel_run(&mut pixel_iter, palette, indices.iter(), width); } _ => panic!(), }; } Ok(()) }, )?; Ok(()) } fn read_16_bit_pixel_data( &mut self, buf: &mut [u8], bitfields: Option<&Bitfields>, ) -> ImageResult<()> { let num_channels = self.num_channels(); let row_padding_len = self.width as usize % 2 * 2; let row_padding = &mut [0; 2][..row_padding_len]; let bitfields = match bitfields { Some(b) => b, None => self.bitfields.as_ref().unwrap(), }; let reader = &mut self.reader; reader.seek(SeekFrom::Start(self.data_offset))?; with_rows( buf, self.width, self.height, num_channels, self.top_down, |row| { for pixel in row.chunks_mut(num_channels) { let data = u32::from(reader.read_u16::()?); pixel[0] = bitfields.r.read(data); pixel[1] = bitfields.g.read(data); pixel[2] = bitfields.b.read(data); if num_channels == 4 { if bitfields.a.len != 0 { pixel[3] = bitfields.a.read(data); } else { pixel[3] = 0xFF; } } } reader.read_exact(row_padding) }, )?; Ok(()) } /// Read image data from a reader in 32-bit formats that use bitfields. fn read_32_bit_pixel_data(&mut self, buf: &mut [u8]) -> ImageResult<()> { let num_channels = self.num_channels(); let bitfields = self.bitfields.as_ref().unwrap(); let reader = &mut self.reader; reader.seek(SeekFrom::Start(self.data_offset))?; with_rows( buf, self.width, self.height, num_channels, self.top_down, |row| { for pixel in row.chunks_mut(num_channels) { let data = reader.read_u32::()?; pixel[0] = bitfields.r.read(data); pixel[1] = bitfields.g.read(data); pixel[2] = bitfields.b.read(data); if num_channels == 4 { if bitfields.a.len != 0 { pixel[3] = bitfields.a.read(data); } else { pixel[3] = 0xff; } } } Ok(()) }, )?; Ok(()) } /// Read image data from a reader where the colours are stored as 8-bit values (24 or 32-bit). fn read_full_byte_pixel_data( &mut self, buf: &mut [u8], format: &FormatFullBytes, ) -> ImageResult<()> { let num_channels = self.num_channels(); let row_padding_len = match *format { FormatFullBytes::RGB24 => (4 - (self.width as usize * 3) % 4) % 4, _ => 0, }; let row_padding = &mut [0; 4][..row_padding_len]; self.reader.seek(SeekFrom::Start(self.data_offset))?; let reader = &mut self.reader; with_rows( buf, self.width, self.height, num_channels, self.top_down, |row| { for pixel in row.chunks_mut(num_channels) { if *format == FormatFullBytes::Format888 { reader.read_u8()?; } // Read the colour values (b, g, r). // Reading 3 bytes and reversing them is significantly faster than reading one // at a time. reader.read_exact(&mut pixel[0..3])?; pixel[0..3].reverse(); if *format == FormatFullBytes::RGB32 { reader.read_u8()?; } // Read the alpha channel if present if *format == FormatFullBytes::RGBA32 { reader.read_exact(&mut pixel[3..4])?; } else if num_channels == 4 { pixel[3] = 0xFF; } } reader.read_exact(row_padding) }, )?; Ok(()) } fn read_rle_data(&mut self, buf: &mut [u8], image_type: ImageType) -> ImageResult<()> { // Seek to the start of the actual image data. self.reader.seek(SeekFrom::Start(self.data_offset))?; let num_channels = self.num_channels(); let p = self.palette.as_ref().unwrap(); // Handling deltas in the RLE scheme means that we need to manually // iterate through rows and pixels. Even if we didn't have to handle // deltas, we have to ensure that a single runlength doesn't straddle // two rows. let mut row_iter = self.rows(buf); while let Some(row) = row_iter.next() { let mut pixel_iter = row.chunks_mut(num_channels); let mut x = 0; loop { let instruction = { let control_byte = self.reader.read_u8()?; match control_byte { RLE_ESCAPE => { let op = self.reader.read_u8()?; match op { RLE_ESCAPE_EOL => RLEInsn::EndOfRow, RLE_ESCAPE_EOF => RLEInsn::EndOfFile, RLE_ESCAPE_DELTA => { let xdelta = self.reader.read_u8()?; let ydelta = self.reader.read_u8()?; RLEInsn::Delta(xdelta, ydelta) } _ => { let mut length = op as usize; if self.image_type == ImageType::RLE4 { length = (length + 1) / 2; } length += length & 1; let mut buffer = vec![0; length]; self.reader.read_exact(&mut buffer)?; RLEInsn::Absolute(op, buffer) } } } _ => { let palette_index = self.reader.read_u8()?; RLEInsn::PixelRun(control_byte, palette_index) } } }; match instruction { RLEInsn::EndOfFile => { pixel_iter.for_each(|p| p.fill(0)); row_iter.for_each(|r| r.fill(0)); return Ok(()); } RLEInsn::EndOfRow => { pixel_iter.for_each(|p| p.fill(0)); break; } RLEInsn::Delta(x_delta, y_delta) => { // The msdn site on bitmap compression doesn't specify // what happens to the values skipped when encountering // a delta code, however IE and the windows image // preview seems to replace them with black pixels, // so we stick to that. if y_delta > 0 { // Zero out the remainder of the current row. pixel_iter.for_each(|p| p.fill(0)); // If any full rows are skipped, zero them out. for _ in 1..y_delta { let row = row_iter.next().ok_or(DecoderError::CorruptRleData)?; row.fill(0); } // Set the pixel iterator to the start of the next row. pixel_iter = row_iter .next() .ok_or(DecoderError::CorruptRleData)? .chunks_mut(num_channels); // Zero out the pixels up to the current point in the row. for _ in 0..x { pixel_iter .next() .ok_or(DecoderError::CorruptRleData)? .fill(0); } } for _ in 0..x_delta { let pixel = pixel_iter.next().ok_or(DecoderError::CorruptRleData)?; pixel.fill(0); } x += x_delta as usize; } RLEInsn::Absolute(length, indices) => { // Absolute mode cannot span rows, so if we run // out of pixels to process, we should stop // processing the image. match image_type { ImageType::RLE8 => { if !set_8bit_pixel_run( &mut pixel_iter, p, indices.iter(), length as usize, ) { return Err(DecoderError::CorruptRleData.into()); } } ImageType::RLE4 => { if !set_4bit_pixel_run( &mut pixel_iter, p, indices.iter(), length as usize, ) { return Err(DecoderError::CorruptRleData.into()); } } _ => unreachable!(), } x += length as usize; } RLEInsn::PixelRun(n_pixels, palette_index) => { // A pixel run isn't allowed to span rows, but we // simply continue on to the next row if we run // out of pixels to set. match image_type { ImageType::RLE8 => { if !set_8bit_pixel_run( &mut pixel_iter, p, repeat(&palette_index), n_pixels as usize, ) { return Err(DecoderError::CorruptRleData.into()); } } ImageType::RLE4 => { if !set_4bit_pixel_run( &mut pixel_iter, p, repeat(&palette_index), n_pixels as usize, ) { return Err(DecoderError::CorruptRleData.into()); } } _ => unreachable!(), } x += n_pixels as usize; } } } } Ok(()) } /// Read the actual data of the image. This function is deliberately not public because it /// cannot be called multiple times without seeking back the underlying reader in between. pub(crate) fn read_image_data(&mut self, buf: &mut [u8]) -> ImageResult<()> { match self.image_type { ImageType::Palette => self.read_palettized_pixel_data(buf), ImageType::RGB16 => self.read_16_bit_pixel_data(buf, Some(&R5_G5_B5_COLOR_MASK)), ImageType::RGB24 => self.read_full_byte_pixel_data(buf, &FormatFullBytes::RGB24), ImageType::RGB32 => self.read_full_byte_pixel_data(buf, &FormatFullBytes::RGB32), ImageType::RGBA32 => self.read_full_byte_pixel_data(buf, &FormatFullBytes::RGBA32), ImageType::RLE8 => self.read_rle_data(buf, ImageType::RLE8), ImageType::RLE4 => self.read_rle_data(buf, ImageType::RLE4), ImageType::Bitfields16 => match self.bitfields { Some(_) => self.read_16_bit_pixel_data(buf, None), None => Err(DecoderError::BitfieldMasksMissing(16).into()), }, ImageType::Bitfields32 => match self.bitfields { Some(R8_G8_B8_COLOR_MASK) => { self.read_full_byte_pixel_data(buf, &FormatFullBytes::Format888) } Some(R8_G8_B8_A8_COLOR_MASK) => { self.read_full_byte_pixel_data(buf, &FormatFullBytes::RGBA32) } Some(_) => self.read_32_bit_pixel_data(buf), None => Err(DecoderError::BitfieldMasksMissing(32).into()), }, } } } /// Wrapper struct around a `Cursor>` pub struct BmpReader(Cursor>, PhantomData); impl Read for BmpReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { if self.0.position() == 0 && buf.is_empty() { mem::swap(buf, self.0.get_mut()); Ok(buf.len()) } else { self.0.read_to_end(buf) } } } impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for BmpDecoder { type Reader = BmpReader; fn dimensions(&self) -> (u32, u32) { (self.width as u32, self.height as u32) } fn color_type(&self) -> ColorType { if self.indexed_color { ColorType::L8 } else if self.add_alpha_channel { ColorType::Rgba8 } else { ColorType::Rgb8 } } fn into_reader(self) -> ImageResult { Ok(BmpReader( Cursor::new(image::decoder_to_vec(self)?), PhantomData, )) } fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); self.read_image_data(buf) } } impl<'a, R: 'a + Read + Seek> ImageDecoderRect<'a> for BmpDecoder { fn read_rect_with_progress( &mut self, x: u32, y: u32, width: u32, height: u32, buf: &mut [u8], progress_callback: F, ) -> ImageResult<()> { let start = self.reader.stream_position()?; image::load_rect( x, y, width, height, buf, progress_callback, self, |_, _| Ok(()), |s, buf| s.read_image_data(buf), )?; self.reader.seek(SeekFrom::Start(start))?; Ok(()) } } #[cfg(test)] mod test { use super::*; #[test] fn test_bitfield_len() { for len in 1..9 { let bitfield = Bitfield { shift: 0, len }; for i in 0..(1 << len) { let read = bitfield.read(i); let calc = (i as f64 / ((1 << len) - 1) as f64 * 255f64).round() as u8; if read != calc { println!("len:{} i:{} read:{} calc:{}", len, i, read, calc); } assert_eq!(read, calc); } } } #[test] fn read_rect() { let f = std::fs::File::open("tests/images/bmp/images/Core_8_Bit.bmp").unwrap(); let mut decoder = super::BmpDecoder::new(f).unwrap(); let mut buf: Vec = vec![0; 8 * 8 * 3]; decoder.read_rect(0, 0, 8, 8, &mut *buf).unwrap(); } #[test] fn read_rle_too_short() { let data = vec![ 0x42, 0x4d, 0x04, 0xee, 0xfe, 0xff, 0xff, 0x10, 0xff, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x0c, 0x41, 0x00, 0x00, 0x07, 0x10, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x21, 0xff, 0x00, 0x66, 0x61, 0x72, 0x62, 0x66, 0x65, 0x6c, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xd8, 0xff, 0x00, 0x00, 0x19, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x31, 0x31, 0x35, 0x36, 0x00, 0xff, 0x00, 0x00, 0x52, 0x3a, 0x37, 0x30, 0x7e, 0x71, 0x63, 0x91, 0x5a, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x35, 0x37, 0x00, 0xff, 0x00, 0x00, 0x52, 0x3a, 0x37, 0x30, 0x7e, 0x71, 0x63, 0x91, 0x5a, 0x04, 0x05, 0x3c, 0x00, 0x00, 0x11, 0x00, 0x5d, 0x7a, 0x82, 0xb7, 0xca, 0x2d, 0x31, 0xff, 0xff, 0xc7, 0x95, 0x33, 0x2e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x66, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, ]; let decoder = BmpDecoder::new(Cursor::new(&data)).unwrap(); let mut buf = vec![0; usize::try_from(decoder.total_bytes()).unwrap()]; assert!(decoder.read_image(&mut buf).is_ok()); } #[test] fn test_no_header() { let tests = [ "Info_R8_G8_B8.bmp", "Info_A8_R8_G8_B8.bmp", "Info_8_Bit.bmp", "Info_4_Bit.bmp", "Info_1_Bit.bmp", ]; for name in &tests { let path = format!("tests/images/bmp/images/{name}"); let ref_img = crate::open(&path).unwrap(); let mut data = std::fs::read(&path).unwrap(); // skip the BITMAPFILEHEADER let slice = &mut data[14..]; let decoder = BmpDecoder::new_without_file_header(Cursor::new(slice)).unwrap(); let no_hdr_img = crate::DynamicImage::from_decoder(decoder).unwrap(); assert_eq!(ref_img, no_hdr_img); } } } image-0.24.7/src/codecs/bmp/encoder.rs000064400000000000000000000322711046102023000155770ustar 00000000000000use byteorder::{LittleEndian, WriteBytesExt}; use std::io::{self, Write}; use crate::error::{ EncodingError, ImageError, ImageFormatHint, ImageResult, ParameterError, ParameterErrorKind, }; use crate::image::ImageEncoder; use crate::{color, ImageFormat}; const BITMAPFILEHEADER_SIZE: u32 = 14; const BITMAPINFOHEADER_SIZE: u32 = 40; const BITMAPV4HEADER_SIZE: u32 = 108; /// The representation of a BMP encoder. pub struct BmpEncoder<'a, W: 'a> { writer: &'a mut W, } impl<'a, W: Write + 'a> BmpEncoder<'a, W> { /// Create a new encoder that writes its output to ```w```. pub fn new(w: &'a mut W) -> Self { BmpEncoder { writer: w } } /// Encodes the image ```image``` /// that has dimensions ```width``` and ```height``` /// and ```ColorType``` ```c```. pub fn encode( &mut self, image: &[u8], width: u32, height: u32, c: color::ColorType, ) -> ImageResult<()> { self.encode_with_palette(image, width, height, c, None) } /// Same as ```encode```, but allow a palette to be passed in. /// The ```palette``` is ignored for color types other than Luma/Luma-with-alpha. pub fn encode_with_palette( &mut self, image: &[u8], width: u32, height: u32, c: color::ColorType, palette: Option<&[[u8; 3]]>, ) -> ImageResult<()> { if palette.is_some() && c != color::ColorType::L8 && c != color::ColorType::La8 { return Err(ImageError::IoError(io::Error::new( io::ErrorKind::InvalidInput, format!( "Unsupported color type {:?} when using a non-empty palette. Supported types: Gray(8), GrayA(8).", c ), ))); } let bmp_header_size = BITMAPFILEHEADER_SIZE; let (dib_header_size, written_pixel_size, palette_color_count) = get_pixel_info(c, palette)?; let row_pad_size = (4 - (width * written_pixel_size) % 4) % 4; // each row must be padded to a multiple of 4 bytes let image_size = width .checked_mul(height) .and_then(|v| v.checked_mul(written_pixel_size)) .and_then(|v| v.checked_add(height * row_pad_size)) .ok_or_else(|| { ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, )) })?; let palette_size = palette_color_count * 4; // all palette colors are BGRA let file_size = bmp_header_size .checked_add(dib_header_size) .and_then(|v| v.checked_add(palette_size)) .and_then(|v| v.checked_add(image_size)) .ok_or_else(|| { ImageError::Encoding(EncodingError::new( ImageFormatHint::Exact(ImageFormat::Bmp), "calculated BMP header size larger than 2^32", )) })?; // write BMP header self.writer.write_u8(b'B')?; self.writer.write_u8(b'M')?; self.writer.write_u32::(file_size)?; // file size self.writer.write_u16::(0)?; // reserved 1 self.writer.write_u16::(0)?; // reserved 2 self.writer .write_u32::(bmp_header_size + dib_header_size + palette_size)?; // image data offset // write DIB header self.writer.write_u32::(dib_header_size)?; self.writer.write_i32::(width as i32)?; self.writer.write_i32::(height as i32)?; self.writer.write_u16::(1)?; // color planes self.writer .write_u16::((written_pixel_size * 8) as u16)?; // bits per pixel if dib_header_size >= BITMAPV4HEADER_SIZE { // Assume BGRA32 self.writer.write_u32::(3)?; // compression method - bitfields } else { self.writer.write_u32::(0)?; // compression method - no compression } self.writer.write_u32::(image_size)?; self.writer.write_i32::(0)?; // horizontal ppm self.writer.write_i32::(0)?; // vertical ppm self.writer.write_u32::(palette_color_count)?; self.writer.write_u32::(0)?; // all colors are important if dib_header_size >= BITMAPV4HEADER_SIZE { // Assume BGRA32 self.writer.write_u32::(0xff << 16)?; // red mask self.writer.write_u32::(0xff << 8)?; // green mask self.writer.write_u32::(0xff)?; // blue mask self.writer.write_u32::(0xff << 24)?; // alpha mask self.writer.write_u32::(0x73524742)?; // colorspace - sRGB // endpoints (3x3) and gamma (3) for _ in 0..12 { self.writer.write_u32::(0)?; } } // write image data match c { color::ColorType::Rgb8 => self.encode_rgb(image, width, height, row_pad_size, 3)?, color::ColorType::Rgba8 => self.encode_rgba(image, width, height, row_pad_size, 4)?, color::ColorType::L8 => { self.encode_gray(image, width, height, row_pad_size, 1, palette)? } color::ColorType::La8 => { self.encode_gray(image, width, height, row_pad_size, 2, palette)? } _ => { return Err(ImageError::IoError(io::Error::new( io::ErrorKind::InvalidInput, &get_unsupported_error_message(c)[..], ))) } } Ok(()) } fn encode_rgb( &mut self, image: &[u8], width: u32, height: u32, row_pad_size: u32, bytes_per_pixel: u32, ) -> io::Result<()> { let width = width as usize; let height = height as usize; let x_stride = bytes_per_pixel as usize; let y_stride = width * x_stride; for row in (0..height).rev() { // from the bottom up let row_start = row * y_stride; for px in image[row_start..][..y_stride].chunks_exact(x_stride) { let r = px[0]; let g = px[1]; let b = px[2]; // written as BGR self.writer.write_all(&[b, g, r])?; } self.write_row_pad(row_pad_size)?; } Ok(()) } fn encode_rgba( &mut self, image: &[u8], width: u32, height: u32, row_pad_size: u32, bytes_per_pixel: u32, ) -> io::Result<()> { let width = width as usize; let height = height as usize; let x_stride = bytes_per_pixel as usize; let y_stride = width * x_stride; for row in (0..height).rev() { // from the bottom up let row_start = row * y_stride; for px in image[row_start..][..y_stride].chunks_exact(x_stride) { let r = px[0]; let g = px[1]; let b = px[2]; let a = px[3]; // written as BGRA self.writer.write_all(&[b, g, r, a])?; } self.write_row_pad(row_pad_size)?; } Ok(()) } fn encode_gray( &mut self, image: &[u8], width: u32, height: u32, row_pad_size: u32, bytes_per_pixel: u32, palette: Option<&[[u8; 3]]>, ) -> io::Result<()> { // write grayscale palette if let Some(palette) = palette { for item in palette { // each color is written as BGRA, where A is always 0 self.writer.write_all(&[item[2], item[1], item[0], 0])?; } } else { for val in 0u8..=255 { // each color is written as BGRA, where A is always 0 and since only grayscale is being written, B = G = R = index self.writer.write_all(&[val, val, val, 0])?; } } // write image data let x_stride = bytes_per_pixel; let y_stride = width * x_stride; for row in (0..height).rev() { // from the bottom up let row_start = row * y_stride; for col in 0..width { let pixel_start = (row_start + (col * x_stride)) as usize; // color value is equal to the palette index self.writer.write_u8(image[pixel_start])?; // alpha is never written as it's not widely supported } self.write_row_pad(row_pad_size)?; } Ok(()) } fn write_row_pad(&mut self, row_pad_size: u32) -> io::Result<()> { for _ in 0..row_pad_size { self.writer.write_u8(0)?; } Ok(()) } } impl<'a, W: Write> ImageEncoder for BmpEncoder<'a, W> { fn write_image( mut self, buf: &[u8], width: u32, height: u32, color_type: color::ColorType, ) -> ImageResult<()> { self.encode(buf, width, height, color_type) } } fn get_unsupported_error_message(c: color::ColorType) -> String { format!( "Unsupported color type {:?}. Supported types: RGB(8), RGBA(8), Gray(8), GrayA(8).", c ) } /// Returns a tuple representing: (dib header size, written pixel size, palette color count). fn get_pixel_info(c: color::ColorType, palette: Option<&[[u8; 3]]>) -> io::Result<(u32, u32, u32)> { let sizes = match c { color::ColorType::Rgb8 => (BITMAPINFOHEADER_SIZE, 3, 0), color::ColorType::Rgba8 => (BITMAPV4HEADER_SIZE, 4, 0), color::ColorType::L8 => ( BITMAPINFOHEADER_SIZE, 1, palette.map(|p| p.len()).unwrap_or(256) as u32, ), color::ColorType::La8 => ( BITMAPINFOHEADER_SIZE, 1, palette.map(|p| p.len()).unwrap_or(256) as u32, ), _ => { return Err(io::Error::new( io::ErrorKind::InvalidInput, &get_unsupported_error_message(c)[..], )) } }; Ok(sizes) } #[cfg(test)] mod tests { use super::super::BmpDecoder; use super::BmpEncoder; use crate::color::ColorType; use crate::image::ImageDecoder; use std::io::Cursor; fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec { let mut encoded_data = Vec::new(); { let mut encoder = BmpEncoder::new(&mut encoded_data); encoder .encode(&image, width, height, c) .expect("could not encode image"); } let decoder = BmpDecoder::new(Cursor::new(&encoded_data)).expect("failed to decode"); let mut buf = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut buf).expect("failed to decode"); buf } #[test] fn round_trip_single_pixel_rgb() { let image = [255u8, 0, 0]; // single red pixel let decoded = round_trip_image(&image, 1, 1, ColorType::Rgb8); assert_eq!(3, decoded.len()); assert_eq!(255, decoded[0]); assert_eq!(0, decoded[1]); assert_eq!(0, decoded[2]); } #[test] #[cfg(target_pointer_width = "64")] fn huge_files_return_error() { let mut encoded_data = Vec::new(); let image = vec![0u8; 3 * 40_000 * 40_000]; // 40_000x40_000 pixels, 3 bytes per pixel, allocated on the heap let mut encoder = BmpEncoder::new(&mut encoded_data); let result = encoder.encode(&image, 40_000, 40_000, ColorType::Rgb8); assert!(result.is_err()); } #[test] fn round_trip_single_pixel_rgba() { let image = [1, 2, 3, 4]; let decoded = round_trip_image(&image, 1, 1, ColorType::Rgba8); assert_eq!(&decoded[..], &image[..]); } #[test] fn round_trip_3px_rgb() { let image = [0u8; 3 * 3 * 3]; // 3x3 pixels, 3 bytes per pixel let _decoded = round_trip_image(&image, 3, 3, ColorType::Rgb8); } #[test] fn round_trip_gray() { let image = [0u8, 1, 2]; // 3 pixels let decoded = round_trip_image(&image, 3, 1, ColorType::L8); // should be read back as 3 RGB pixels assert_eq!(9, decoded.len()); assert_eq!(0, decoded[0]); assert_eq!(0, decoded[1]); assert_eq!(0, decoded[2]); assert_eq!(1, decoded[3]); assert_eq!(1, decoded[4]); assert_eq!(1, decoded[5]); assert_eq!(2, decoded[6]); assert_eq!(2, decoded[7]); assert_eq!(2, decoded[8]); } #[test] fn round_trip_graya() { let image = [0u8, 0, 1, 0, 2, 0]; // 3 pixels, each with an alpha channel let decoded = round_trip_image(&image, 1, 3, ColorType::La8); // should be read back as 3 RGB pixels assert_eq!(9, decoded.len()); assert_eq!(0, decoded[0]); assert_eq!(0, decoded[1]); assert_eq!(0, decoded[2]); assert_eq!(1, decoded[3]); assert_eq!(1, decoded[4]); assert_eq!(1, decoded[5]); assert_eq!(2, decoded[6]); assert_eq!(2, decoded[7]); assert_eq!(2, decoded[8]); } } image-0.24.7/src/codecs/bmp/mod.rs000064400000000000000000000005731046102023000147370ustar 00000000000000//! Decoding and Encoding of BMP Images //! //! A decoder and encoder for BMP (Windows Bitmap) images //! //! # Related Links //! * //! * //! pub use self::decoder::BmpDecoder; pub use self::encoder::BmpEncoder; mod decoder; mod encoder; image-0.24.7/src/codecs/dds.rs000064400000000000000000000323201046102023000141470ustar 00000000000000//! Decoding of DDS images //! //! DDS (DirectDraw Surface) is a container format for storing DXT (S3TC) compressed images. //! //! # Related Links //! * - Description of the DDS format. use std::io::Read; use std::{error, fmt}; use byteorder::{LittleEndian, ReadBytesExt}; #[allow(deprecated)] use crate::codecs::dxt::{DxtDecoder, DxtReader, DxtVariant}; use crate::color::ColorType; use crate::error::{ DecodingError, ImageError, ImageFormatHint, ImageResult, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{ImageDecoder, ImageFormat}; /// Errors that can occur during decoding and parsing a DDS image #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] enum DecoderError { /// Wrong DDS channel width PixelFormatSizeInvalid(u32), /// Wrong DDS header size HeaderSizeInvalid(u32), /// Wrong DDS header flags HeaderFlagsInvalid(u32), /// Invalid DXGI format in DX10 header DxgiFormatInvalid(u32), /// Invalid resource dimension ResourceDimensionInvalid(u32), /// Invalid flags in DX10 header Dx10FlagsInvalid(u32), /// Invalid array size in DX10 header Dx10ArraySizeInvalid(u32), /// DDS "DDS " signature invalid or missing DdsSignatureInvalid, } impl fmt::Display for DecoderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DecoderError::PixelFormatSizeInvalid(s) => { f.write_fmt(format_args!("Invalid DDS PixelFormat size: {}", s)) } DecoderError::HeaderSizeInvalid(s) => { f.write_fmt(format_args!("Invalid DDS header size: {}", s)) } DecoderError::HeaderFlagsInvalid(fs) => { f.write_fmt(format_args!("Invalid DDS header flags: {:#010X}", fs)) } DecoderError::DxgiFormatInvalid(df) => { f.write_fmt(format_args!("Invalid DDS DXGI format: {}", df)) } DecoderError::ResourceDimensionInvalid(d) => { f.write_fmt(format_args!("Invalid DDS resource dimension: {}", d)) } DecoderError::Dx10FlagsInvalid(fs) => { f.write_fmt(format_args!("Invalid DDS DX10 header flags: {:#010X}", fs)) } DecoderError::Dx10ArraySizeInvalid(s) => { f.write_fmt(format_args!("Invalid DDS DX10 array size: {}", s)) } DecoderError::DdsSignatureInvalid => f.write_str("DDS signature not found"), } } } impl From for ImageError { fn from(e: DecoderError) -> ImageError { ImageError::Decoding(DecodingError::new(ImageFormat::Dds.into(), e)) } } impl error::Error for DecoderError {} /// Header used by DDS image files #[derive(Debug)] struct Header { _flags: u32, height: u32, width: u32, _pitch_or_linear_size: u32, _depth: u32, _mipmap_count: u32, pixel_format: PixelFormat, _caps: u32, _caps2: u32, } /// Extended DX10 header used by some DDS image files #[derive(Debug)] struct DX10Header { dxgi_format: u32, resource_dimension: u32, misc_flag: u32, array_size: u32, misc_flags_2: u32, } /// DDS pixel format #[derive(Debug)] struct PixelFormat { flags: u32, fourcc: [u8; 4], _rgb_bit_count: u32, _r_bit_mask: u32, _g_bit_mask: u32, _b_bit_mask: u32, _a_bit_mask: u32, } impl PixelFormat { fn from_reader(r: &mut dyn Read) -> ImageResult { let size = r.read_u32::()?; if size != 32 { return Err(DecoderError::PixelFormatSizeInvalid(size).into()); } Ok(Self { flags: r.read_u32::()?, fourcc: { let mut v = [0; 4]; r.read_exact(&mut v)?; v }, _rgb_bit_count: r.read_u32::()?, _r_bit_mask: r.read_u32::()?, _g_bit_mask: r.read_u32::()?, _b_bit_mask: r.read_u32::()?, _a_bit_mask: r.read_u32::()?, }) } } impl Header { fn from_reader(r: &mut dyn Read) -> ImageResult { let size = r.read_u32::()?; if size != 124 { return Err(DecoderError::HeaderSizeInvalid(size).into()); } const REQUIRED_FLAGS: u32 = 0x1 | 0x2 | 0x4 | 0x1000; const VALID_FLAGS: u32 = 0x1 | 0x2 | 0x4 | 0x8 | 0x1000 | 0x20000 | 0x80000 | 0x800000; let flags = r.read_u32::()?; if flags & (REQUIRED_FLAGS | !VALID_FLAGS) != REQUIRED_FLAGS { return Err(DecoderError::HeaderFlagsInvalid(flags).into()); } let height = r.read_u32::()?; let width = r.read_u32::()?; let pitch_or_linear_size = r.read_u32::()?; let depth = r.read_u32::()?; let mipmap_count = r.read_u32::()?; // Skip `dwReserved1` { let mut skipped = [0; 4 * 11]; r.read_exact(&mut skipped)?; } let pixel_format = PixelFormat::from_reader(r)?; let caps = r.read_u32::()?; let caps2 = r.read_u32::()?; // Skip `dwCaps3`, `dwCaps4`, `dwReserved2` (unused) { let mut skipped = [0; 4 + 4 + 4]; r.read_exact(&mut skipped)?; } Ok(Self { _flags: flags, height, width, _pitch_or_linear_size: pitch_or_linear_size, _depth: depth, _mipmap_count: mipmap_count, pixel_format, _caps: caps, _caps2: caps2, }) } } impl DX10Header { fn from_reader(r: &mut dyn Read) -> ImageResult { let dxgi_format = r.read_u32::()?; let resource_dimension = r.read_u32::()?; let misc_flag = r.read_u32::()?; let array_size = r.read_u32::()?; let misc_flags_2 = r.read_u32::()?; let dx10_header = Self { dxgi_format, resource_dimension, misc_flag, array_size, misc_flags_2, }; dx10_header.validate()?; Ok(dx10_header) } fn validate(&self) -> Result<(), ImageError> { // Note: see https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dds-header-dxt10 for info on valid values if self.dxgi_format > 132 { // Invalid format return Err(DecoderError::DxgiFormatInvalid(self.dxgi_format).into()); } if self.resource_dimension < 2 || self.resource_dimension > 4 { // Invalid dimension // Only 1D (2), 2D (3) and 3D (4) resource dimensions are allowed return Err(DecoderError::ResourceDimensionInvalid(self.resource_dimension).into()); } if self.misc_flag != 0x0 && self.misc_flag != 0x4 { // Invalid flag // Only no (0x0) and DDS_RESOURCE_MISC_TEXTURECUBE (0x4) flags are allowed return Err(DecoderError::Dx10FlagsInvalid(self.misc_flag).into()); } if self.resource_dimension == 4 && self.array_size != 1 { // Invalid array size // 3D textures (resource dimension == 4) must have an array size of 1 return Err(DecoderError::Dx10ArraySizeInvalid(self.array_size).into()); } if self.misc_flags_2 > 0x4 { // Invalid alpha flags return Err(DecoderError::Dx10FlagsInvalid(self.misc_flags_2).into()); } Ok(()) } } /// The representation of a DDS decoder pub struct DdsDecoder { #[allow(deprecated)] inner: DxtDecoder, } impl DdsDecoder { /// Create a new decoder that decodes from the stream `r` pub fn new(mut r: R) -> ImageResult { let mut magic = [0; 4]; r.read_exact(&mut magic)?; if magic != b"DDS "[..] { return Err(DecoderError::DdsSignatureInvalid.into()); } let header = Header::from_reader(&mut r)?; if header.pixel_format.flags & 0x4 != 0 { #[allow(deprecated)] let variant = match &header.pixel_format.fourcc { b"DXT1" => DxtVariant::DXT1, b"DXT3" => DxtVariant::DXT3, b"DXT5" => DxtVariant::DXT5, b"DX10" => { let dx10_header = DX10Header::from_reader(&mut r)?; // Format equivalents were taken from https://docs.microsoft.com/en-us/windows/win32/direct3d11/texture-block-compression-in-direct3d-11 // The enum integer values were taken from https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format // DXT1 represents the different BC1 variants, DTX3 represents the different BC2 variants and DTX5 represents the different BC3 variants match dx10_header.dxgi_format { 70 | 71 | 72 => DxtVariant::DXT1, // DXGI_FORMAT_BC1_TYPELESS, DXGI_FORMAT_BC1_UNORM or DXGI_FORMAT_BC1_UNORM_SRGB 73 | 74 | 75 => DxtVariant::DXT3, // DXGI_FORMAT_BC2_TYPELESS, DXGI_FORMAT_BC2_UNORM or DXGI_FORMAT_BC2_UNORM_SRGB 76 | 77 | 78 => DxtVariant::DXT5, // DXGI_FORMAT_BC3_TYPELESS, DXGI_FORMAT_BC3_UNORM or DXGI_FORMAT_BC3_UNORM_SRGB _ => { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Dds.into(), UnsupportedErrorKind::GenericFeature(format!( "DDS DXGI Format {}", dx10_header.dxgi_format )), ), )) } } } fourcc => { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Dds.into(), UnsupportedErrorKind::GenericFeature(format!( "DDS FourCC {:?}", fourcc )), ), )) } }; #[allow(deprecated)] let bytes_per_pixel = variant.color_type().bytes_per_pixel(); if crate::utils::check_dimension_overflow(header.width, header.height, bytes_per_pixel) { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Dds.into(), UnsupportedErrorKind::GenericFeature(format!( "Image dimensions ({}x{}) are too large", header.width, header.height )), ), )); } #[allow(deprecated)] let inner = DxtDecoder::new(r, header.width, header.height, variant)?; Ok(Self { inner }) } else { // For now, supports only DXT variants Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Dds.into(), UnsupportedErrorKind::Format(ImageFormatHint::Name("DDS".to_string())), ), )) } } } impl<'a, R: 'a + Read> ImageDecoder<'a> for DdsDecoder { #[allow(deprecated)] type Reader = DxtReader; fn dimensions(&self) -> (u32, u32) { self.inner.dimensions() } fn color_type(&self) -> ColorType { self.inner.color_type() } fn scanline_bytes(&self) -> u64 { self.inner.scanline_bytes() } fn into_reader(self) -> ImageResult { self.inner.into_reader() } fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { self.inner.read_image(buf) } } #[cfg(test)] mod test { use super::*; #[test] fn dimension_overflow() { // A DXT1 header set to 0xFFFF_FFFC width and height (the highest u32%4 == 0) let header = vec![ 0x44, 0x44, 0x53, 0x20, 0x7C, 0x0, 0x0, 0x0, 0x7, 0x10, 0x8, 0x0, 0xFC, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0x0, 0xC0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x49, 0x4D, 0x41, 0x47, 0x45, 0x4D, 0x41, 0x47, 0x49, 0x43, 0x4B, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x44, 0x58, 0x54, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ]; assert!(DdsDecoder::new(&header[..]).is_err()); } } image-0.24.7/src/codecs/dxt.rs000064400000000000000000000744001046102023000142010ustar 00000000000000//! Decoding of DXT (S3TC) compression //! //! DXT is an image format that supports lossy compression //! //! # Related Links //! * - Description of the DXT compression OpenGL extensions. //! //! Note: this module only implements bare DXT encoding/decoding, it does not parse formats that can contain DXT files like .dds use std::convert::TryFrom; use std::io::{self, Read, Seek, SeekFrom, Write}; use crate::color::ColorType; use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; use crate::image::{self, ImageDecoder, ImageDecoderRect, ImageReadBuffer, Progress}; /// What version of DXT compression are we using? /// Note that DXT2 and DXT4 are left away as they're /// just DXT3 and DXT5 with premultiplied alpha #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum DxtVariant { /// The DXT1 format. 48 bytes of RGB data in a 4x4 pixel square is /// compressed into an 8 byte block of DXT1 data DXT1, /// The DXT3 format. 64 bytes of RGBA data in a 4x4 pixel square is /// compressed into a 16 byte block of DXT3 data DXT3, /// The DXT5 format. 64 bytes of RGBA data in a 4x4 pixel square is /// compressed into a 16 byte block of DXT5 data DXT5, } impl DxtVariant { /// Returns the amount of bytes of raw image data /// that is encoded in a single DXTn block fn decoded_bytes_per_block(self) -> usize { match self { DxtVariant::DXT1 => 48, DxtVariant::DXT3 | DxtVariant::DXT5 => 64, } } /// Returns the amount of bytes per block of encoded DXTn data fn encoded_bytes_per_block(self) -> usize { match self { DxtVariant::DXT1 => 8, DxtVariant::DXT3 | DxtVariant::DXT5 => 16, } } /// Returns the color type that is stored in this DXT variant pub fn color_type(self) -> ColorType { match self { DxtVariant::DXT1 => ColorType::Rgb8, DxtVariant::DXT3 | DxtVariant::DXT5 => ColorType::Rgba8, } } } /// DXT decoder pub struct DxtDecoder { inner: R, width_blocks: u32, height_blocks: u32, variant: DxtVariant, row: u32, } impl DxtDecoder { /// Create a new DXT decoder that decodes from the stream ```r```. /// As DXT is often stored as raw buffers with the width/height /// somewhere else the width and height of the image need /// to be passed in ```width``` and ```height```, as well as the /// DXT variant in ```variant```. /// width and height are required to be powers of 2 and at least 4. /// otherwise an error will be returned pub fn new( r: R, width: u32, height: u32, variant: DxtVariant, ) -> Result, ImageError> { if width % 4 != 0 || height % 4 != 0 { // TODO: this is actually a bit of a weird case. We could return `DecodingError` but // it's not really the format that is wrong However, the encoder should surely return // `EncodingError` so it would be the logical choice for symmetry. return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))); } let width_blocks = width / 4; let height_blocks = height / 4; Ok(DxtDecoder { inner: r, width_blocks, height_blocks, variant, row: 0, }) } fn read_scanline(&mut self, buf: &mut [u8]) -> io::Result { assert_eq!(u64::try_from(buf.len()), Ok(self.scanline_bytes())); let mut src = vec![0u8; self.variant.encoded_bytes_per_block() * self.width_blocks as usize]; self.inner.read_exact(&mut src)?; match self.variant { DxtVariant::DXT1 => decode_dxt1_row(&src, buf), DxtVariant::DXT3 => decode_dxt3_row(&src, buf), DxtVariant::DXT5 => decode_dxt5_row(&src, buf), } self.row += 1; Ok(buf.len()) } } // Note that, due to the way that DXT compression works, a scanline is considered to consist out of // 4 lines of pixels. impl<'a, R: 'a + Read> ImageDecoder<'a> for DxtDecoder { type Reader = DxtReader; fn dimensions(&self) -> (u32, u32) { (self.width_blocks * 4, self.height_blocks * 4) } fn color_type(&self) -> ColorType { self.variant.color_type() } fn scanline_bytes(&self) -> u64 { self.variant.decoded_bytes_per_block() as u64 * u64::from(self.width_blocks) } fn into_reader(self) -> ImageResult { Ok(DxtReader { buffer: ImageReadBuffer::new(self.scanline_bytes(), self.total_bytes()), decoder: self, }) } fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); for chunk in buf.chunks_mut(self.scanline_bytes().max(1) as usize) { self.read_scanline(chunk)?; } Ok(()) } } impl<'a, R: 'a + Read + Seek> ImageDecoderRect<'a> for DxtDecoder { fn read_rect_with_progress( &mut self, x: u32, y: u32, width: u32, height: u32, buf: &mut [u8], progress_callback: F, ) -> ImageResult<()> { let encoded_scanline_bytes = self.variant.encoded_bytes_per_block() as u64 * u64::from(self.width_blocks); let start = self.inner.stream_position()?; image::load_rect( x, y, width, height, buf, progress_callback, self, |s, scanline| { s.inner .seek(SeekFrom::Start(start + scanline * encoded_scanline_bytes))?; Ok(()) }, |s, buf| s.read_scanline(buf).map(|_| ()), )?; self.inner.seek(SeekFrom::Start(start))?; Ok(()) } } /// DXT reader pub struct DxtReader { buffer: ImageReadBuffer, decoder: DxtDecoder, } impl Read for DxtReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { let decoder = &mut self.decoder; self.buffer.read(buf, |buf| decoder.read_scanline(buf)) } } /// DXT encoder pub struct DxtEncoder { w: W, } impl DxtEncoder { /// Create a new encoder that writes its output to ```w``` pub fn new(w: W) -> DxtEncoder { DxtEncoder { w } } /// Encodes the image data ```data``` /// that has dimensions ```width``` and ```height``` /// in ```DxtVariant``` ```variant``` /// data is assumed to be in variant.color_type() pub fn encode( mut self, data: &[u8], width: u32, height: u32, variant: DxtVariant, ) -> ImageResult<()> { if width % 4 != 0 || height % 4 != 0 { // TODO: this is not very idiomatic yet. Should return an EncodingError. return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))); } let width_blocks = width / 4; let height_blocks = height / 4; let stride = variant.decoded_bytes_per_block(); assert!(data.len() >= width_blocks as usize * height_blocks as usize * stride); for chunk in data.chunks(width_blocks as usize * stride) { let data = match variant { DxtVariant::DXT1 => encode_dxt1_row(chunk), DxtVariant::DXT3 => encode_dxt3_row(chunk), DxtVariant::DXT5 => encode_dxt5_row(chunk), }; self.w.write_all(&data)?; } Ok(()) } } /** * Actual encoding/decoding logic below. */ use std::mem::swap; type Rgb = [u8; 3]; /// decodes a 5-bit R, 6-bit G, 5-bit B 16-bit packed color value into 8-bit RGB /// mapping is done so min/max range values are preserved. So for 5-bit /// values 0x00 -> 0x00 and 0x1F -> 0xFF fn enc565_decode(value: u16) -> Rgb { let red = (value >> 11) & 0x1F; let green = (value >> 5) & 0x3F; let blue = (value) & 0x1F; [ (red * 0xFF / 0x1F) as u8, (green * 0xFF / 0x3F) as u8, (blue * 0xFF / 0x1F) as u8, ] } /// encodes an 8-bit RGB value into a 5-bit R, 6-bit G, 5-bit B 16-bit packed color value /// mapping preserves min/max values. It is guaranteed that i == encode(decode(i)) for all i fn enc565_encode(rgb: Rgb) -> u16 { let red = (u16::from(rgb[0]) * 0x1F + 0x7E) / 0xFF; let green = (u16::from(rgb[1]) * 0x3F + 0x7E) / 0xFF; let blue = (u16::from(rgb[2]) * 0x1F + 0x7E) / 0xFF; (red << 11) | (green << 5) | blue } /// utility function: squares a value fn square(a: i32) -> i32 { a * a } /// returns the squared error between two RGB values fn diff(a: Rgb, b: Rgb) -> i32 { square(i32::from(a[0]) - i32::from(b[0])) + square(i32::from(a[1]) - i32::from(b[1])) + square(i32::from(a[2]) - i32::from(b[2])) } /* * Functions for decoding DXT compression */ /// Constructs the DXT5 alpha lookup table from the two alpha entries /// if alpha0 > alpha1, constructs a table of [a0, a1, 6 linearly interpolated values from a0 to a1] /// if alpha0 <= alpha1, constructs a table of [a0, a1, 4 linearly interpolated values from a0 to a1, 0, 0xFF] fn alpha_table_dxt5(alpha0: u8, alpha1: u8) -> [u8; 8] { let mut table = [alpha0, alpha1, 0, 0, 0, 0, 0, 0xFF]; if alpha0 > alpha1 { for i in 2..8u16 { table[i as usize] = (((8 - i) * u16::from(alpha0) + (i - 1) * u16::from(alpha1)) / 7) as u8; } } else { for i in 2..6u16 { table[i as usize] = (((6 - i) * u16::from(alpha0) + (i - 1) * u16::from(alpha1)) / 5) as u8; } } table } /// decodes an 8-byte dxt color block into the RGB channels of a 16xRGB or 16xRGBA block. /// source should have a length of 8, dest a length of 48 (RGB) or 64 (RGBA) fn decode_dxt_colors(source: &[u8], dest: &mut [u8], is_dxt1: bool) { // sanity checks, also enable the compiler to elide all following bound checks assert!(source.len() == 8 && (dest.len() == 48 || dest.len() == 64)); // calculate pitch to store RGB values in dest (3 for RGB, 4 for RGBA) let pitch = dest.len() / 16; // extract color data let color0 = u16::from(source[0]) | (u16::from(source[1]) << 8); let color1 = u16::from(source[2]) | (u16::from(source[3]) << 8); let color_table = u32::from(source[4]) | (u32::from(source[5]) << 8) | (u32::from(source[6]) << 16) | (u32::from(source[7]) << 24); // let color_table = source[4..8].iter().rev().fold(0, |t, &b| (t << 8) | b as u32); // decode the colors to rgb format let mut colors = [[0; 3]; 4]; colors[0] = enc565_decode(color0); colors[1] = enc565_decode(color1); // determine color interpolation method if color0 > color1 || !is_dxt1 { // linearly interpolate the other two color table entries for i in 0..3 { colors[2][i] = ((u16::from(colors[0][i]) * 2 + u16::from(colors[1][i]) + 1) / 3) as u8; colors[3][i] = ((u16::from(colors[0][i]) + u16::from(colors[1][i]) * 2 + 1) / 3) as u8; } } else { // linearly interpolate one other entry, keep the other at 0 for i in 0..3 { colors[2][i] = ((u16::from(colors[0][i]) + u16::from(colors[1][i]) + 1) / 2) as u8; } } // serialize the result. Every color is determined by looking up // two bits in color_table which identify which color to actually pick from the 4 possible colors for i in 0..16 { dest[i * pitch..i * pitch + 3] .copy_from_slice(&colors[(color_table >> (i * 2)) as usize & 3]); } } /// Decodes a 16-byte bock of dxt5 data to a 16xRGBA block fn decode_dxt5_block(source: &[u8], dest: &mut [u8]) { assert!(source.len() == 16 && dest.len() == 64); // extract alpha index table (stored as little endian 64-bit value) let alpha_table = source[2..8] .iter() .rev() .fold(0, |t, &b| (t << 8) | u64::from(b)); // alhpa level decode let alphas = alpha_table_dxt5(source[0], source[1]); // serialize alpha for i in 0..16 { dest[i * 4 + 3] = alphas[(alpha_table >> (i * 3)) as usize & 7]; } // handle colors decode_dxt_colors(&source[8..16], dest, false); } /// Decodes a 16-byte bock of dxt3 data to a 16xRGBA block fn decode_dxt3_block(source: &[u8], dest: &mut [u8]) { assert!(source.len() == 16 && dest.len() == 64); // extract alpha index table (stored as little endian 64-bit value) let alpha_table = source[0..8] .iter() .rev() .fold(0, |t, &b| (t << 8) | u64::from(b)); // serialize alpha (stored as 4-bit values) for i in 0..16 { dest[i * 4 + 3] = ((alpha_table >> (i * 4)) as u8 & 0xF) * 0x11; } // handle colors decode_dxt_colors(&source[8..16], dest, false); } /// Decodes a 8-byte bock of dxt5 data to a 16xRGB block fn decode_dxt1_block(source: &[u8], dest: &mut [u8]) { assert!(source.len() == 8 && dest.len() == 48); decode_dxt_colors(source, dest, true); } /// Decode a row of DXT1 data to four rows of RGB data. /// source.len() should be a multiple of 8, otherwise this panics. fn decode_dxt1_row(source: &[u8], dest: &mut [u8]) { assert!(source.len() % 8 == 0); let block_count = source.len() / 8; assert!(dest.len() >= block_count * 48); // contains the 16 decoded pixels per block let mut decoded_block = [0u8; 48]; for (x, encoded_block) in source.chunks(8).enumerate() { decode_dxt1_block(encoded_block, &mut decoded_block); // copy the values from the decoded block to linewise RGB layout for line in 0..4 { let offset = (block_count * line + x) * 12; dest[offset..offset + 12].copy_from_slice(&decoded_block[line * 12..(line + 1) * 12]); } } } /// Decode a row of DXT3 data to four rows of RGBA data. /// source.len() should be a multiple of 16, otherwise this panics. fn decode_dxt3_row(source: &[u8], dest: &mut [u8]) { assert!(source.len() % 16 == 0); let block_count = source.len() / 16; assert!(dest.len() >= block_count * 64); // contains the 16 decoded pixels per block let mut decoded_block = [0u8; 64]; for (x, encoded_block) in source.chunks(16).enumerate() { decode_dxt3_block(encoded_block, &mut decoded_block); // copy the values from the decoded block to linewise RGB layout for line in 0..4 { let offset = (block_count * line + x) * 16; dest[offset..offset + 16].copy_from_slice(&decoded_block[line * 16..(line + 1) * 16]); } } } /// Decode a row of DXT5 data to four rows of RGBA data. /// source.len() should be a multiple of 16, otherwise this panics. fn decode_dxt5_row(source: &[u8], dest: &mut [u8]) { assert!(source.len() % 16 == 0); let block_count = source.len() / 16; assert!(dest.len() >= block_count * 64); // contains the 16 decoded pixels per block let mut decoded_block = [0u8; 64]; for (x, encoded_block) in source.chunks(16).enumerate() { decode_dxt5_block(encoded_block, &mut decoded_block); // copy the values from the decoded block to linewise RGB layout for line in 0..4 { let offset = (block_count * line + x) * 16; dest[offset..offset + 16].copy_from_slice(&decoded_block[line * 16..(line + 1) * 16]); } } } /* * Functions for encoding DXT compression */ /// Tries to perform the color encoding part of dxt compression /// the approach taken is simple, it picks unique combinations /// of the colors present in the block, and attempts to encode the /// block with each, picking the encoding that yields the least /// squared error out of all of them. /// /// This could probably be faster but is already reasonably fast /// and a good reference impl to optimize others against. /// /// Another way to perform this analysis would be to perform a /// singular value decomposition of the different colors, and /// then pick 2 points on this line as the base colors. But /// this is still rather unwieldy math and has issues /// with the 3-linear-colors-and-0 case, it's also worse /// at conserving the original colors. /// /// source: should be RGBAx16 or RGBx16 bytes of data, /// dest 8 bytes of resulting encoded color data fn encode_dxt_colors(source: &[u8], dest: &mut [u8], is_dxt1: bool) { // sanity checks and determine stride when parsing the source data assert!((source.len() == 64 || source.len() == 48) && dest.len() == 8); let stride = source.len() / 16; // reference colors array let mut colors = [[0u8; 3]; 4]; // Put the colors we're going to be processing in an array with pure RGB layout // note: we reverse the pixel order here. The reason for this is found in the inner quantization loop. let mut targets = [[0u8; 3]; 16]; for (s, d) in source.chunks(stride).rev().zip(&mut targets) { *d = [s[0], s[1], s[2]]; } // roundtrip all colors through the r5g6b5 encoding for rgb in &mut targets { *rgb = enc565_decode(enc565_encode(*rgb)); } // and deduplicate the set of colors to choose from as the algorithm is O(N^2) in this let mut colorspace_ = [[0u8; 3]; 16]; let mut colorspace_len = 0; for color in &targets { if !colorspace_[..colorspace_len].contains(color) { colorspace_[colorspace_len] = *color; colorspace_len += 1; } } let mut colorspace = &colorspace_[..colorspace_len]; // in case of slight gradients it can happen that there's only one entry left in the color table. // as the resulting banding can be quite bad if we would just left the block at the closest // encodable color, we have a special path here that tries to emulate the wanted color // using the linear interpolation between gradients if colorspace.len() == 1 { // the base color we got from colorspace reduction let ref_rgb = colorspace[0]; // the unreduced color in this block that's the furthest away from the actual block let mut rgb = targets .iter() .cloned() .max_by_key(|rgb| diff(*rgb, ref_rgb)) .unwrap(); // amplify differences by 2.5, which should push them to the next quantized value // if possible without overshoot for i in 0..3 { rgb[i] = ((i16::from(rgb[i]) - i16::from(ref_rgb[i])) * 5 / 2 + i16::from(ref_rgb[i])) as u8; } // roundtrip it through quantization let encoded = enc565_encode(rgb); let rgb = enc565_decode(encoded); // in case this didn't land us a different color the best way to represent this field is // as a single color block if rgb == ref_rgb { dest[0] = encoded as u8; dest[1] = (encoded >> 8) as u8; for d in dest.iter_mut().take(8).skip(2) { *d = 0; } return; } // we did find a separate value: add it to the options so after one round of quantization // we're done colorspace_[1] = rgb; colorspace = &colorspace_[..2]; } // block quantization loop: we basically just try every possible combination, returning // the combination with the least squared error // stores the best candidate colors let mut chosen_colors = [[0; 3]; 4]; // did this index table use the [0,0,0] variant let mut chosen_use_0 = false; // error calculated for the last entry let mut chosen_error = 0xFFFF_FFFFu32; // loop through unique permutations of the colorspace, where c1 != c2 'search: for (i, &c1) in colorspace.iter().enumerate() { colors[0] = c1; for &c2 in &colorspace[0..i] { colors[1] = c2; if is_dxt1 { // what's inside here is ran at most 120 times. for use_0 in 0..2 { // and 240 times here. if use_0 != 0 { // interpolate one color, set the other to 0 for i in 0..3 { colors[2][i] = ((u16::from(colors[0][i]) + u16::from(colors[1][i]) + 1) / 2) as u8; } colors[3] = [0, 0, 0]; } else { // interpolate to get 2 more colors for i in 0..3 { colors[2][i] = ((u16::from(colors[0][i]) * 2 + u16::from(colors[1][i]) + 1) / 3) as u8; colors[3][i] = ((u16::from(colors[0][i]) + u16::from(colors[1][i]) * 2 + 1) / 3) as u8; } } // calculate the total error if we were to quantize the block with these color combinations // both these loops have statically known iteration counts and are well vectorizable // note that the inside of this can be run about 15360 times worst case, i.e. 960 times per // pixel. let total_error = targets .iter() .map(|t| colors.iter().map(|c| diff(*c, *t) as u32).min().unwrap()) .sum(); // update the match if we found a better one if total_error < chosen_error { chosen_colors = colors; chosen_use_0 = use_0 != 0; chosen_error = total_error; // if we've got a perfect or at most 1 LSB off match, we're done if total_error < 4 { break 'search; } } } } else { // what's inside here is ran at most 120 times. // interpolate to get 2 more colors for i in 0..3 { colors[2][i] = ((u16::from(colors[0][i]) * 2 + u16::from(colors[1][i]) + 1) / 3) as u8; colors[3][i] = ((u16::from(colors[0][i]) + u16::from(colors[1][i]) * 2 + 1) / 3) as u8; } // calculate the total error if we were to quantize the block with these color combinations // both these loops have statically known iteration counts and are well vectorizable // note that the inside of this can be run about 15360 times worst case, i.e. 960 times per // pixel. let total_error = targets .iter() .map(|t| colors.iter().map(|c| diff(*c, *t) as u32).min().unwrap()) .sum(); // update the match if we found a better one if total_error < chosen_error { chosen_colors = colors; chosen_error = total_error; // if we've got a perfect or at most 1 LSB off match, we're done if total_error < 4 { break 'search; } } } } } // calculate the final indices // note that targets is already in reverse pixel order, to make the index computation easy. let mut chosen_indices = 0u32; for t in &targets { let (idx, _) = chosen_colors .iter() .enumerate() .min_by_key(|&(_, c)| diff(*c, *t)) .unwrap(); chosen_indices = (chosen_indices << 2) | idx as u32; } // encode the colors let mut color0 = enc565_encode(chosen_colors[0]); let mut color1 = enc565_encode(chosen_colors[1]); // determine encoding. Note that color0 == color1 is impossible at this point if is_dxt1 { if color0 > color1 { if chosen_use_0 { swap(&mut color0, &mut color1); // Indexes are packed 2 bits wide, swap index 0/1 but preserve 2/3. let filter = (chosen_indices & 0xAAAA_AAAA) >> 1; chosen_indices ^= filter ^ 0x5555_5555; } } else if !chosen_use_0 { swap(&mut color0, &mut color1); // Indexes are packed 2 bits wide, swap index 0/1 and 2/3. chosen_indices ^= 0x5555_5555; } } // encode everything. dest[0] = color0 as u8; dest[1] = (color0 >> 8) as u8; dest[2] = color1 as u8; dest[3] = (color1 >> 8) as u8; for i in 0..4 { dest[i + 4] = (chosen_indices >> (i * 8)) as u8; } } /// Encodes a buffer of 16 alpha bytes into a dxt5 alpha index table, /// where the alpha table they are indexed against is created by /// calling alpha_table_dxt5(alpha0, alpha1) /// returns the resulting error and alpha table fn encode_dxt5_alpha(alpha0: u8, alpha1: u8, alphas: &[u8; 16]) -> (i32, u64) { // create a table for the given alpha ranges let table = alpha_table_dxt5(alpha0, alpha1); let mut indices = 0u64; let mut total_error = 0i32; // least error brute force search for (i, &a) in alphas.iter().enumerate() { let (index, error) = table .iter() .enumerate() .map(|(i, &e)| (i, square(i32::from(e) - i32::from(a)))) .min_by_key(|&(_, e)| e) .unwrap(); total_error += error; indices |= (index as u64) << (i * 3); } (total_error, indices) } /// Encodes a RGBAx16 sequence of bytes to a 16 bytes DXT5 block fn encode_dxt5_block(source: &[u8], dest: &mut [u8]) { assert!(source.len() == 64 && dest.len() == 16); // perform dxt color encoding encode_dxt_colors(source, &mut dest[8..16], false); // copy out the alpha bytes let mut alphas = [0; 16]; for i in 0..16 { alphas[i] = source[i * 4 + 3]; } // try both alpha compression methods, see which has the least error. let alpha07 = alphas.iter().cloned().min().unwrap(); let alpha17 = alphas.iter().cloned().max().unwrap(); let (error7, indices7) = encode_dxt5_alpha(alpha07, alpha17, &alphas); // if all alphas are 0 or 255 it doesn't particularly matter what we do here. let alpha05 = alphas .iter() .cloned() .filter(|&i| i != 255) .max() .unwrap_or(255); let alpha15 = alphas .iter() .cloned() .filter(|&i| i != 0) .min() .unwrap_or(0); let (error5, indices5) = encode_dxt5_alpha(alpha05, alpha15, &alphas); // pick the best one, encode the min/max values let mut alpha_table = if error5 < error7 { dest[0] = alpha05; dest[1] = alpha15; indices5 } else { dest[0] = alpha07; dest[1] = alpha17; indices7 }; // encode the alphas for byte in dest[2..8].iter_mut() { *byte = alpha_table as u8; alpha_table >>= 8; } } /// Encodes a RGBAx16 sequence of bytes into a 16 bytes DXT3 block fn encode_dxt3_block(source: &[u8], dest: &mut [u8]) { assert!(source.len() == 64 && dest.len() == 16); // perform dxt color encoding encode_dxt_colors(source, &mut dest[8..16], false); // DXT3 alpha compression is very simple, just round towards the nearest value // index the alpha values into the 64bit alpha table let mut alpha_table = 0u64; for i in 0..16 { let alpha = u64::from(source[i * 4 + 3]); let alpha = (alpha + 0x8) / 0x11; alpha_table |= alpha << (i * 4); } // encode the alpha values for byte in &mut dest[0..8] { *byte = alpha_table as u8; alpha_table >>= 8; } } /// Encodes a RGBx16 sequence of bytes into a 8 bytes DXT1 block fn encode_dxt1_block(source: &[u8], dest: &mut [u8]) { assert!(source.len() == 48 && dest.len() == 8); // perform dxt color encoding encode_dxt_colors(source, dest, true); } /// Decode a row of DXT1 data to four rows of RGBA data. /// source.len() should be a multiple of 8, otherwise this panics. fn encode_dxt1_row(source: &[u8]) -> Vec { assert!(source.len() % 48 == 0); let block_count = source.len() / 48; let mut dest = vec![0u8; block_count * 8]; // contains the 16 decoded pixels per block let mut decoded_block = [0u8; 48]; for (x, encoded_block) in dest.chunks_mut(8).enumerate() { // copy the values from the decoded block to linewise RGB layout for line in 0..4 { let offset = (block_count * line + x) * 12; decoded_block[line * 12..(line + 1) * 12].copy_from_slice(&source[offset..offset + 12]); } encode_dxt1_block(&decoded_block, encoded_block); } dest } /// Decode a row of DXT3 data to four rows of RGBA data. /// source.len() should be a multiple of 16, otherwise this panics. fn encode_dxt3_row(source: &[u8]) -> Vec { assert!(source.len() % 64 == 0); let block_count = source.len() / 64; let mut dest = vec![0u8; block_count * 16]; // contains the 16 decoded pixels per block let mut decoded_block = [0u8; 64]; for (x, encoded_block) in dest.chunks_mut(16).enumerate() { // copy the values from the decoded block to linewise RGB layout for line in 0..4 { let offset = (block_count * line + x) * 16; decoded_block[line * 16..(line + 1) * 16].copy_from_slice(&source[offset..offset + 16]); } encode_dxt3_block(&decoded_block, encoded_block); } dest } /// Decode a row of DXT5 data to four rows of RGBA data. /// source.len() should be a multiple of 16, otherwise this panics. fn encode_dxt5_row(source: &[u8]) -> Vec { assert!(source.len() % 64 == 0); let block_count = source.len() / 64; let mut dest = vec![0u8; block_count * 16]; // contains the 16 decoded pixels per block let mut decoded_block = [0u8; 64]; for (x, encoded_block) in dest.chunks_mut(16).enumerate() { // copy the values from the decoded block to linewise RGB layout for line in 0..4 { let offset = (block_count * line + x) * 16; decoded_block[line * 16..(line + 1) * 16].copy_from_slice(&source[offset..offset + 16]); } encode_dxt5_block(&decoded_block, encoded_block); } dest } image-0.24.7/src/codecs/farbfeld.rs000064400000000000000000000312441046102023000151460ustar 00000000000000//! Decoding of farbfeld images //! //! farbfeld is a lossless image format which is easy to parse, pipe and compress. //! //! It has the following format: //! //! | Bytes | Description | //! |--------|---------------------------------------------------------| //! | 8 | "farbfeld" magic value | //! | 4 | 32-Bit BE unsigned integer (width) | //! | 4 | 32-Bit BE unsigned integer (height) | //! | [2222] | 4⋅16-Bit BE unsigned integers [RGBA] / pixel, row-major | //! //! The RGB-data should be sRGB for best interoperability and not alpha-premultiplied. //! //! # Related Links //! * - the farbfeld specification use std::convert::TryFrom; use std::i64; use std::io::{self, Read, Seek, SeekFrom, Write}; use byteorder::{BigEndian, ByteOrder, NativeEndian}; use crate::color::ColorType; use crate::error::{ DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{self, ImageDecoder, ImageDecoderRect, ImageEncoder, ImageFormat, Progress}; /// farbfeld Reader pub struct FarbfeldReader { width: u32, height: u32, inner: R, /// Relative to the start of the pixel data current_offset: u64, cached_byte: Option, } impl FarbfeldReader { fn new(mut buffered_read: R) -> ImageResult> { fn read_dimm(from: &mut R) -> ImageResult { let mut buf = [0u8; 4]; from.read_exact(&mut buf).map_err(|err| { ImageError::Decoding(DecodingError::new(ImageFormat::Farbfeld.into(), err)) })?; Ok(BigEndian::read_u32(&buf)) } let mut magic = [0u8; 8]; buffered_read.read_exact(&mut magic).map_err(|err| { ImageError::Decoding(DecodingError::new(ImageFormat::Farbfeld.into(), err)) })?; if &magic != b"farbfeld" { return Err(ImageError::Decoding(DecodingError::new( ImageFormat::Farbfeld.into(), format!("Invalid magic: {:02x?}", magic), ))); } let reader = FarbfeldReader { width: read_dimm(&mut buffered_read)?, height: read_dimm(&mut buffered_read)?, inner: buffered_read, current_offset: 0, cached_byte: None, }; if crate::utils::check_dimension_overflow( reader.width, reader.height, // ColorType is always rgba16 ColorType::Rgba16.bytes_per_pixel(), ) { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Farbfeld.into(), UnsupportedErrorKind::GenericFeature(format!( "Image dimensions ({}x{}) are too large", reader.width, reader.height )), ), )); } Ok(reader) } } impl Read for FarbfeldReader { fn read(&mut self, mut buf: &mut [u8]) -> io::Result { let mut bytes_written = 0; if let Some(byte) = self.cached_byte.take() { buf[0] = byte; buf = &mut buf[1..]; bytes_written = 1; self.current_offset += 1; } if buf.len() == 1 { buf[0] = cache_byte(&mut self.inner, &mut self.cached_byte)?; bytes_written += 1; self.current_offset += 1; } else { for channel_out in buf.chunks_exact_mut(2) { consume_channel(&mut self.inner, channel_out)?; bytes_written += 2; self.current_offset += 2; } } Ok(bytes_written) } } impl Seek for FarbfeldReader { fn seek(&mut self, pos: SeekFrom) -> io::Result { fn parse_offset(original_offset: u64, end_offset: u64, pos: SeekFrom) -> Option { match pos { SeekFrom::Start(off) => i64::try_from(off) .ok()? .checked_sub(i64::try_from(original_offset).ok()?), SeekFrom::End(off) => { if off < i64::try_from(end_offset).unwrap_or(i64::MAX) { None } else { Some(i64::try_from(end_offset.checked_sub(original_offset)?).ok()? + off) } } SeekFrom::Current(off) => { if off < i64::try_from(original_offset).unwrap_or(i64::MAX) { None } else { Some(off) } } } } let original_offset = self.current_offset; let end_offset = self.width as u64 * self.height as u64 * 2; let offset_from_current = parse_offset(original_offset, end_offset, pos).ok_or_else(|| { io::Error::new( io::ErrorKind::InvalidInput, "invalid seek to a negative or overflowing position", ) })?; // TODO: convert to seek_relative() once that gets stabilised self.inner.seek(SeekFrom::Current(offset_from_current))?; self.current_offset = if offset_from_current < 0 { original_offset.checked_sub(offset_from_current.wrapping_neg() as u64) } else { original_offset.checked_add(offset_from_current as u64) } .expect("This should've been checked above"); if self.current_offset < end_offset && self.current_offset % 2 == 1 { let curr = self.inner.seek(SeekFrom::Current(-1))?; cache_byte(&mut self.inner, &mut self.cached_byte)?; self.inner.seek(SeekFrom::Start(curr))?; } else { self.cached_byte = None; } Ok(original_offset) } } fn consume_channel(from: &mut R, to: &mut [u8]) -> io::Result<()> { let mut ibuf = [0u8; 2]; from.read_exact(&mut ibuf)?; NativeEndian::write_u16(to, BigEndian::read_u16(&ibuf)); Ok(()) } fn cache_byte(from: &mut R, cached_byte: &mut Option) -> io::Result { let mut obuf = [0u8; 2]; consume_channel(from, &mut obuf)?; *cached_byte = Some(obuf[1]); Ok(obuf[0]) } /// farbfeld decoder pub struct FarbfeldDecoder { reader: FarbfeldReader, } impl FarbfeldDecoder { /// Creates a new decoder that decodes from the stream ```r``` pub fn new(buffered_read: R) -> ImageResult> { Ok(FarbfeldDecoder { reader: FarbfeldReader::new(buffered_read)?, }) } } impl<'a, R: 'a + Read> ImageDecoder<'a> for FarbfeldDecoder { type Reader = FarbfeldReader; fn dimensions(&self) -> (u32, u32) { (self.reader.width, self.reader.height) } fn color_type(&self) -> ColorType { ColorType::Rgba16 } fn into_reader(self) -> ImageResult { Ok(self.reader) } fn scanline_bytes(&self) -> u64 { 2 } } impl<'a, R: 'a + Read + Seek> ImageDecoderRect<'a> for FarbfeldDecoder { fn read_rect_with_progress( &mut self, x: u32, y: u32, width: u32, height: u32, buf: &mut [u8], progress_callback: F, ) -> ImageResult<()> { // A "scanline" (defined as "shortest non-caching read" in the doc) is just one channel in this case let start = self.reader.stream_position()?; image::load_rect( x, y, width, height, buf, progress_callback, self, |s, scanline| s.reader.seek(SeekFrom::Start(scanline * 2)).map(|_| ()), |s, buf| s.reader.read_exact(buf), )?; self.reader.seek(SeekFrom::Start(start))?; Ok(()) } } /// farbfeld encoder pub struct FarbfeldEncoder { w: W, } impl FarbfeldEncoder { /// Create a new encoder that writes its output to ```w```. The writer should be buffered. pub fn new(buffered_writer: W) -> FarbfeldEncoder { FarbfeldEncoder { w: buffered_writer } } /// Encodes the image ```data``` (native endian) /// that has dimensions ```width``` and ```height``` pub fn encode(self, data: &[u8], width: u32, height: u32) -> ImageResult<()> { self.encode_impl(data, width, height)?; Ok(()) } fn encode_impl(mut self, data: &[u8], width: u32, height: u32) -> io::Result<()> { self.w.write_all(b"farbfeld")?; let mut buf = [0u8; 4]; BigEndian::write_u32(&mut buf, width); self.w.write_all(&buf)?; BigEndian::write_u32(&mut buf, height); self.w.write_all(&buf)?; for channel in data.chunks_exact(2) { BigEndian::write_u16(&mut buf, NativeEndian::read_u16(channel)); self.w.write_all(&buf[..2])?; } Ok(()) } } impl ImageEncoder for FarbfeldEncoder { fn write_image( self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()> { if color_type != ColorType::Rgba16 { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Farbfeld.into(), UnsupportedErrorKind::Color(color_type.into()), ), )); } self.encode(buf, width, height) } } #[cfg(test)] mod tests { use crate::codecs::farbfeld::FarbfeldDecoder; use crate::ImageDecoderRect; use byteorder::{ByteOrder, NativeEndian}; use std::io::{Cursor, Seek, SeekFrom}; static RECTANGLE_IN: &[u8] = b"farbfeld\ \x00\x00\x00\x02\x00\x00\x00\x03\ \xFF\x01\xFE\x02\xFD\x03\xFC\x04\xFB\x05\xFA\x06\xF9\x07\xF8\x08\ \xF7\x09\xF6\x0A\xF5\x0B\xF4\x0C\xF3\x0D\xF2\x0E\xF1\x0F\xF0\x10\ \xEF\x11\xEE\x12\xED\x13\xEC\x14\xEB\x15\xEA\x16\xE9\x17\xE8\x18"; #[test] fn read_rect_1x2() { static RECTANGLE_OUT: &[u16] = &[ 0xF30D, 0xF20E, 0xF10F, 0xF010, 0xEB15, 0xEA16, 0xE917, 0xE818, ]; read_rect(1, 1, 1, 2, RECTANGLE_OUT); } #[test] fn read_rect_2x2() { static RECTANGLE_OUT: &[u16] = &[ 0xFF01, 0xFE02, 0xFD03, 0xFC04, 0xFB05, 0xFA06, 0xF907, 0xF808, 0xF709, 0xF60A, 0xF50B, 0xF40C, 0xF30D, 0xF20E, 0xF10F, 0xF010, ]; read_rect(0, 0, 2, 2, RECTANGLE_OUT); } #[test] fn read_rect_2x1() { static RECTANGLE_OUT: &[u16] = &[ 0xEF11, 0xEE12, 0xED13, 0xEC14, 0xEB15, 0xEA16, 0xE917, 0xE818, ]; read_rect(0, 2, 2, 1, RECTANGLE_OUT); } #[test] fn read_rect_2x3() { static RECTANGLE_OUT: &[u16] = &[ 0xFF01, 0xFE02, 0xFD03, 0xFC04, 0xFB05, 0xFA06, 0xF907, 0xF808, 0xF709, 0xF60A, 0xF50B, 0xF40C, 0xF30D, 0xF20E, 0xF10F, 0xF010, 0xEF11, 0xEE12, 0xED13, 0xEC14, 0xEB15, 0xEA16, 0xE917, 0xE818, ]; read_rect(0, 0, 2, 3, RECTANGLE_OUT); } #[test] fn read_rect_in_stream() { static RECTANGLE_OUT: &[u16] = &[0xEF11, 0xEE12, 0xED13, 0xEC14]; let mut input = vec![]; input.extend_from_slice(b"This is a 31-byte-long prologue"); input.extend_from_slice(RECTANGLE_IN); let mut input_cur = Cursor::new(input); input_cur.seek(SeekFrom::Start(31)).unwrap(); let mut out_buf = [0u8; 64]; FarbfeldDecoder::new(input_cur) .unwrap() .read_rect(0, 2, 1, 1, &mut out_buf) .unwrap(); let exp = degenerate_pixels(RECTANGLE_OUT); assert_eq!(&out_buf[..exp.len()], &exp[..]); } #[test] fn dimension_overflow() { let header = b"farbfeld\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"; assert!(FarbfeldDecoder::new(Cursor::new(header)).is_err()); } fn read_rect(x: u32, y: u32, width: u32, height: u32, exp_wide: &[u16]) { let mut out_buf = [0u8; 64]; FarbfeldDecoder::new(Cursor::new(RECTANGLE_IN)) .unwrap() .read_rect(x, y, width, height, &mut out_buf) .unwrap(); let exp = degenerate_pixels(exp_wide); assert_eq!(&out_buf[..exp.len()], &exp[..]); } fn degenerate_pixels(exp_wide: &[u16]) -> Vec { let mut exp = vec![0u8; exp_wide.len() * 2]; NativeEndian::write_u16_into(exp_wide, &mut exp); exp } } image-0.24.7/src/codecs/gif.rs000064400000000000000000000516361046102023000141550ustar 00000000000000//! Decoding of GIF Images //! //! GIF (Graphics Interchange Format) is an image format that supports lossless compression. //! //! # Related Links //! * - The GIF Specification //! //! # Examples //! ```rust,no_run //! use image::codecs::gif::{GifDecoder, GifEncoder}; //! use image::{ImageDecoder, AnimationDecoder}; //! use std::fs::File; //! # fn main() -> std::io::Result<()> { //! // Decode a gif into frames //! let file_in = File::open("foo.gif")?; //! let mut decoder = GifDecoder::new(file_in).unwrap(); //! let frames = decoder.into_frames(); //! let frames = frames.collect_frames().expect("error decoding gif"); //! //! // Encode frames into a gif and save to a file //! let mut file_out = File::open("out.gif")?; //! let mut encoder = GifEncoder::new(file_out); //! encoder.encode_frames(frames.into_iter()); //! # Ok(()) //! # } //! ``` #![allow(clippy::while_let_loop)] use std::convert::TryFrom; use std::convert::TryInto; use std::io::{self, Cursor, Read, Write}; use std::marker::PhantomData; use std::mem; use gif::ColorOutput; use gif::{DisposalMethod, Frame}; use num_rational::Ratio; use crate::animation; use crate::color::{ColorType, Rgba}; use crate::error::{ DecodingError, EncodingError, ImageError, ImageResult, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{self, AnimationDecoder, ImageDecoder, ImageFormat}; use crate::io::Limits; use crate::traits::Pixel; use crate::ImageBuffer; /// GIF decoder pub struct GifDecoder { reader: gif::Decoder, limits: Limits, } impl GifDecoder { /// Creates a new decoder that decodes the input steam `r` pub fn new(r: R) -> ImageResult> { let mut decoder = gif::DecodeOptions::new(); decoder.set_color_output(ColorOutput::RGBA); Ok(GifDecoder { reader: decoder.read_info(r).map_err(ImageError::from_decoding)?, limits: Limits::default(), }) } /// Creates a new decoder that decodes the input steam `r`, using limits `limits` pub fn with_limits(r: R, limits: Limits) -> ImageResult> { let mut decoder = gif::DecodeOptions::new(); decoder.set_color_output(ColorOutput::RGBA); Ok(GifDecoder { reader: decoder.read_info(r).map_err(ImageError::from_decoding)?, limits, }) } } /// Wrapper struct around a `Cursor>` pub struct GifReader(Cursor>, PhantomData); impl Read for GifReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { if self.0.position() == 0 && buf.is_empty() { mem::swap(buf, self.0.get_mut()); Ok(buf.len()) } else { self.0.read_to_end(buf) } } } impl<'a, R: 'a + Read> ImageDecoder<'a> for GifDecoder { type Reader = GifReader; fn dimensions(&self) -> (u32, u32) { ( u32::from(self.reader.width()), u32::from(self.reader.height()), ) } fn color_type(&self) -> ColorType { ColorType::Rgba8 } fn into_reader(self) -> ImageResult { Ok(GifReader( Cursor::new(image::decoder_to_vec(self)?), PhantomData, )) } fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); let frame = match self .reader .next_frame_info() .map_err(ImageError::from_decoding)? { Some(frame) => FrameInfo::new_from_frame(frame), None => { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::NoMoreData, ))) } }; let (width, height) = self.dimensions(); if frame.left == 0 && frame.width == width && (frame.top as u64 + frame.height as u64 <= height as u64) { // If the frame matches the logical screen, or, as a more general case, // fits into it and touches its left and right borders, then // we can directly write it into the buffer without causing line wraparound. let line_length = usize::try_from(width) .unwrap() .checked_mul(self.color_type().bytes_per_pixel() as usize) .unwrap(); // isolate the portion of the buffer to read the frame data into. // the chunks above and below it are going to be zeroed. let (blank_top, rest) = buf.split_at_mut(line_length.checked_mul(frame.top as usize).unwrap()); let (buf, blank_bottom) = rest.split_at_mut(line_length.checked_mul(frame.height as usize).unwrap()); debug_assert_eq!(buf.len(), self.reader.buffer_size()); // this is only necessary in case the buffer is not zeroed for b in blank_top { *b = 0; } // fill the middle section with the frame data self.reader .read_into_buffer(buf) .map_err(ImageError::from_decoding)?; // this is only necessary in case the buffer is not zeroed for b in blank_bottom { *b = 0; } } else { // If the frame does not match the logical screen, read into an extra buffer // and 'insert' the frame from left/top to logical screen width/height. let buffer_size = self.reader.buffer_size(); self.limits.reserve_usize(buffer_size)?; let mut frame_buffer = vec![0; buffer_size]; self.limits.free_usize(buffer_size); self.reader .read_into_buffer(&mut frame_buffer[..]) .map_err(ImageError::from_decoding)?; let frame_buffer = ImageBuffer::from_raw(frame.width, frame.height, frame_buffer); let image_buffer = ImageBuffer::from_raw(width, height, buf); // `buffer_size` uses wrapping arithmetic, thus might not report the // correct storage requirement if the result does not fit in `usize`. // `ImageBuffer::from_raw` detects overflow and reports by returning `None`. if frame_buffer.is_none() || image_buffer.is_none() { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Gif.into(), UnsupportedErrorKind::GenericFeature(format!( "Image dimensions ({}, {}) are too large", frame.width, frame.height )), ), )); } let frame_buffer = frame_buffer.unwrap(); let mut image_buffer = image_buffer.unwrap(); for (x, y, pixel) in image_buffer.enumerate_pixels_mut() { let frame_x = x.wrapping_sub(frame.left); let frame_y = y.wrapping_sub(frame.top); if frame_x < frame.width && frame_y < frame.height { *pixel = *frame_buffer.get_pixel(frame_x, frame_y); } else { // this is only necessary in case the buffer is not zeroed *pixel = Rgba([0, 0, 0, 0]); } } } Ok(()) } } struct GifFrameIterator { reader: gif::Decoder, width: u32, height: u32, non_disposed_frame: ImageBuffer, Vec>, } impl GifFrameIterator { fn new(decoder: GifDecoder) -> GifFrameIterator { let (width, height) = decoder.dimensions(); // intentionally ignore the background color for web compatibility // create the first non disposed frame let non_disposed_frame = ImageBuffer::from_pixel(width, height, Rgba([0, 0, 0, 0])); GifFrameIterator { reader: decoder.reader, width, height, non_disposed_frame, } } } impl Iterator for GifFrameIterator { type Item = ImageResult; fn next(&mut self) -> Option> { // begin looping over each frame let frame = match self.reader.next_frame_info() { Ok(frame_info) => { if let Some(frame) = frame_info { FrameInfo::new_from_frame(frame) } else { // no more frames return None; } } Err(err) => return Some(Err(ImageError::from_decoding(err))), }; let mut vec = vec![0; self.reader.buffer_size()]; if let Err(err) = self.reader.read_into_buffer(&mut vec) { return Some(Err(ImageError::from_decoding(err))); } // create the image buffer from the raw frame. // `buffer_size` uses wrapping arithmetic, thus might not report the // correct storage requirement if the result does not fit in `usize`. // on the other hand, `ImageBuffer::from_raw` detects overflow and // reports by returning `None`. let mut frame_buffer = match ImageBuffer::from_raw(frame.width, frame.height, vec) { Some(frame_buffer) => frame_buffer, None => { return Some(Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Gif.into(), UnsupportedErrorKind::GenericFeature(format!( "Image dimensions ({}, {}) are too large", frame.width, frame.height )), ), ))) } }; // blend the current frame with the non-disposed frame, then update // the non-disposed frame according to the disposal method. fn blend_and_dispose_pixel( dispose: DisposalMethod, previous: &mut Rgba, current: &mut Rgba, ) { let pixel_alpha = current.channels()[3]; if pixel_alpha == 0 { *current = *previous; } match dispose { DisposalMethod::Any | DisposalMethod::Keep => { // do not dispose // (keep pixels from this frame) // note: the `Any` disposal method is underspecified in the GIF // spec, but most viewers treat it identically to `Keep` *previous = *current; } DisposalMethod::Background => { // restore to background color // (background shows through transparent pixels in the next frame) *previous = Rgba([0, 0, 0, 0]); } DisposalMethod::Previous => { // restore to previous // (dispose frames leaving the last none disposal frame) } } } // if `frame_buffer`'s frame exactly matches the entire image, then // use it directly, else create a new buffer to hold the composited // image. let image_buffer = if (frame.left, frame.top) == (0, 0) && (self.width, self.height) == frame_buffer.dimensions() { for (x, y, pixel) in frame_buffer.enumerate_pixels_mut() { let previous_pixel = self.non_disposed_frame.get_pixel_mut(x, y); blend_and_dispose_pixel(frame.disposal_method, previous_pixel, pixel); } frame_buffer } else { ImageBuffer::from_fn(self.width, self.height, |x, y| { let frame_x = x.wrapping_sub(frame.left); let frame_y = y.wrapping_sub(frame.top); let previous_pixel = self.non_disposed_frame.get_pixel_mut(x, y); if frame_x < frame_buffer.width() && frame_y < frame_buffer.height() { let mut pixel = *frame_buffer.get_pixel(frame_x, frame_y); blend_and_dispose_pixel(frame.disposal_method, previous_pixel, &mut pixel); pixel } else { // out of bounds, return pixel from previous frame *previous_pixel } }) }; Some(Ok(animation::Frame::from_parts( image_buffer, 0, 0, frame.delay, ))) } } impl<'a, R: Read + 'a> AnimationDecoder<'a> for GifDecoder { fn into_frames(self) -> animation::Frames<'a> { animation::Frames::new(Box::new(GifFrameIterator::new(self))) } } struct FrameInfo { left: u32, top: u32, width: u32, height: u32, disposal_method: DisposalMethod, delay: animation::Delay, } impl FrameInfo { fn new_from_frame(frame: &Frame) -> FrameInfo { FrameInfo { left: u32::from(frame.left), top: u32::from(frame.top), width: u32::from(frame.width), height: u32::from(frame.height), disposal_method: frame.dispose, // frame.delay is in units of 10ms so frame.delay*10 is in ms delay: animation::Delay::from_ratio(Ratio::new(u32::from(frame.delay) * 10, 1)), } } } /// Number of repetitions for a GIF animation #[derive(Clone, Copy, Debug)] pub enum Repeat { /// Finite number of repetitions Finite(u16), /// Looping GIF Infinite, } impl Repeat { pub(crate) fn to_gif_enum(&self) -> gif::Repeat { match self { Repeat::Finite(n) => gif::Repeat::Finite(*n), Repeat::Infinite => gif::Repeat::Infinite, } } } /// GIF encoder. pub struct GifEncoder { w: Option, gif_encoder: Option>, speed: i32, repeat: Option, } impl GifEncoder { /// Creates a new GIF encoder with a speed of 1. This prioritizes quality over performance at any cost. pub fn new(w: W) -> GifEncoder { Self::new_with_speed(w, 1) } /// Create a new GIF encoder, and has the speed parameter `speed`. See /// [`Frame::from_rgba_speed`](https://docs.rs/gif/latest/gif/struct.Frame.html#method.from_rgba_speed) /// for more information. pub fn new_with_speed(w: W, speed: i32) -> GifEncoder { assert!( (1..=30).contains(&speed), "speed needs to be in the range [1, 30]" ); GifEncoder { w: Some(w), gif_encoder: None, speed, repeat: None, } } /// Set the repeat behaviour of the encoded GIF pub fn set_repeat(&mut self, repeat: Repeat) -> ImageResult<()> { if let Some(ref mut encoder) = self.gif_encoder { encoder .set_repeat(repeat.to_gif_enum()) .map_err(ImageError::from_encoding)?; } self.repeat = Some(repeat); Ok(()) } /// Encode a single image. pub fn encode( &mut self, data: &[u8], width: u32, height: u32, color: ColorType, ) -> ImageResult<()> { let (width, height) = self.gif_dimensions(width, height)?; match color { ColorType::Rgb8 => self.encode_gif(Frame::from_rgb(width, height, data)), ColorType::Rgba8 => { self.encode_gif(Frame::from_rgba(width, height, &mut data.to_owned())) } _ => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Gif.into(), UnsupportedErrorKind::Color(color.into()), ), )), } } /// Encode one frame of animation. pub fn encode_frame(&mut self, img_frame: animation::Frame) -> ImageResult<()> { let frame = self.convert_frame(img_frame)?; self.encode_gif(frame) } /// Encodes Frames. /// Consider using `try_encode_frames` instead to encode an `animation::Frames` like iterator. pub fn encode_frames(&mut self, frames: F) -> ImageResult<()> where F: IntoIterator, { for img_frame in frames { self.encode_frame(img_frame)?; } Ok(()) } /// Try to encode a collection of `ImageResult` objects. /// Use this function to encode an `animation::Frames` like iterator. /// Whenever an `Err` item is encountered, that value is returned without further actions. pub fn try_encode_frames(&mut self, frames: F) -> ImageResult<()> where F: IntoIterator>, { for img_frame in frames { self.encode_frame(img_frame?)?; } Ok(()) } pub(crate) fn convert_frame( &mut self, img_frame: animation::Frame, ) -> ImageResult> { // get the delay before converting img_frame let frame_delay = img_frame.delay().into_ratio().to_integer(); // convert img_frame into RgbaImage let mut rbga_frame = img_frame.into_buffer(); let (width, height) = self.gif_dimensions(rbga_frame.width(), rbga_frame.height())?; // Create the gif::Frame from the animation::Frame let mut frame = Frame::from_rgba_speed(width, height, &mut rbga_frame, self.speed); // Saturate the conversion to u16::MAX instead of returning an error as that // would require a new special cased variant in ParameterErrorKind which most // likely couldn't be reused for other cases. This isn't a bad trade-off given // that the current algorithm is already lossy. frame.delay = (frame_delay / 10).try_into().unwrap_or(std::u16::MAX); Ok(frame) } fn gif_dimensions(&self, width: u32, height: u32) -> ImageResult<(u16, u16)> { fn inner_dimensions(width: u32, height: u32) -> Option<(u16, u16)> { let width = u16::try_from(width).ok()?; let height = u16::try_from(height).ok()?; Some((width, height)) } // TODO: this is not very idiomatic yet. Should return an EncodingError. inner_dimensions(width, height).ok_or_else(|| { ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, )) }) } pub(crate) fn encode_gif(&mut self, mut frame: Frame) -> ImageResult<()> { let gif_encoder; if let Some(ref mut encoder) = self.gif_encoder { gif_encoder = encoder; } else { let writer = self.w.take().unwrap(); let mut encoder = gif::Encoder::new(writer, frame.width, frame.height, &[]) .map_err(ImageError::from_encoding)?; if let Some(ref repeat) = self.repeat { encoder .set_repeat(repeat.to_gif_enum()) .map_err(ImageError::from_encoding)?; } self.gif_encoder = Some(encoder); gif_encoder = self.gif_encoder.as_mut().unwrap() } frame.dispose = gif::DisposalMethod::Background; gif_encoder .write_frame(&frame) .map_err(ImageError::from_encoding) } } impl ImageError { fn from_decoding(err: gif::DecodingError) -> ImageError { use gif::DecodingError::*; match err { err @ Format(_) => { ImageError::Decoding(DecodingError::new(ImageFormat::Gif.into(), err)) } Io(io_err) => ImageError::IoError(io_err), } } fn from_encoding(err: gif::EncodingError) -> ImageError { use gif::EncodingError::*; match err { err @ Format(_) => { ImageError::Encoding(EncodingError::new(ImageFormat::Gif.into(), err)) } Io(io_err) => ImageError::IoError(io_err), } } } #[cfg(test)] mod test { use super::*; #[test] fn frames_exceeding_logical_screen_size() { // This is a gif with 10x10 logical screen, but a 16x16 frame + 6px offset inside. let data = vec![ 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x0A, 0x00, 0x0A, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0x1F, 0x21, 0xF9, 0x04, 0x09, 0x64, 0x00, 0x00, 0x00, 0x2C, 0x06, 0x00, 0x06, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x23, 0x84, 0x8F, 0xA9, 0xBB, 0xE1, 0xE8, 0x42, 0x8A, 0x0F, 0x50, 0x79, 0xAE, 0xD1, 0xF9, 0x7A, 0xE8, 0x71, 0x5B, 0x48, 0x81, 0x64, 0xD5, 0x91, 0xCA, 0x89, 0x4D, 0x21, 0x63, 0x89, 0x4C, 0x09, 0x77, 0xF5, 0x6D, 0x14, 0x00, 0x3B, ]; let decoder = GifDecoder::new(Cursor::new(data)).unwrap(); let mut buf = vec![0u8; decoder.total_bytes() as usize]; assert!(decoder.read_image(&mut buf).is_ok()); } } image-0.24.7/src/codecs/hdr/decoder.rs000064400000000000000000001074471046102023000155740ustar 00000000000000use crate::Primitive; use num_traits::identities::Zero; #[cfg(test)] use std::borrow::Cow; use std::convert::TryFrom; use std::io::{self, BufRead, Cursor, Read, Seek}; use std::iter::Iterator; use std::marker::PhantomData; use std::num::{ParseFloatError, ParseIntError}; use std::path::Path; use std::{error, fmt, mem}; use crate::color::{ColorType, Rgb}; use crate::error::{ DecodingError, ImageError, ImageFormatHint, ImageResult, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{self, ImageDecoder, ImageDecoderRect, ImageFormat, Progress}; /// Errors that can occur during decoding and parsing of a HDR image #[derive(Debug, Clone, PartialEq, Eq)] enum DecoderError { /// HDR's "#?RADIANCE" signature wrong or missing RadianceHdrSignatureInvalid, /// EOF before end of header TruncatedHeader, /// EOF instead of image dimensions TruncatedDimensions, /// A value couldn't be parsed UnparsableF32(LineType, ParseFloatError), /// A value couldn't be parsed UnparsableU32(LineType, ParseIntError), /// Not enough numbers in line LineTooShort(LineType), /// COLORCORR contains too many numbers in strict mode ExtraneousColorcorrNumbers, /// Dimensions line had too few elements DimensionsLineTooShort(usize, usize), /// Dimensions line had too many elements DimensionsLineTooLong(usize), /// The length of a scanline (1) wasn't a match for the specified length (2) WrongScanlineLength(usize, usize), /// First pixel of a scanline is a run length marker FirstPixelRlMarker, } impl fmt::Display for DecoderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DecoderError::RadianceHdrSignatureInvalid => { f.write_str("Radiance HDR signature not found") } DecoderError::TruncatedHeader => f.write_str("EOF in header"), DecoderError::TruncatedDimensions => f.write_str("EOF in dimensions line"), DecoderError::UnparsableF32(line, pe) => { f.write_fmt(format_args!("Cannot parse {} value as f32: {}", line, pe)) } DecoderError::UnparsableU32(line, pe) => { f.write_fmt(format_args!("Cannot parse {} value as u32: {}", line, pe)) } DecoderError::LineTooShort(line) => { f.write_fmt(format_args!("Not enough numbers in {}", line)) } DecoderError::ExtraneousColorcorrNumbers => f.write_str("Extra numbers in COLORCORR"), DecoderError::DimensionsLineTooShort(elements, expected) => f.write_fmt(format_args!( "Dimensions line too short: have {} elements, expected {}", elements, expected )), DecoderError::DimensionsLineTooLong(expected) => f.write_fmt(format_args!( "Dimensions line too long, expected {} elements", expected )), DecoderError::WrongScanlineLength(len, expected) => f.write_fmt(format_args!( "Wrong length of decoded scanline: got {}, expected {}", len, expected )), DecoderError::FirstPixelRlMarker => { f.write_str("First pixel of a scanline shouldn't be run length marker") } } } } impl From for ImageError { fn from(e: DecoderError) -> ImageError { ImageError::Decoding(DecodingError::new(ImageFormat::Hdr.into(), e)) } } impl error::Error for DecoderError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { DecoderError::UnparsableF32(_, err) => Some(err), DecoderError::UnparsableU32(_, err) => Some(err), _ => None, } } } /// Lines which contain parsable data that can fail #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] enum LineType { Exposure, Pixaspect, Colorcorr, DimensionsHeight, DimensionsWidth, } impl fmt::Display for LineType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { LineType::Exposure => "EXPOSURE", LineType::Pixaspect => "PIXASPECT", LineType::Colorcorr => "COLORCORR", LineType::DimensionsHeight => "height dimension", LineType::DimensionsWidth => "width dimension", }) } } /// Adapter to conform to `ImageDecoder` trait #[derive(Debug)] pub struct HdrAdapter { inner: Option>, // data: Option>, meta: HdrMetadata, } impl HdrAdapter { /// Creates adapter pub fn new(r: R) -> ImageResult> { let decoder = HdrDecoder::new(r)?; let meta = decoder.metadata(); Ok(HdrAdapter { inner: Some(decoder), meta, }) } /// Allows reading old Radiance HDR images pub fn new_nonstrict(r: R) -> ImageResult> { let decoder = HdrDecoder::with_strictness(r, false)?; let meta = decoder.metadata(); Ok(HdrAdapter { inner: Some(decoder), meta, }) } /// Read the actual data of the image, and store it in Self::data. fn read_image_data(&mut self, buf: &mut [u8]) -> ImageResult<()> { assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); match self.inner.take() { Some(decoder) => { let img: Vec> = decoder.read_image_ldr()?; for (i, Rgb(data)) in img.into_iter().enumerate() { buf[(i * 3)..][..3].copy_from_slice(&data); } Ok(()) } None => Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::NoMoreData, ))), } } } /// Wrapper struct around a `Cursor>` pub struct HdrReader(Cursor>, PhantomData); impl Read for HdrReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { if self.0.position() == 0 && buf.is_empty() { mem::swap(buf, self.0.get_mut()); Ok(buf.len()) } else { self.0.read_to_end(buf) } } } impl<'a, R: 'a + BufRead> ImageDecoder<'a> for HdrAdapter { type Reader = HdrReader; fn dimensions(&self) -> (u32, u32) { (self.meta.width, self.meta.height) } fn color_type(&self) -> ColorType { ColorType::Rgb8 } fn into_reader(self) -> ImageResult { Ok(HdrReader( Cursor::new(image::decoder_to_vec(self)?), PhantomData, )) } fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { self.read_image_data(buf) } } impl<'a, R: 'a + BufRead + Seek> ImageDecoderRect<'a> for HdrAdapter { fn read_rect_with_progress( &mut self, x: u32, y: u32, width: u32, height: u32, buf: &mut [u8], progress_callback: F, ) -> ImageResult<()> { image::load_rect( x, y, width, height, buf, progress_callback, self, |_, _| unreachable!(), |s, buf| s.read_image_data(buf), ) } } /// Radiance HDR file signature pub const SIGNATURE: &[u8] = b"#?RADIANCE"; const SIGNATURE_LENGTH: usize = 10; /// An Radiance HDR decoder #[derive(Debug)] pub struct HdrDecoder { r: R, width: u32, height: u32, meta: HdrMetadata, } /// Refer to [wikipedia](https://en.wikipedia.org/wiki/RGBE_image_format) #[repr(C)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Rgbe8Pixel { /// Color components pub c: [u8; 3], /// Exponent pub e: u8, } /// Creates `Rgbe8Pixel` from components pub fn rgbe8(r: u8, g: u8, b: u8, e: u8) -> Rgbe8Pixel { Rgbe8Pixel { c: [r, g, b], e } } impl Rgbe8Pixel { /// Converts `Rgbe8Pixel` into `Rgb` linearly #[inline] pub fn to_hdr(self) -> Rgb { if self.e == 0 { Rgb([0.0, 0.0, 0.0]) } else { // let exp = f32::ldexp(1., self.e as isize - (128 + 8)); // unstable let exp = f32::exp2(>::from(self.e) - (128.0 + 8.0)); Rgb([ exp * >::from(self.c[0]), exp * >::from(self.c[1]), exp * >::from(self.c[2]), ]) } } /// Converts `Rgbe8Pixel` into `Rgb` with scale=1 and gamma=2.2 /// /// color_ldr = (color_hdr*scale)gamma /// /// # Panic /// /// Panics when `T::max_value()` cannot be represented as f32. #[inline] pub fn to_ldr(self) -> Rgb { self.to_ldr_scale_gamma(1.0, 2.2) } /// Converts `Rgbe8Pixel` into `Rgb` using provided scale and gamma /// /// color_ldr = (color_hdr*scale)gamma /// /// # Panic /// /// Panics when `T::max_value()` cannot be represented as f32. /// Panics when scale or gamma is NaN #[inline] pub fn to_ldr_scale_gamma(self, scale: f32, gamma: f32) -> Rgb { let Rgb(data) = self.to_hdr(); let (r, g, b) = (data[0], data[1], data[2]); #[inline] fn sg(v: f32, scale: f32, gamma: f32) -> T { let t_max = T::max_value(); // Disassembly shows that t_max_f32 is compiled into constant let t_max_f32: f32 = num_traits::NumCast::from(t_max) .expect("to_ldr_scale_gamma: maximum value of type is not representable as f32"); let fv = f32::powf(v * scale, gamma) * t_max_f32 + 0.5; if fv < 0.0 { T::zero() } else if fv > t_max_f32 { t_max } else { num_traits::NumCast::from(fv) .expect("to_ldr_scale_gamma: cannot convert f32 to target type. NaN?") } } Rgb([ sg(r, scale, gamma), sg(g, scale, gamma), sg(b, scale, gamma), ]) } } impl HdrDecoder { /// Reads Radiance HDR image header from stream `r` /// if the header is valid, creates HdrDecoder /// strict mode is enabled pub fn new(reader: R) -> ImageResult> { HdrDecoder::with_strictness(reader, true) } /// Reads Radiance HDR image header from stream `reader`, /// if the header is valid, creates `HdrDecoder`. /// /// strict enables strict mode /// /// Warning! Reading wrong file in non-strict mode /// could consume file size worth of memory in the process. pub fn with_strictness(mut reader: R, strict: bool) -> ImageResult> { let mut attributes = HdrMetadata::new(); { // scope to make borrowck happy let r = &mut reader; if strict { let mut signature = [0; SIGNATURE_LENGTH]; r.read_exact(&mut signature)?; if signature != SIGNATURE { return Err(DecoderError::RadianceHdrSignatureInvalid.into()); } // no else // skip signature line ending read_line_u8(r)?; } else { // Old Radiance HDR files (*.pic) don't use signature // Let them be parsed in non-strict mode } // read header data until empty line loop { match read_line_u8(r)? { None => { // EOF before end of header return Err(DecoderError::TruncatedHeader.into()); } Some(line) => { if line.is_empty() { // end of header break; } else if line[0] == b'#' { // line[0] will not panic, line.len() == 0 is false here // skip comments continue; } // no else // process attribute line let line = String::from_utf8_lossy(&line[..]); attributes.update_header_info(&line, strict)?; } // <= Some(line) } // match read_line_u8() } // loop } // scope to end borrow of reader // parse dimensions let (width, height) = match read_line_u8(&mut reader)? { None => { // EOF instead of image dimensions return Err(DecoderError::TruncatedDimensions.into()); } Some(dimensions) => { let dimensions = String::from_utf8_lossy(&dimensions[..]); parse_dimensions_line(&dimensions, strict)? } }; // color type is always rgb8 if crate::utils::check_dimension_overflow(width, height, ColorType::Rgb8.bytes_per_pixel()) { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Hdr.into(), UnsupportedErrorKind::GenericFeature(format!( "Image dimensions ({}x{}) are too large", width, height )), ), )); } Ok(HdrDecoder { r: reader, width, height, meta: HdrMetadata { width, height, ..attributes }, }) } // end with_strictness /// Returns file metadata. Refer to `HdrMetadata` for details. pub fn metadata(&self) -> HdrMetadata { self.meta.clone() } /// Consumes decoder and returns a vector of RGBE8 pixels pub fn read_image_native(mut self) -> ImageResult> { // Don't read anything if image is empty if self.width == 0 || self.height == 0 { return Ok(vec![]); } // expression self.width > 0 && self.height > 0 is true from now to the end of this method let pixel_count = self.width as usize * self.height as usize; let mut ret = vec![Default::default(); pixel_count]; for chunk in ret.chunks_mut(self.width as usize) { read_scanline(&mut self.r, chunk)?; } Ok(ret) } /// Consumes decoder and returns a vector of transformed pixels pub fn read_image_transform T>( mut self, f: F, output_slice: &mut [T], ) -> ImageResult<()> { assert_eq!( output_slice.len(), self.width as usize * self.height as usize ); // Don't read anything if image is empty if self.width == 0 || self.height == 0 { return Ok(()); } let chunks_iter = output_slice.chunks_mut(self.width as usize); let mut buf = vec![Default::default(); self.width as usize]; for chunk in chunks_iter { // read_scanline overwrites the entire buffer or returns an Err, // so not resetting the buffer here is ok. read_scanline(&mut self.r, &mut buf[..])?; for (dst, &pix) in chunk.iter_mut().zip(buf.iter()) { *dst = f(pix); } } Ok(()) } /// Consumes decoder and returns a vector of `Rgb` pixels. /// scale = 1, gamma = 2.2 pub fn read_image_ldr(self) -> ImageResult>> { let mut ret = vec![Rgb([0, 0, 0]); self.width as usize * self.height as usize]; self.read_image_transform(|pix| pix.to_ldr(), &mut ret[..])?; Ok(ret) } /// Consumes decoder and returns a vector of `Rgb` pixels. /// pub fn read_image_hdr(self) -> ImageResult>> { let mut ret = vec![Rgb([0.0, 0.0, 0.0]); self.width as usize * self.height as usize]; self.read_image_transform(|pix| pix.to_hdr(), &mut ret[..])?; Ok(ret) } } impl IntoIterator for HdrDecoder { type Item = ImageResult; type IntoIter = HdrImageDecoderIterator; fn into_iter(self) -> Self::IntoIter { HdrImageDecoderIterator { r: self.r, scanline_cnt: self.height as usize, buf: vec![Default::default(); self.width as usize], col: 0, scanline: 0, trouble: true, // make first call to `next()` read scanline error_encountered: false, } } } /// Scanline buffered pixel by pixel iterator pub struct HdrImageDecoderIterator { r: R, scanline_cnt: usize, buf: Vec, // scanline buffer col: usize, // current position in scanline scanline: usize, // current scanline trouble: bool, // optimization, true indicates that we need to check something error_encountered: bool, } impl HdrImageDecoderIterator { // Advances counter to the next pixel #[inline] fn advance(&mut self) { self.col += 1; if self.col == self.buf.len() { self.col = 0; self.scanline += 1; self.trouble = true; } } } impl Iterator for HdrImageDecoderIterator { type Item = ImageResult; fn next(&mut self) -> Option { if !self.trouble { let ret = self.buf[self.col]; self.advance(); Some(Ok(ret)) } else { // some condition is pending if self.buf.is_empty() || self.scanline == self.scanline_cnt { // No more pixels return None; } // no else if self.error_encountered { self.advance(); // Error was encountered. Keep producing errors. // ImageError can't implement Clone, so just dump some error return Some(Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::FailedAlready, )))); } // no else if self.col == 0 { // fill scanline buffer match read_scanline(&mut self.r, &mut self.buf[..]) { Ok(_) => { // no action required } Err(err) => { self.advance(); self.error_encountered = true; self.trouble = true; return Some(Err(err)); } } } // no else self.trouble = false; let ret = self.buf[0]; self.advance(); Some(Ok(ret)) } } fn size_hint(&self) -> (usize, Option) { let total_cnt = self.buf.len() * self.scanline_cnt; let cur_cnt = self.buf.len() * self.scanline + self.col; let remaining = total_cnt - cur_cnt; (remaining, Some(remaining)) } } impl ExactSizeIterator for HdrImageDecoderIterator {} // Precondition: buf.len() > 0 fn read_scanline(r: &mut R, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> { assert!(!buf.is_empty()); let width = buf.len(); // first 4 bytes in scanline allow to determine compression method let fb = read_rgbe(r)?; if fb.c[0] == 2 && fb.c[1] == 2 && fb.c[2] < 128 { // denormalized pixel value (2,2,<128,_) indicates new per component RLE method // decode_component guarantees that offset is within 0 .. width // therefore we can skip bounds checking here, but we will not decode_component(r, width, |offset, value| buf[offset].c[0] = value)?; decode_component(r, width, |offset, value| buf[offset].c[1] = value)?; decode_component(r, width, |offset, value| buf[offset].c[2] = value)?; decode_component(r, width, |offset, value| buf[offset].e = value)?; } else { // old RLE method (it was considered old around 1991, should it be here?) decode_old_rle(r, fb, buf)?; } Ok(()) } #[inline(always)] fn read_byte(r: &mut R) -> io::Result { let mut buf = [0u8]; r.read_exact(&mut buf[..])?; Ok(buf[0]) } // Guarantees that first parameter of set_component will be within pos .. pos+width #[inline] fn decode_component( r: &mut R, width: usize, mut set_component: S, ) -> ImageResult<()> { let mut buf = [0; 128]; let mut pos = 0; while pos < width { // increment position by a number of decompressed values pos += { let rl = read_byte(r)?; if rl <= 128 { // sanity check if pos + rl as usize > width { return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into()); } // read values r.read_exact(&mut buf[0..rl as usize])?; for (offset, &value) in buf[0..rl as usize].iter().enumerate() { set_component(pos + offset, value); } rl as usize } else { // run let rl = rl - 128; // sanity check if pos + rl as usize > width { return Err(DecoderError::WrongScanlineLength(pos + rl as usize, width).into()); } // fill with same value let value = read_byte(r)?; for offset in 0..rl as usize { set_component(pos + offset, value); } rl as usize } }; } if pos != width { return Err(DecoderError::WrongScanlineLength(pos, width).into()); } Ok(()) } // Decodes scanline, places it into buf // Precondition: buf.len() > 0 // fb - first 4 bytes of scanline fn decode_old_rle(r: &mut R, fb: Rgbe8Pixel, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> { assert!(!buf.is_empty()); let width = buf.len(); // convenience function. // returns run length if pixel is a run length marker #[inline] fn rl_marker(pix: Rgbe8Pixel) -> Option { if pix.c == [1, 1, 1] { Some(pix.e as usize) } else { None } } // first pixel in scanline should not be run length marker // it is error if it is if rl_marker(fb).is_some() { return Err(DecoderError::FirstPixelRlMarker.into()); } buf[0] = fb; // set first pixel of scanline let mut x_off = 1; // current offset from beginning of a scanline let mut rl_mult = 1; // current run length multiplier let mut prev_pixel = fb; while x_off < width { let pix = read_rgbe(r)?; // it's harder to forget to increase x_off if I write this this way. x_off += { if let Some(rl) = rl_marker(pix) { // rl_mult takes care of consecutive RL markers let rl = rl * rl_mult; rl_mult *= 256; if x_off + rl <= width { // do run for b in &mut buf[x_off..x_off + rl] { *b = prev_pixel; } } else { return Err(DecoderError::WrongScanlineLength(x_off + rl, width).into()); }; rl // value to increase x_off by } else { rl_mult = 1; // chain of consecutive RL markers is broken prev_pixel = pix; buf[x_off] = pix; 1 // value to increase x_off by } }; } if x_off != width { return Err(DecoderError::WrongScanlineLength(x_off, width).into()); } Ok(()) } fn read_rgbe(r: &mut R) -> io::Result { let mut buf = [0u8; 4]; r.read_exact(&mut buf[..])?; Ok(Rgbe8Pixel { c: [buf[0], buf[1], buf[2]], e: buf[3], }) } /// Metadata for Radiance HDR image #[derive(Debug, Clone)] pub struct HdrMetadata { /// Width of decoded image. It could be either scanline length, /// or scanline count, depending on image orientation. pub width: u32, /// Height of decoded image. It depends on orientation too. pub height: u32, /// Orientation matrix. For standard orientation it is ((1,0),(0,1)) - left to right, top to bottom. /// First pair tells how resulting pixel coordinates change along a scanline. /// Second pair tells how they change from one scanline to the next. pub orientation: ((i8, i8), (i8, i8)), /// Divide color values by exposure to get to get physical radiance in /// watts/steradian/m2 /// /// Image may not contain physical data, even if this field is set. pub exposure: Option, /// Divide color values by corresponding tuple member (r, g, b) to get to get physical radiance /// in watts/steradian/m2 /// /// Image may not contain physical data, even if this field is set. pub color_correction: Option<(f32, f32, f32)>, /// Pixel height divided by pixel width pub pixel_aspect_ratio: Option, /// All lines contained in image header are put here. Ordering of lines is preserved. /// Lines in the form "key=value" are represented as ("key", "value"). /// All other lines are ("", "line") pub custom_attributes: Vec<(String, String)>, } impl HdrMetadata { fn new() -> HdrMetadata { HdrMetadata { width: 0, height: 0, orientation: ((1, 0), (0, 1)), exposure: None, color_correction: None, pixel_aspect_ratio: None, custom_attributes: vec![], } } // Updates header info, in strict mode returns error for malformed lines (no '=' separator) // unknown attributes are skipped fn update_header_info(&mut self, line: &str, strict: bool) -> ImageResult<()> { // split line at first '=' // old Radiance HDR files (*.pic) feature tabs in key, so vvv trim let maybe_key_value = split_at_first(line, "=").map(|(key, value)| (key.trim(), value)); // save all header lines in custom_attributes match maybe_key_value { Some((key, val)) => self .custom_attributes .push((key.to_owned(), val.to_owned())), None => self.custom_attributes.push(("".into(), line.to_owned())), } // parse known attributes match maybe_key_value { Some(("FORMAT", val)) => { if val.trim() != "32-bit_rle_rgbe" { // XYZE isn't supported yet return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Hdr.into(), UnsupportedErrorKind::Format(ImageFormatHint::Name(limit_string_len( val, 20, ))), ), )); } } Some(("EXPOSURE", val)) => { match val.trim().parse::() { Ok(v) => { self.exposure = Some(self.exposure.unwrap_or(1.0) * v); // all encountered exposure values should be multiplied } Err(parse_error) => { if strict { return Err(DecoderError::UnparsableF32( LineType::Exposure, parse_error, ) .into()); } // no else, skip this line in non-strict mode } }; } Some(("PIXASPECT", val)) => { match val.trim().parse::() { Ok(v) => { self.pixel_aspect_ratio = Some(self.pixel_aspect_ratio.unwrap_or(1.0) * v); // all encountered exposure values should be multiplied } Err(parse_error) => { if strict { return Err(DecoderError::UnparsableF32( LineType::Pixaspect, parse_error, ) .into()); } // no else, skip this line in non-strict mode } }; } Some(("COLORCORR", val)) => { let mut rgbcorr = [1.0, 1.0, 1.0]; match parse_space_separated_f32(val, &mut rgbcorr, LineType::Colorcorr) { Ok(extra_numbers) => { if strict && extra_numbers { return Err(DecoderError::ExtraneousColorcorrNumbers.into()); } // no else, just ignore extra numbers let (rc, gc, bc) = self.color_correction.unwrap_or((1.0, 1.0, 1.0)); self.color_correction = Some((rc * rgbcorr[0], gc * rgbcorr[1], bc * rgbcorr[2])); } Err(err) => { if strict { return Err(err); } // no else, skip malformed line in non-strict mode } } } None => { // old Radiance HDR files (*.pic) contain commands in a header // just skip them } _ => { // skip unknown attribute } } // match attributes Ok(()) } } fn parse_space_separated_f32(line: &str, vals: &mut [f32], line_tp: LineType) -> ImageResult { let mut nums = line.split_whitespace(); for val in vals.iter_mut() { if let Some(num) = nums.next() { match num.parse::() { Ok(v) => *val = v, Err(err) => return Err(DecoderError::UnparsableF32(line_tp, err).into()), } } else { // not enough numbers in line return Err(DecoderError::LineTooShort(line_tp).into()); } } Ok(nums.next().is_some()) } // Parses dimension line "-Y height +X width" // returns (width, height) or error fn parse_dimensions_line(line: &str, strict: bool) -> ImageResult<(u32, u32)> { const DIMENSIONS_COUNT: usize = 4; let mut dim_parts = line.split_whitespace(); let c1_tag = dim_parts .next() .ok_or(DecoderError::DimensionsLineTooShort(0, DIMENSIONS_COUNT))?; let c1_str = dim_parts .next() .ok_or(DecoderError::DimensionsLineTooShort(1, DIMENSIONS_COUNT))?; let c2_tag = dim_parts .next() .ok_or(DecoderError::DimensionsLineTooShort(2, DIMENSIONS_COUNT))?; let c2_str = dim_parts .next() .ok_or(DecoderError::DimensionsLineTooShort(3, DIMENSIONS_COUNT))?; if strict && dim_parts.next().is_some() { // extra data in dimensions line return Err(DecoderError::DimensionsLineTooLong(DIMENSIONS_COUNT).into()); } // no else // dimensions line is in the form "-Y 10 +X 20" // There are 8 possible orientations: +Y +X, +X -Y and so on match (c1_tag, c2_tag) { ("-Y", "+X") => { // Common orientation (left-right, top-down) // c1_str is height, c2_str is width let height = c1_str .parse::() .map_err(|pe| DecoderError::UnparsableU32(LineType::DimensionsHeight, pe))?; let width = c2_str .parse::() .map_err(|pe| DecoderError::UnparsableU32(LineType::DimensionsWidth, pe))?; Ok((width, height)) } _ => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Hdr.into(), UnsupportedErrorKind::GenericFeature(format!( "Orientation {} {}", limit_string_len(c1_tag, 4), limit_string_len(c2_tag, 4) )), ), )), } // final expression. Returns value } // Returns string with no more than len+3 characters fn limit_string_len(s: &str, len: usize) -> String { let s_char_len = s.chars().count(); if s_char_len > len { s.chars().take(len).chain("...".chars()).collect() } else { s.into() } } // Splits string into (before separator, after separator) tuple // or None if separator isn't found fn split_at_first<'a>(s: &'a str, separator: &str) -> Option<(&'a str, &'a str)> { match s.find(separator) { None | Some(0) => None, Some(p) if p >= s.len() - separator.len() => None, Some(p) => Some((&s[..p], &s[(p + separator.len())..])), } } #[test] fn split_at_first_test() { assert_eq!(split_at_first(&Cow::Owned("".into()), "="), None); assert_eq!(split_at_first(&Cow::Owned("=".into()), "="), None); assert_eq!(split_at_first(&Cow::Owned("= ".into()), "="), None); assert_eq!( split_at_first(&Cow::Owned(" = ".into()), "="), Some((" ", " ")) ); assert_eq!( split_at_first(&Cow::Owned("EXPOSURE= ".into()), "="), Some(("EXPOSURE", " ")) ); assert_eq!( split_at_first(&Cow::Owned("EXPOSURE= =".into()), "="), Some(("EXPOSURE", " =")) ); assert_eq!( split_at_first(&Cow::Owned("EXPOSURE== =".into()), "=="), Some(("EXPOSURE", " =")) ); assert_eq!(split_at_first(&Cow::Owned("EXPOSURE".into()), ""), None); } // Reads input until b"\n" or EOF // Returns vector of read bytes NOT including end of line characters // or return None to indicate end of file fn read_line_u8(r: &mut R) -> ::std::io::Result>> { let mut ret = Vec::with_capacity(16); match r.read_until(b'\n', &mut ret) { Ok(0) => Ok(None), Ok(_) => { if let Some(&b'\n') = ret[..].last() { let _ = ret.pop(); } Ok(Some(ret)) } Err(err) => Err(err), } } #[test] fn read_line_u8_test() { let buf: Vec<_> = (&b"One\nTwo\nThree\nFour\n\n\n"[..]).into(); let input = &mut ::std::io::Cursor::new(buf); assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"One"[..]); assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"Two"[..]); assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"Three"[..]); assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b"Four"[..]); assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b""[..]); assert_eq!(&read_line_u8(input).unwrap().unwrap()[..], &b""[..]); assert_eq!(read_line_u8(input).unwrap(), None); } /// Helper function for reading raw 3-channel f32 images pub fn read_raw_file>(path: P) -> ::std::io::Result>> { use byteorder::{LittleEndian as LE, ReadBytesExt}; use std::fs::File; use std::io::BufReader; let mut r = BufReader::new(File::open(path)?); let w = r.read_u32::()? as usize; let h = r.read_u32::()? as usize; let c = r.read_u32::()? as usize; assert_eq!(c, 3); let cnt = w * h; let mut ret = Vec::with_capacity(cnt); for _ in 0..cnt { let cr = r.read_f32::()?; let cg = r.read_f32::()?; let cb = r.read_f32::()?; ret.push(Rgb([cr, cg, cb])); } Ok(ret) } #[cfg(test)] mod test { use super::*; use std::io::Cursor; #[test] fn dimension_overflow() { let data = b"#?RADIANCE\nFORMAT=32-bit_rle_rgbe\n\n -Y 4294967295 +X 4294967295"; assert!(HdrAdapter::new(Cursor::new(data)).is_err()); assert!(HdrAdapter::new_nonstrict(Cursor::new(data)).is_err()); } } image-0.24.7/src/codecs/hdr/encoder.rs000064400000000000000000000360171046102023000156000ustar 00000000000000use crate::codecs::hdr::{rgbe8, Rgbe8Pixel, SIGNATURE}; use crate::color::Rgb; use crate::error::ImageResult; use std::cmp::Ordering; use std::io::{Result, Write}; /// Radiance HDR encoder pub struct HdrEncoder { w: W, } impl HdrEncoder { /// Creates encoder pub fn new(w: W) -> HdrEncoder { HdrEncoder { w } } /// Encodes the image ```data``` /// that has dimensions ```width``` and ```height``` pub fn encode(mut self, data: &[Rgb], width: usize, height: usize) -> ImageResult<()> { assert!(data.len() >= width * height); let w = &mut self.w; w.write_all(SIGNATURE)?; w.write_all(b"\n")?; w.write_all(b"# Rust HDR encoder\n")?; w.write_all(b"FORMAT=32-bit_rle_rgbe\n\n")?; w.write_all(format!("-Y {} +X {}\n", height, width).as_bytes())?; if !(8..=32_768).contains(&width) { for &pix in data { write_rgbe8(w, to_rgbe8(pix))?; } } else { // new RLE marker contains scanline width let marker = rgbe8(2, 2, (width / 256) as u8, (width % 256) as u8); // buffers for encoded pixels let mut bufr = vec![0; width]; let mut bufg = vec![0; width]; let mut bufb = vec![0; width]; let mut bufe = vec![0; width]; let mut rle_buf = vec![0; width]; for scanline in data.chunks(width) { for ((((r, g), b), e), &pix) in bufr .iter_mut() .zip(bufg.iter_mut()) .zip(bufb.iter_mut()) .zip(bufe.iter_mut()) .zip(scanline.iter()) { let cp = to_rgbe8(pix); *r = cp.c[0]; *g = cp.c[1]; *b = cp.c[2]; *e = cp.e; } write_rgbe8(w, marker)?; // New RLE encoding marker rle_buf.clear(); rle_compress(&bufr[..], &mut rle_buf); w.write_all(&rle_buf[..])?; rle_buf.clear(); rle_compress(&bufg[..], &mut rle_buf); w.write_all(&rle_buf[..])?; rle_buf.clear(); rle_compress(&bufb[..], &mut rle_buf); w.write_all(&rle_buf[..])?; rle_buf.clear(); rle_compress(&bufe[..], &mut rle_buf); w.write_all(&rle_buf[..])?; } } Ok(()) } } #[derive(Debug, PartialEq, Eq)] enum RunOrNot { Run(u8, usize), Norun(usize, usize), } use self::RunOrNot::{Norun, Run}; const RUN_MAX_LEN: usize = 127; const NORUN_MAX_LEN: usize = 128; struct RunIterator<'a> { data: &'a [u8], curidx: usize, } impl<'a> RunIterator<'a> { fn new(data: &'a [u8]) -> RunIterator<'a> { RunIterator { data, curidx: 0 } } } impl<'a> Iterator for RunIterator<'a> { type Item = RunOrNot; fn next(&mut self) -> Option { if self.curidx == self.data.len() { None } else { let cv = self.data[self.curidx]; let crun = self.data[self.curidx..] .iter() .take_while(|&&v| v == cv) .take(RUN_MAX_LEN) .count(); let ret = if crun > 2 { Run(cv, crun) } else { Norun(self.curidx, crun) }; self.curidx += crun; Some(ret) } } } struct NorunCombineIterator<'a> { runiter: RunIterator<'a>, prev: Option, } impl<'a> NorunCombineIterator<'a> { fn new(data: &'a [u8]) -> NorunCombineIterator<'a> { NorunCombineIterator { runiter: RunIterator::new(data), prev: None, } } } // Combines sequential noruns produced by RunIterator impl<'a> Iterator for NorunCombineIterator<'a> { type Item = RunOrNot; fn next(&mut self) -> Option { loop { match self.prev.take() { Some(Run(c, len)) => { // Just return stored run return Some(Run(c, len)); } Some(Norun(idx, len)) => { // Let's see if we need to continue norun match self.runiter.next() { Some(Norun(_, len1)) => { // norun continues let clen = len + len1; // combined length match clen.cmp(&NORUN_MAX_LEN) { Ordering::Equal => return Some(Norun(idx, clen)), Ordering::Greater => { // combined norun exceeds maximum length. store extra part of norun self.prev = Some(Norun(idx + NORUN_MAX_LEN, clen - NORUN_MAX_LEN)); // then return maximal norun return Some(Norun(idx, NORUN_MAX_LEN)); } Ordering::Less => { // len + len1 < NORUN_MAX_LEN self.prev = Some(Norun(idx, len + len1)); // combine and continue loop } } } Some(Run(c, len1)) => { // Run encountered. Store it self.prev = Some(Run(c, len1)); return Some(Norun(idx, len)); // and return combined norun } None => { // End of sequence return Some(Norun(idx, len)); // return combined norun } } } // End match self.prev.take() == Some(NoRun()) None => { // No norun to combine match self.runiter.next() { Some(Norun(idx, len)) => { self.prev = Some(Norun(idx, len)); // store for combine and continue the loop } Some(Run(c, len)) => { // Some run. Just return it return Some(Run(c, len)); } None => { // That's all, folks return None; } } } // End match self.prev.take() == None } // End match } // End loop } } // Appends RLE compressed ```data``` to ```rle``` fn rle_compress(data: &[u8], rle: &mut Vec) { rle.clear(); if data.is_empty() { rle.push(0); // Technically correct. It means read next 0 bytes. return; } // Task: split data into chunks of repeating (max 127) and non-repeating bytes (max 128) // Prepend non-repeating chunk with its length // Replace repeating byte with (run length + 128) and the byte for rnr in NorunCombineIterator::new(data) { match rnr { Run(c, len) => { assert!(len <= 127); rle.push(128u8 + len as u8); rle.push(c); } Norun(idx, len) => { assert!(len <= 128); rle.push(len as u8); rle.extend_from_slice(&data[idx..idx + len]); } } } } fn write_rgbe8(w: &mut W, v: Rgbe8Pixel) -> Result<()> { w.write_all(&[v.c[0], v.c[1], v.c[2], v.e]) } /// Converts ```Rgb``` into ```Rgbe8Pixel``` pub fn to_rgbe8(pix: Rgb) -> Rgbe8Pixel { let pix = pix.0; let mx = f32::max(pix[0], f32::max(pix[1], pix[2])); if mx <= 0.0 { Rgbe8Pixel { c: [0, 0, 0], e: 0 } } else { // let (frac, exp) = mx.frexp(); // unstable yet let exp = mx.log2().floor() as i32 + 1; let mul = f32::powi(2.0, exp); let mut conv = [0u8; 3]; for (cv, &sv) in conv.iter_mut().zip(pix.iter()) { *cv = f32::trunc(sv / mul * 256.0) as u8; } Rgbe8Pixel { c: conv, e: (exp + 128) as u8, } } } #[test] fn to_rgbe8_test() { use crate::codecs::hdr::rgbe8; let test_cases = vec![rgbe8(0, 0, 0, 0), rgbe8(1, 1, 128, 128)]; for &pix in &test_cases { assert_eq!(pix, to_rgbe8(pix.to_hdr())); } for mc in 128..255 { // TODO: use inclusive range when stable let pix = rgbe8(mc, mc, mc, 100); assert_eq!(pix, to_rgbe8(pix.to_hdr())); let pix = rgbe8(mc, 0, mc, 130); assert_eq!(pix, to_rgbe8(pix.to_hdr())); let pix = rgbe8(0, 0, mc, 140); assert_eq!(pix, to_rgbe8(pix.to_hdr())); let pix = rgbe8(1, 0, mc, 150); assert_eq!(pix, to_rgbe8(pix.to_hdr())); let pix = rgbe8(1, mc, 10, 128); assert_eq!(pix, to_rgbe8(pix.to_hdr())); for c in 0..255 { // Radiance HDR seems to be pre IEEE 754. // exponent can be -128 (represented as 0u8), so some colors cannot be represented in normalized f32 // Let's exclude exponent value of -128 (0u8) from testing let pix = rgbe8(1, mc, c, if c == 0 { 1 } else { c }); assert_eq!(pix, to_rgbe8(pix.to_hdr())); } } fn relative_dist(a: Rgb, b: Rgb) -> f32 { // maximal difference divided by maximal value let max_diff = a.0.iter() .zip(b.0.iter()) .fold(0.0, |diff, (&a, &b)| f32::max(diff, (a - b).abs())); let max_val = a.0.iter() .chain(b.0.iter()) .fold(0.0, |maxv, &a| f32::max(maxv, a)); if max_val == 0.0 { 0.0 } else { max_diff / max_val } } let test_values = vec![ 0.000_001, 0.000_02, 0.000_3, 0.004, 0.05, 0.6, 7.0, 80.0, 900.0, 1_000.0, 20_000.0, 300_000.0, ]; for &r in &test_values { for &g in &test_values { for &b in &test_values { let c1 = Rgb([r, g, b]); let c2 = to_rgbe8(c1).to_hdr(); let rel_dist = relative_dist(c1, c2); // Maximal value is normalized to the range 128..256, thus we have 1/128 precision assert!( rel_dist <= 1.0 / 128.0, "Relative distance ({}) exceeds 1/128 for {:?} and {:?}", rel_dist, c1, c2 ); } } } } #[test] fn runiterator_test() { let data = []; let mut run_iter = RunIterator::new(&data[..]); assert_eq!(run_iter.next(), None); let data = [5]; let mut run_iter = RunIterator::new(&data[..]); assert_eq!(run_iter.next(), Some(Norun(0, 1))); assert_eq!(run_iter.next(), None); let data = [1, 1]; let mut run_iter = RunIterator::new(&data[..]); assert_eq!(run_iter.next(), Some(Norun(0, 2))); assert_eq!(run_iter.next(), None); let data = [0, 0, 0]; let mut run_iter = RunIterator::new(&data[..]); assert_eq!(run_iter.next(), Some(Run(0u8, 3))); assert_eq!(run_iter.next(), None); let data = [0, 0, 1, 1]; let mut run_iter = RunIterator::new(&data[..]); assert_eq!(run_iter.next(), Some(Norun(0, 2))); assert_eq!(run_iter.next(), Some(Norun(2, 2))); assert_eq!(run_iter.next(), None); let data = [0, 0, 0, 1, 1]; let mut run_iter = RunIterator::new(&data[..]); assert_eq!(run_iter.next(), Some(Run(0u8, 3))); assert_eq!(run_iter.next(), Some(Norun(3, 2))); assert_eq!(run_iter.next(), None); let data = [1, 2, 2, 2]; let mut run_iter = RunIterator::new(&data[..]); assert_eq!(run_iter.next(), Some(Norun(0, 1))); assert_eq!(run_iter.next(), Some(Run(2u8, 3))); assert_eq!(run_iter.next(), None); let data = [1, 1, 2, 2, 2]; let mut run_iter = RunIterator::new(&data[..]); assert_eq!(run_iter.next(), Some(Norun(0, 2))); assert_eq!(run_iter.next(), Some(Run(2u8, 3))); assert_eq!(run_iter.next(), None); let data = [2; 128]; let mut run_iter = RunIterator::new(&data[..]); assert_eq!(run_iter.next(), Some(Run(2u8, 127))); assert_eq!(run_iter.next(), Some(Norun(127, 1))); assert_eq!(run_iter.next(), None); let data = [2; 129]; let mut run_iter = RunIterator::new(&data[..]); assert_eq!(run_iter.next(), Some(Run(2u8, 127))); assert_eq!(run_iter.next(), Some(Norun(127, 2))); assert_eq!(run_iter.next(), None); let data = [2; 130]; let mut run_iter = RunIterator::new(&data[..]); assert_eq!(run_iter.next(), Some(Run(2u8, 127))); assert_eq!(run_iter.next(), Some(Run(2u8, 3))); assert_eq!(run_iter.next(), None); } #[test] fn noruncombine_test() { fn a(mut v: Vec, mut other: Vec) -> Vec { v.append(&mut other); v } let v = vec![]; let mut rsi = NorunCombineIterator::new(&v[..]); assert_eq!(rsi.next(), None); let v = vec![1]; let mut rsi = NorunCombineIterator::new(&v[..]); assert_eq!(rsi.next(), Some(Norun(0, 1))); assert_eq!(rsi.next(), None); let v = vec![2, 2]; let mut rsi = NorunCombineIterator::new(&v[..]); assert_eq!(rsi.next(), Some(Norun(0, 2))); assert_eq!(rsi.next(), None); let v = vec![3, 3, 3]; let mut rsi = NorunCombineIterator::new(&v[..]); assert_eq!(rsi.next(), Some(Run(3, 3))); assert_eq!(rsi.next(), None); let v = vec![4, 4, 3, 3, 3]; let mut rsi = NorunCombineIterator::new(&v[..]); assert_eq!(rsi.next(), Some(Norun(0, 2))); assert_eq!(rsi.next(), Some(Run(3, 3))); assert_eq!(rsi.next(), None); let v = vec![40; 400]; let mut rsi = NorunCombineIterator::new(&v[..]); assert_eq!(rsi.next(), Some(Run(40, 127))); assert_eq!(rsi.next(), Some(Run(40, 127))); assert_eq!(rsi.next(), Some(Run(40, 127))); assert_eq!(rsi.next(), Some(Run(40, 19))); assert_eq!(rsi.next(), None); let v = a(a(vec![5; 3], vec![6; 129]), vec![7, 3, 7, 10, 255]); let mut rsi = NorunCombineIterator::new(&v[..]); assert_eq!(rsi.next(), Some(Run(5, 3))); assert_eq!(rsi.next(), Some(Run(6, 127))); assert_eq!(rsi.next(), Some(Norun(130, 7))); assert_eq!(rsi.next(), None); let v = a(a(vec![5; 2], vec![6; 129]), vec![7, 3, 7, 7, 255]); let mut rsi = NorunCombineIterator::new(&v[..]); assert_eq!(rsi.next(), Some(Norun(0, 2))); assert_eq!(rsi.next(), Some(Run(6, 127))); assert_eq!(rsi.next(), Some(Norun(129, 7))); assert_eq!(rsi.next(), None); let v: Vec<_> = ::std::iter::repeat(()) .flat_map(|_| (0..2)) .take(257) .collect(); let mut rsi = NorunCombineIterator::new(&v[..]); assert_eq!(rsi.next(), Some(Norun(0, 128))); assert_eq!(rsi.next(), Some(Norun(128, 128))); assert_eq!(rsi.next(), Some(Norun(256, 1))); assert_eq!(rsi.next(), None); } image-0.24.7/src/codecs/hdr/mod.rs000064400000000000000000000004671046102023000147400ustar 00000000000000//! Decoding of Radiance HDR Images //! //! A decoder for Radiance HDR images //! //! # Related Links //! //! * //! * //! mod decoder; mod encoder; pub use self::decoder::*; pub use self::encoder::*; image-0.24.7/src/codecs/ico/decoder.rs000064400000000000000000000461471046102023000155700ustar 00000000000000use byteorder::{LittleEndian, ReadBytesExt}; use std::convert::TryFrom; use std::io::{self, Cursor, Read, Seek, SeekFrom}; use std::marker::PhantomData; use std::{error, fmt, mem}; use crate::color::ColorType; use crate::error::{ DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{self, ImageDecoder, ImageFormat}; use self::InnerDecoder::*; use crate::codecs::bmp::BmpDecoder; use crate::codecs::png::{PngDecoder, PNG_SIGNATURE}; /// Errors that can occur during decoding and parsing an ICO image or one of its enclosed images. #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] enum DecoderError { /// The ICO directory is empty NoEntries, /// The number of color planes (0 or 1), or the horizontal coordinate of the hotspot for CUR files too big. IcoEntryTooManyPlanesOrHotspot, /// The bit depth (may be 0 meaning unspecified), or the vertical coordinate of the hotspot for CUR files too big. IcoEntryTooManyBitsPerPixelOrHotspot, /// The entry is in PNG format and specified a length that is shorter than PNG header. PngShorterThanHeader, /// The enclosed PNG is not in RGBA, which is invalid: https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473/. PngNotRgba, /// The entry is in BMP format and specified a data size that is not correct for the image and optional mask data. InvalidDataSize, /// The dimensions specified by the entry does not match the dimensions in the header of the enclosed image. ImageEntryDimensionMismatch { /// The mismatched subimage's type format: IcoEntryImageFormat, /// The dimensions specified by the entry entry: (u16, u16), /// The dimensions of the image itself image: (u32, u32), }, } impl fmt::Display for DecoderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DecoderError::NoEntries => f.write_str("ICO directory contains no image"), DecoderError::IcoEntryTooManyPlanesOrHotspot => { f.write_str("ICO image entry has too many color planes or too large hotspot value") } DecoderError::IcoEntryTooManyBitsPerPixelOrHotspot => f.write_str( "ICO image entry has too many bits per pixel or too large hotspot value", ), DecoderError::PngShorterThanHeader => { f.write_str("Entry specified a length that is shorter than PNG header!") } DecoderError::PngNotRgba => f.write_str("The PNG is not in RGBA format!"), DecoderError::InvalidDataSize => { f.write_str("ICO image data size did not match expected size") } DecoderError::ImageEntryDimensionMismatch { format, entry, image, } => f.write_fmt(format_args!( "Entry{:?} and {}{:?} dimensions do not match!", entry, format, image )), } } } impl From for ImageError { fn from(e: DecoderError) -> ImageError { ImageError::Decoding(DecodingError::new(ImageFormat::Ico.into(), e)) } } impl error::Error for DecoderError {} /// The image formats an ICO may contain #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] enum IcoEntryImageFormat { /// PNG in ARGB Png, /// BMP with optional alpha mask Bmp, } impl fmt::Display for IcoEntryImageFormat { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { IcoEntryImageFormat::Png => "PNG", IcoEntryImageFormat::Bmp => "BMP", }) } } impl From for ImageFormat { fn from(val: IcoEntryImageFormat) -> Self { match val { IcoEntryImageFormat::Png => ImageFormat::Png, IcoEntryImageFormat::Bmp => ImageFormat::Bmp, } } } /// An ico decoder pub struct IcoDecoder { selected_entry: DirEntry, inner_decoder: InnerDecoder, } enum InnerDecoder { Bmp(BmpDecoder), Png(Box>), } #[derive(Clone, Copy, Default)] struct DirEntry { width: u8, height: u8, // We ignore some header fields as they will be replicated in the PNG, BMP and they are not // necessary for determining the best_entry. #[allow(unused)] color_count: u8, // Wikipedia has this to say: // Although Microsoft's technical documentation states that this value must be zero, the icon // encoder built into .NET (System.Drawing.Icon.Save) sets this value to 255. It appears that // the operating system ignores this value altogether. #[allow(unused)] reserved: u8, // We ignore some header fields as they will be replicated in the PNG, BMP and they are not // necessary for determining the best_entry. #[allow(unused)] num_color_planes: u16, bits_per_pixel: u16, image_length: u32, image_offset: u32, } impl IcoDecoder { /// Create a new decoder that decodes from the stream ```r``` pub fn new(mut r: R) -> ImageResult> { let entries = read_entries(&mut r)?; let entry = best_entry(entries)?; let decoder = entry.decoder(r)?; Ok(IcoDecoder { selected_entry: entry, inner_decoder: decoder, }) } } fn read_entries(r: &mut R) -> ImageResult> { let _reserved = r.read_u16::()?; let _type = r.read_u16::()?; let count = r.read_u16::()?; (0..count).map(|_| read_entry(r)).collect() } fn read_entry(r: &mut R) -> ImageResult { Ok(DirEntry { width: r.read_u8()?, height: r.read_u8()?, color_count: r.read_u8()?, reserved: r.read_u8()?, num_color_planes: { // This may be either the number of color planes (0 or 1), or the horizontal coordinate // of the hotspot for CUR files. let num = r.read_u16::()?; if num > 256 { return Err(DecoderError::IcoEntryTooManyPlanesOrHotspot.into()); } num }, bits_per_pixel: { // This may be either the bit depth (may be 0 meaning unspecified), // or the vertical coordinate of the hotspot for CUR files. let num = r.read_u16::()?; if num > 256 { return Err(DecoderError::IcoEntryTooManyBitsPerPixelOrHotspot.into()); } num }, image_length: r.read_u32::()?, image_offset: r.read_u32::()?, }) } /// Find the entry with the highest (color depth, size). fn best_entry(mut entries: Vec) -> ImageResult { let mut best = entries.pop().ok_or(DecoderError::NoEntries)?; let mut best_score = ( best.bits_per_pixel, u32::from(best.real_width()) * u32::from(best.real_height()), ); for entry in entries { let score = ( entry.bits_per_pixel, u32::from(entry.real_width()) * u32::from(entry.real_height()), ); if score > best_score { best = entry; best_score = score; } } Ok(best) } impl DirEntry { fn real_width(&self) -> u16 { match self.width { 0 => 256, w => u16::from(w), } } fn real_height(&self) -> u16 { match self.height { 0 => 256, h => u16::from(h), } } fn matches_dimensions(&self, width: u32, height: u32) -> bool { u32::from(self.real_width()) == width.min(256) && u32::from(self.real_height()) == height.min(256) } fn seek_to_start(&self, r: &mut R) -> ImageResult<()> { r.seek(SeekFrom::Start(u64::from(self.image_offset)))?; Ok(()) } fn is_png(&self, r: &mut R) -> ImageResult { self.seek_to_start(r)?; // Read the first 8 bytes to sniff the image. let mut signature = [0u8; 8]; r.read_exact(&mut signature)?; Ok(signature == PNG_SIGNATURE) } fn decoder(&self, mut r: R) -> ImageResult> { let is_png = self.is_png(&mut r)?; self.seek_to_start(&mut r)?; if is_png { Ok(Png(Box::new(PngDecoder::new(r)?))) } else { Ok(Bmp(BmpDecoder::new_with_ico_format(r)?)) } } } /// Wrapper struct around a `Cursor>` pub struct IcoReader(Cursor>, PhantomData); impl Read for IcoReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { if self.0.position() == 0 && buf.is_empty() { mem::swap(buf, self.0.get_mut()); Ok(buf.len()) } else { self.0.read_to_end(buf) } } } impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for IcoDecoder { type Reader = IcoReader; fn dimensions(&self) -> (u32, u32) { match self.inner_decoder { Bmp(ref decoder) => decoder.dimensions(), Png(ref decoder) => decoder.dimensions(), } } fn color_type(&self) -> ColorType { match self.inner_decoder { Bmp(ref decoder) => decoder.color_type(), Png(ref decoder) => decoder.color_type(), } } fn into_reader(self) -> ImageResult { Ok(IcoReader( Cursor::new(image::decoder_to_vec(self)?), PhantomData, )) } fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); match self.inner_decoder { Png(decoder) => { if self.selected_entry.image_length < PNG_SIGNATURE.len() as u32 { return Err(DecoderError::PngShorterThanHeader.into()); } // Check if the image dimensions match the ones in the image data. let (width, height) = decoder.dimensions(); if !self.selected_entry.matches_dimensions(width, height) { return Err(DecoderError::ImageEntryDimensionMismatch { format: IcoEntryImageFormat::Png, entry: ( self.selected_entry.real_width(), self.selected_entry.real_height(), ), image: (width, height), } .into()); } // Embedded PNG images can only be of the 32BPP RGBA format. // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473/ if decoder.color_type() != ColorType::Rgba8 { return Err(DecoderError::PngNotRgba.into()); } decoder.read_image(buf) } Bmp(mut decoder) => { let (width, height) = decoder.dimensions(); if !self.selected_entry.matches_dimensions(width, height) { return Err(DecoderError::ImageEntryDimensionMismatch { format: IcoEntryImageFormat::Bmp, entry: ( self.selected_entry.real_width(), self.selected_entry.real_height(), ), image: (width, height), } .into()); } // The ICO decoder needs an alpha channel to apply the AND mask. if decoder.color_type() != ColorType::Rgba8 { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Bmp.into(), UnsupportedErrorKind::Color(decoder.color_type().into()), ), )); } decoder.read_image_data(buf)?; let r = decoder.reader(); let image_end = r.stream_position()?; let data_end = u64::from(self.selected_entry.image_offset) + u64::from(self.selected_entry.image_length); let mask_row_bytes = ((width + 31) / 32) * 4; let mask_length = u64::from(mask_row_bytes) * u64::from(height); // data_end should be image_end + the mask length (mask_row_bytes * height). // According to // https://devblogs.microsoft.com/oldnewthing/20101021-00/?p=12483 // the mask is required, but according to Wikipedia // https://en.wikipedia.org/wiki/ICO_(file_format) // the mask is not required. Unfortunately, Wikipedia does not have a citation // for that claim, so we can't be sure which is correct. if data_end >= image_end + mask_length { // If there's an AND mask following the image, read and apply it. for y in 0..height { let mut x = 0; for _ in 0..mask_row_bytes { // Apply the bits of each byte until we reach the end of the row. let mask_byte = r.read_u8()?; for bit in (0..8).rev() { if x >= width { break; } if mask_byte & (1 << bit) != 0 { // Set alpha channel to transparent. buf[((height - y - 1) * width + x) as usize * 4 + 3] = 0; } x += 1; } } } Ok(()) } else if data_end == image_end { // accept images with no mask data Ok(()) } else { Err(DecoderError::InvalidDataSize.into()) } } } } } #[cfg(test)] mod test { use super::*; // Test if BMP images without alpha channel inside ICOs don't panic. // Because the test data is invalid decoding should produce an error. #[test] fn bmp_16_with_missing_alpha_channel() { let data = vec![ 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0e, 0x04, 0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xf8, 0xff, 0xff, 0xff, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x83, 0x70, 0x61, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xeb, 0x00, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x62, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0c, 0x00, 0x00, 0x00, 0xc3, 0x3f, 0x94, 0x61, 0xaa, 0x17, 0x4d, 0x8d, 0x79, 0x1d, 0x8b, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x2e, 0x28, 0x40, 0xe5, 0x9f, 0x4b, 0x4d, 0xe9, 0x87, 0xd3, 0xda, 0xd6, 0x89, 0x81, 0xc5, 0xa4, 0xa1, 0x60, 0x98, 0x31, 0xc7, 0x1d, 0xb6, 0x8f, 0x20, 0xc8, 0x3e, 0xee, 0xd8, 0xe4, 0x8f, 0xee, 0x7b, 0x48, 0x9b, 0x88, 0x25, 0x13, 0xda, 0xa4, 0x13, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x40, 0x16, 0x01, 0xff, 0xff, 0xff, 0xff, 0xe9, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0x66, 0x64, 0x41, 0x54, 0xa3, 0xa3, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0x66, 0x64, 0x41, 0x54, 0xa3, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x83, 0x70, 0x61, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x62, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x94, 0xc8, 0x00, 0x02, 0x0c, 0x00, 0xff, 0xff, 0xc6, 0x84, 0x00, 0x2a, 0x75, 0x03, 0xa3, 0x05, 0xfb, 0xe1, 0x6e, 0xe8, 0x27, 0xd6, 0xd3, 0x96, 0xc1, 0xe4, 0x30, 0x0c, 0x05, 0xb9, 0xa3, 0x8b, 0x29, 0xda, 0xa4, 0xf1, 0x4d, 0xf3, 0xb2, 0x98, 0x2b, 0xe6, 0x93, 0x07, 0xf9, 0xca, 0x2b, 0xc2, 0x39, 0x20, 0xba, 0x7c, 0xa0, 0xb1, 0x43, 0xe6, 0xf9, 0xdc, 0xd1, 0xc2, 0x52, 0xdc, 0x41, 0xc1, 0x2f, 0x29, 0xf7, 0x46, 0x32, 0xda, 0x1b, 0x72, 0x8c, 0xe6, 0x2b, 0x01, 0xe5, 0x49, 0x21, 0x89, 0x89, 0xe4, 0x3d, 0xa1, 0xdb, 0x3b, 0x4a, 0x0b, 0x52, 0x86, 0x52, 0x33, 0x9d, 0xb2, 0xcf, 0x4a, 0x86, 0x53, 0xd7, 0xa9, 0x4b, 0xaf, 0x62, 0x06, 0x49, 0x53, 0x00, 0xc3, 0x3f, 0x94, 0x61, 0xaa, 0x17, 0x4d, 0x8d, 0x79, 0x1d, 0x8b, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x2e, 0x28, 0x40, 0xe5, 0x9f, 0x4b, 0x4d, 0xe9, 0x87, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe7, 0xc5, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x0b, 0x00, 0x50, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x76, 0x76, 0x01, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x23, 0x3f, 0x52, 0x41, 0x44, 0x49, 0x41, 0x4e, 0x43, 0x45, 0x61, 0x50, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x4d, 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x05, 0x50, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x37, 0x61, ]; let decoder = IcoDecoder::new(Cursor::new(&data)).unwrap(); let mut buf = vec![0; usize::try_from(decoder.total_bytes()).unwrap()]; assert!(decoder.read_image(&mut buf).is_err()); } } image-0.24.7/src/codecs/ico/encoder.rs000064400000000000000000000144761046102023000156020ustar 00000000000000use byteorder::{LittleEndian, WriteBytesExt}; use std::borrow::Cow; use std::io::{self, Write}; use crate::color::ColorType; use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; use crate::image::ImageEncoder; use crate::codecs::png::PngEncoder; // Enum value indicating an ICO image (as opposed to a CUR image): const ICO_IMAGE_TYPE: u16 = 1; // The length of an ICO file ICONDIR structure, in bytes: const ICO_ICONDIR_SIZE: u32 = 6; // The length of an ICO file DIRENTRY structure, in bytes: const ICO_DIRENTRY_SIZE: u32 = 16; /// ICO encoder pub struct IcoEncoder { w: W, } /// An ICO image entry pub struct IcoFrame<'a> { // Pre-encoded PNG or BMP encoded_image: Cow<'a, [u8]>, // Stored as `0 => 256, n => n` width: u8, // Stored as `0 => 256, n => n` height: u8, color_type: ColorType, } impl<'a> IcoFrame<'a> { /// Construct a new `IcoFrame` using a pre-encoded PNG or BMP /// /// The `width` and `height` must be between 1 and 256 (inclusive). pub fn with_encoded( encoded_image: impl Into>, width: u32, height: u32, color_type: ColorType, ) -> ImageResult { let encoded_image = encoded_image.into(); if !(1..=256).contains(&width) { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::Generic(format!( "the image width must be `1..=256`, instead width {} was provided", width, )), ))); } if !(1..=256).contains(&height) { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::Generic(format!( "the image height must be `1..=256`, instead height {} was provided", height, )), ))); } Ok(Self { encoded_image, width: width as u8, height: height as u8, color_type, }) } /// Construct a new `IcoFrame` by encoding `buf` as a PNG /// /// The `width` and `height` must be between 1 and 256 (inclusive) pub fn as_png(buf: &[u8], width: u32, height: u32, color_type: ColorType) -> ImageResult { let mut image_data: Vec = Vec::new(); PngEncoder::new(&mut image_data).write_image(buf, width, height, color_type)?; let frame = Self::with_encoded(image_data, width, height, color_type)?; Ok(frame) } } impl IcoEncoder { /// Create a new encoder that writes its output to ```w```. pub fn new(w: W) -> IcoEncoder { IcoEncoder { w } } /// Encodes the image ```image``` that has dimensions ```width``` and /// ```height``` and ```ColorType``` ```c```. The dimensions of the image /// must be between 1 and 256 (inclusive) or an error will be returned. /// /// Expects data to be big endian. #[deprecated = "Use `IcoEncoder::write_image` instead. Beware that `write_image` has a different endianness convention"] pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> { let mut image_data: Vec = Vec::new(); #[allow(deprecated)] PngEncoder::new(&mut image_data).encode(data, width, height, color)?; let image = IcoFrame::with_encoded(&image_data, width, height, color)?; self.encode_images(&[image]) } /// Takes some [`IcoFrame`]s and encodes them into an ICO. /// /// `images` is a list of images, usually ordered by dimension, which /// must be between 1 and 65535 (inclusive) in length. pub fn encode_images(mut self, images: &[IcoFrame<'_>]) -> ImageResult<()> { if !(1..=usize::from(u16::MAX)).contains(&images.len()) { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::Generic(format!( "the number of images must be `1..=u16::MAX`, instead {} images were provided", images.len(), )), ))); } let num_images = images.len() as u16; let mut offset = ICO_ICONDIR_SIZE + (ICO_DIRENTRY_SIZE * (images.len() as u32)); write_icondir(&mut self.w, num_images)?; for image in images { write_direntry( &mut self.w, image.width, image.height, image.color_type, offset, image.encoded_image.len() as u32, )?; offset += image.encoded_image.len() as u32; } for image in images { self.w.write_all(&image.encoded_image)?; } Ok(()) } } impl ImageEncoder for IcoEncoder { /// Write an ICO image with the specified width, height, and color type. /// /// For color types with 16-bit per channel or larger, the contents of `buf` should be in /// native endian. /// /// WARNING: In image 0.23.14 and earlier this method erroneously expected buf to be in big endian. fn write_image( self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()> { let image = IcoFrame::as_png(buf, width, height, color_type)?; self.encode_images(&[image]) } } fn write_icondir(w: &mut W, num_images: u16) -> io::Result<()> { // Reserved field (must be zero): w.write_u16::(0)?; // Image type (ICO or CUR): w.write_u16::(ICO_IMAGE_TYPE)?; // Number of images in the file: w.write_u16::(num_images)?; Ok(()) } fn write_direntry( w: &mut W, width: u8, height: u8, color: ColorType, data_start: u32, data_size: u32, ) -> io::Result<()> { // Image dimensions: w.write_u8(width)?; w.write_u8(height)?; // Number of colors in palette (or zero for no palette): w.write_u8(0)?; // Reserved field (must be zero): w.write_u8(0)?; // Color planes: w.write_u16::(0)?; // Bits per pixel: w.write_u16::(color.bits_per_pixel())?; // Image data size, in bytes: w.write_u32::(data_size)?; // Image data offset, in bytes: w.write_u32::(data_start)?; Ok(()) } image-0.24.7/src/codecs/ico/mod.rs000064400000000000000000000006161046102023000147310ustar 00000000000000//! Decoding and Encoding of ICO files //! //! A decoder and encoder for ICO (Windows Icon) image container files. //! //! # Related Links //! * //! * pub use self::decoder::IcoDecoder; #[allow(deprecated)] pub use self::encoder::{IcoEncoder, IcoFrame}; mod decoder; mod encoder; image-0.24.7/src/codecs/jpeg/decoder.rs000064400000000000000000001113311046102023000157270ustar 00000000000000use std::convert::TryFrom; use std::io::{self, Cursor, Read}; use std::marker::PhantomData; use std::mem; use crate::color::ColorType; use crate::error::{ DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{ImageDecoder, ImageFormat}; /// JPEG decoder pub struct JpegDecoder { decoder: jpeg::Decoder, metadata: jpeg::ImageInfo, } impl JpegDecoder { /// Create a new decoder that decodes from the stream ```r``` pub fn new(r: R) -> ImageResult> { let mut decoder = jpeg::Decoder::new(r); decoder.read_info().map_err(ImageError::from_jpeg)?; let mut metadata = decoder.info().ok_or_else(|| { ImageError::Decoding(DecodingError::from_format_hint(ImageFormat::Jpeg.into())) })?; // We convert CMYK data to RGB before returning it to the user. if metadata.pixel_format == jpeg::PixelFormat::CMYK32 { metadata.pixel_format = jpeg::PixelFormat::RGB24; } Ok(JpegDecoder { decoder, metadata }) } /// Configure the decoder to scale the image during decoding. /// /// This efficiently scales the image by the smallest supported /// scale factor that produces an image larger than or equal to /// the requested size in at least one axis. The currently /// implemented scale factors are 1/8, 1/4, 1/2 and 1. /// /// To generate a thumbnail of an exact size, pass the desired /// size and then scale to the final size using a traditional /// resampling algorithm. /// /// The size of the image to be loaded, with the scale factor /// applied, is returned. pub fn scale( &mut self, requested_width: u16, requested_height: u16, ) -> ImageResult<(u16, u16)> { let result = self .decoder .scale(requested_width, requested_height) .map_err(ImageError::from_jpeg)?; self.metadata.width = result.0; self.metadata.height = result.1; Ok(result) } } /// Wrapper struct around a `Cursor>` pub struct JpegReader(Cursor>, PhantomData); impl Read for JpegReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { if self.0.position() == 0 && buf.is_empty() { mem::swap(buf, self.0.get_mut()); Ok(buf.len()) } else { self.0.read_to_end(buf) } } } impl<'a, R: 'a + Read> ImageDecoder<'a> for JpegDecoder { type Reader = JpegReader; fn dimensions(&self) -> (u32, u32) { ( u32::from(self.metadata.width), u32::from(self.metadata.height), ) } fn color_type(&self) -> ColorType { ColorType::from_jpeg(self.metadata.pixel_format) } fn icc_profile(&mut self) -> Option> { self.decoder.icc_profile() } fn into_reader(mut self) -> ImageResult { let mut data = self.decoder.decode().map_err(ImageError::from_jpeg)?; data = match self.decoder.info().unwrap().pixel_format { jpeg::PixelFormat::CMYK32 => cmyk_to_rgb(&data), _ => data, }; Ok(JpegReader(Cursor::new(data), PhantomData)) } fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); let mut data = self.decoder.decode().map_err(ImageError::from_jpeg)?; data = match self.decoder.info().unwrap().pixel_format { jpeg::PixelFormat::CMYK32 => cmyk_to_rgb(&data), _ => data, }; buf.copy_from_slice(&data); Ok(()) } } fn cmyk_to_rgb(input: &[u8]) -> Vec { let count = input.len() / 4; let mut output = vec![0; 3 * count]; let in_pixels = input[..4 * count].chunks_exact(4); let out_pixels = output[..3 * count].chunks_exact_mut(3); for (pixel, outp) in in_pixels.zip(out_pixels) { let c = 255 - u16::from(pixel[0]); let m = 255 - u16::from(pixel[1]); let y = 255 - u16::from(pixel[2]); let k = 255 - u16::from(pixel[3]); // CMY -> RGB let r = (k * c) / 255; let g = (k * m) / 255; let b = (k * y) / 255; outp[0] = r as u8; outp[1] = g as u8; outp[2] = b as u8; } output } impl ColorType { fn from_jpeg(pixel_format: jpeg::PixelFormat) -> ColorType { use jpeg::PixelFormat::*; match pixel_format { L8 => ColorType::L8, L16 => ColorType::L16, RGB24 => ColorType::Rgb8, CMYK32 => panic!(), } } } impl ImageError { fn from_jpeg(err: jpeg::Error) -> ImageError { use jpeg::Error::*; match err { err @ Format(_) => { ImageError::Decoding(DecodingError::new(ImageFormat::Jpeg.into(), err)) } Unsupported(desc) => ImageError::Unsupported(UnsupportedError::from_format_and_kind( ImageFormat::Jpeg.into(), UnsupportedErrorKind::GenericFeature(format!("{:?}", desc)), )), Io(err) => ImageError::IoError(err), Internal(err) => { ImageError::Decoding(DecodingError::new(ImageFormat::Jpeg.into(), err)) } } } } #[cfg(test)] mod tests { #[cfg(feature = "benchmarks")] extern crate test; use super::cmyk_to_rgb; #[cfg(feature = "benchmarks")] use test::Bencher; #[cfg(feature = "benchmarks")] const W: usize = 256; #[cfg(feature = "benchmarks")] const H: usize = 256; #[test] fn cmyk_to_rgb_correct() { for c in 0..=255 { for k in 0..=255 { // Based on R = 255 * (1-C/255) * (1-K/255) let r = (255.0 - f32::from(c)) * (255.0 - f32::from(k)) / 255.0; let r_u8 = r as u8; let convert_r = cmyk_to_rgb(&[c, 0, 0, k])[0]; let convert_g = cmyk_to_rgb(&[0, c, 0, k])[1]; let convert_b = cmyk_to_rgb(&[0, 0, c, k])[2]; assert_eq!( convert_r, r_u8, "c = {}, k = {}, cymk_to_rgb[0] = {}, should be {}", c, k, convert_r, r_u8 ); assert_eq!( convert_g, r_u8, "m = {}, k = {}, cymk_to_rgb[1] = {}, should be {}", c, k, convert_g, r_u8 ); assert_eq!( convert_b, r_u8, "y = {}, k = {}, cymk_to_rgb[2] = {}, should be {}", c, k, convert_b, r_u8 ); } } } fn single_pix_correct(cmyk_pix: [u8; 4], rgb_pix_true: [u8; 3]) { let rgb_pix = cmyk_to_rgb(&cmyk_pix); assert_eq!( rgb_pix_true[0], rgb_pix[0], "With CMYK {:?} expected {:?}, got {:?}", cmyk_pix, rgb_pix_true, rgb_pix ); assert_eq!( rgb_pix_true[1], rgb_pix[1], "With CMYK {:?} expected {:?}, got {:?}", cmyk_pix, rgb_pix_true, rgb_pix ); assert_eq!( rgb_pix_true[2], rgb_pix[2], "With CMYK {:?} expected {:?}, got {:?}", cmyk_pix, rgb_pix_true, rgb_pix ); } #[test] fn test_assorted_colors() { let cmyk_pixels = vec![ [0, 51, 102, 65], [153, 204, 0, 65], [0, 0, 0, 67], [0, 85, 170, 69], [0, 0, 0, 71], [0, 0, 0, 73], [0, 17, 34, 75], [51, 68, 85, 75], [102, 119, 136, 75], [153, 170, 187, 75], [204, 221, 238, 75], [0, 0, 0, 77], [0, 0, 0, 79], [0, 85, 170, 81], [0, 0, 0, 83], [0, 3, 6, 85], [9, 12, 15, 85], [18, 21, 24, 85], [27, 30, 33, 85], [36, 39, 42, 85], [45, 48, 51, 85], [54, 57, 60, 85], [63, 66, 69, 85], [72, 75, 78, 85], [81, 84, 87, 85], [90, 93, 96, 85], [99, 102, 105, 85], [108, 111, 114, 85], [117, 120, 123, 85], [126, 129, 132, 85], [135, 138, 141, 85], [144, 147, 150, 85], [153, 156, 159, 85], [162, 165, 168, 85], [171, 174, 177, 85], [180, 183, 186, 85], [189, 192, 195, 85], [198, 201, 204, 85], [207, 210, 213, 85], [216, 219, 222, 85], [225, 228, 231, 85], [234, 237, 240, 85], [243, 246, 249, 85], [252, 0, 0, 85], [0, 85, 170, 87], [0, 0, 0, 89], [0, 0, 0, 91], [0, 85, 170, 93], [0, 51, 102, 95], [153, 204, 0, 95], [0, 0, 0, 97], [0, 85, 170, 99], [0, 0, 0, 101], [0, 0, 0, 103], [0, 17, 34, 105], [51, 68, 85, 105], [102, 119, 136, 105], [153, 170, 187, 105], [204, 221, 238, 105], [0, 0, 0, 107], [0, 0, 0, 109], [0, 85, 170, 111], [0, 0, 0, 113], [0, 51, 102, 115], [153, 204, 0, 115], [0, 85, 170, 117], [0, 15, 30, 119], [45, 60, 75, 119], [90, 105, 120, 119], [135, 150, 165, 119], [180, 195, 210, 119], [225, 240, 0, 119], [0, 0, 0, 121], [0, 85, 170, 123], [0, 51, 102, 125], [153, 204, 0, 125], [0, 0, 0, 127], [0, 0, 0, 128], [0, 85, 170, 129], [0, 51, 102, 130], [153, 204, 0, 130], [0, 0, 0, 131], [0, 85, 170, 132], [0, 0, 0, 133], [0, 0, 0, 134], [0, 17, 34, 135], [51, 68, 85, 135], [102, 119, 136, 135], [153, 170, 187, 135], [204, 221, 238, 135], [0, 15, 30, 136], [45, 60, 75, 136], [90, 105, 120, 136], [135, 150, 165, 136], [180, 195, 210, 136], [225, 240, 0, 136], [0, 0, 0, 137], [0, 85, 170, 138], [0, 0, 0, 139], [0, 51, 102, 140], [153, 204, 0, 140], [0, 85, 170, 141], [0, 0, 0, 142], [0, 0, 0, 143], [0, 85, 170, 144], [0, 51, 102, 145], [153, 204, 0, 145], [0, 0, 0, 146], [0, 85, 170, 147], [0, 0, 0, 148], [0, 0, 0, 149], [0, 17, 34, 150], [51, 68, 85, 150], [102, 119, 136, 150], [153, 170, 187, 150], [204, 221, 238, 150], [0, 0, 0, 151], [0, 0, 0, 152], [0, 5, 10, 153], [15, 20, 25, 153], [30, 35, 40, 153], [45, 50, 55, 153], [60, 65, 70, 153], [75, 80, 85, 153], [90, 95, 100, 153], [105, 110, 115, 153], [120, 125, 130, 153], [135, 140, 145, 153], [150, 155, 160, 153], [165, 170, 175, 153], [180, 185, 190, 153], [195, 200, 205, 153], [210, 215, 220, 153], [225, 230, 235, 153], [240, 245, 250, 153], [0, 0, 0, 154], [0, 51, 102, 155], [153, 204, 0, 155], [0, 85, 170, 156], [0, 0, 0, 157], [0, 0, 0, 158], [0, 85, 170, 159], [0, 51, 102, 160], [153, 204, 0, 160], [0, 0, 0, 161], [0, 85, 170, 162], [0, 0, 0, 163], [0, 0, 0, 164], [0, 17, 34, 165], [51, 68, 85, 165], [102, 119, 136, 165], [153, 170, 187, 165], [204, 221, 238, 165], [0, 0, 0, 166], [0, 0, 0, 167], [0, 85, 170, 168], [0, 0, 0, 169], [0, 3, 6, 170], [9, 12, 15, 170], [18, 21, 24, 170], [27, 30, 33, 170], [36, 39, 42, 170], [45, 48, 51, 170], [54, 57, 60, 170], [63, 66, 69, 170], [72, 75, 78, 170], [81, 84, 87, 170], [90, 93, 96, 170], [99, 102, 105, 170], [108, 111, 114, 170], [117, 120, 123, 170], [126, 129, 132, 170], [135, 138, 141, 170], [144, 147, 150, 170], [153, 156, 159, 170], [162, 165, 168, 170], [171, 174, 177, 170], [180, 183, 186, 170], [189, 192, 195, 170], [198, 201, 204, 170], [207, 210, 213, 170], [216, 219, 222, 170], [225, 228, 231, 170], [234, 237, 240, 170], [243, 246, 249, 170], [252, 0, 0, 170], [0, 85, 170, 171], [0, 0, 0, 172], [0, 0, 0, 173], [0, 85, 170, 174], [0, 51, 102, 175], [153, 204, 0, 175], [0, 0, 0, 176], [0, 85, 170, 177], [0, 0, 0, 178], [0, 0, 0, 179], [0, 17, 34, 180], [51, 68, 85, 180], [102, 119, 136, 180], [153, 170, 187, 180], [204, 221, 238, 180], [0, 0, 0, 181], [0, 0, 0, 182], [0, 85, 170, 183], [0, 0, 0, 184], [0, 51, 102, 185], [153, 204, 0, 185], [0, 85, 170, 186], [0, 15, 30, 187], [45, 60, 75, 187], [90, 105, 120, 187], [135, 150, 165, 187], [180, 195, 210, 187], [225, 240, 0, 187], [0, 0, 0, 188], [0, 85, 170, 189], [0, 51, 102, 190], [153, 204, 0, 190], [0, 0, 0, 191], [0, 85, 170, 192], [0, 0, 0, 193], [0, 0, 0, 194], [0, 17, 34, 195], [51, 68, 85, 195], [102, 119, 136, 195], [153, 170, 187, 195], [204, 221, 238, 195], [0, 0, 0, 196], [0, 0, 0, 197], [0, 85, 170, 198], [0, 0, 0, 199], [0, 51, 102, 200], [153, 204, 0, 200], [0, 85, 170, 201], [0, 0, 0, 202], [0, 0, 0, 203], [0, 5, 10, 204], [15, 20, 25, 204], [30, 35, 40, 204], [45, 50, 55, 204], [60, 65, 70, 204], [75, 80, 85, 204], [90, 95, 100, 204], [105, 110, 115, 204], [120, 125, 130, 204], [135, 140, 145, 204], [150, 155, 160, 204], [165, 170, 175, 204], [180, 185, 190, 204], [195, 200, 205, 204], [210, 215, 220, 204], [225, 230, 235, 204], [240, 245, 250, 204], [0, 51, 102, 205], [153, 204, 0, 205], [0, 0, 0, 206], [0, 85, 170, 207], [0, 0, 0, 208], [0, 0, 0, 209], [0, 17, 34, 210], [51, 68, 85, 210], [102, 119, 136, 210], [153, 170, 187, 210], [204, 221, 238, 210], [0, 0, 0, 211], [0, 0, 0, 212], [0, 85, 170, 213], [0, 0, 0, 214], [0, 51, 102, 215], [153, 204, 0, 215], [0, 85, 170, 216], [0, 0, 0, 217], [0, 0, 0, 218], [0, 85, 170, 219], [0, 51, 102, 220], [153, 204, 0, 220], [0, 15, 30, 221], [45, 60, 75, 221], [90, 105, 120, 221], [135, 150, 165, 221], [180, 195, 210, 221], [225, 240, 0, 221], [0, 85, 170, 222], [0, 0, 0, 223], [0, 0, 0, 224], [0, 17, 34, 225], [51, 68, 85, 225], [102, 119, 136, 225], [153, 170, 187, 225], [204, 221, 238, 225], [0, 0, 0, 226], [0, 0, 0, 227], [0, 85, 170, 228], [0, 0, 0, 229], [0, 51, 102, 230], [153, 204, 0, 230], [0, 85, 170, 231], [0, 0, 0, 232], [0, 0, 0, 233], [0, 85, 170, 234], [0, 51, 102, 235], [153, 204, 0, 235], [0, 0, 0, 236], [0, 85, 170, 237], [0, 15, 30, 238], [45, 60, 75, 238], [90, 105, 120, 238], [135, 150, 165, 238], [180, 195, 210, 238], [225, 240, 0, 238], [0, 0, 0, 239], [0, 17, 34, 240], [51, 68, 85, 240], [102, 119, 136, 240], [153, 170, 187, 240], [204, 221, 238, 240], [0, 0, 0, 241], [0, 0, 0, 242], [0, 85, 170, 243], [0, 0, 0, 244], [0, 51, 102, 245], [153, 204, 0, 245], [0, 85, 170, 246], [0, 0, 0, 247], [0, 0, 0, 248], [0, 85, 170, 249], [0, 51, 102, 250], [153, 204, 0, 250], [0, 0, 0, 251], [0, 85, 170, 252], [0, 0, 0, 253], [0, 0, 0, 254], [5, 15, 25, 102], [35, 40, 45, 102], [50, 55, 60, 102], [65, 70, 75, 102], [80, 85, 90, 102], [95, 100, 105, 102], [110, 115, 120, 102], [125, 130, 135, 102], [140, 145, 150, 102], [155, 160, 165, 102], [170, 175, 180, 102], [185, 190, 195, 102], [200, 205, 210, 102], [215, 220, 225, 102], [230, 235, 240, 102], [245, 250, 0, 102], [15, 45, 60, 68], [75, 90, 105, 68], [120, 135, 150, 68], [165, 180, 195, 68], [210, 225, 240, 68], [17, 34, 51, 45], [68, 85, 102, 45], [119, 136, 153, 45], [170, 187, 204, 45], [221, 238, 0, 45], [17, 51, 68, 60], [85, 102, 119, 60], [136, 153, 170, 60], [187, 204, 221, 60], [238, 0, 0, 60], [17, 34, 51, 90], [68, 85, 102, 90], [119, 136, 153, 90], [170, 187, 204, 90], [221, 238, 0, 90], [17, 34, 51, 120], [68, 85, 102, 120], [119, 136, 153, 120], [170, 187, 204, 120], [221, 238, 0, 120], [20, 25, 30, 51], [35, 40, 45, 51], [50, 55, 60, 51], [65, 70, 75, 51], [80, 85, 90, 51], [95, 100, 105, 51], [110, 115, 120, 51], [125, 130, 135, 51], [140, 145, 150, 51], [155, 160, 165, 51], [170, 175, 180, 51], [185, 190, 195, 51], [200, 205, 210, 51], [215, 220, 225, 51], [230, 235, 240, 51], [245, 250, 0, 51], [45, 60, 75, 17], [90, 105, 120, 17], [135, 150, 165, 17], [180, 195, 210, 17], [225, 240, 0, 17], [45, 75, 90, 34], [105, 120, 135, 34], [150, 165, 180, 34], [195, 210, 225, 34], [240, 0, 0, 34], [51, 153, 204, 20], [51, 102, 153, 25], [204, 0, 0, 25], [51, 85, 119, 30], [136, 153, 170, 30], [187, 204, 221, 30], [238, 0, 0, 30], [51, 102, 153, 35], [204, 0, 0, 35], [51, 102, 153, 40], [204, 0, 0, 40], [51, 102, 153, 50], [204, 0, 0, 50], [51, 102, 153, 55], [204, 0, 0, 55], [51, 102, 153, 70], [204, 0, 0, 70], [51, 102, 153, 80], [204, 0, 0, 80], [51, 102, 153, 100], [204, 0, 0, 100], [51, 102, 153, 110], [204, 0, 0, 110], [65, 67, 69, 0], [71, 73, 75, 0], [77, 79, 81, 0], [83, 85, 87, 0], [89, 91, 93, 0], [95, 97, 99, 0], [101, 103, 105, 0], [107, 109, 111, 0], [113, 115, 117, 0], [119, 121, 123, 0], [125, 127, 128, 0], [129, 130, 131, 0], [132, 133, 134, 0], [135, 136, 137, 0], [138, 139, 140, 0], [141, 142, 143, 0], [144, 145, 146, 0], [147, 148, 149, 0], [150, 151, 152, 0], [153, 154, 155, 0], [156, 157, 158, 0], [159, 160, 161, 0], [162, 163, 164, 0], [165, 166, 167, 0], [168, 169, 170, 0], [171, 172, 173, 0], [174, 175, 176, 0], [177, 178, 179, 0], [180, 181, 182, 0], [183, 184, 185, 0], [186, 187, 188, 0], [189, 190, 191, 0], [192, 193, 194, 0], [195, 196, 197, 0], [198, 199, 200, 0], [201, 202, 203, 0], [204, 205, 206, 0], [207, 208, 209, 0], [210, 211, 212, 0], [213, 214, 215, 0], [216, 217, 218, 0], [219, 220, 221, 0], [222, 223, 224, 0], [225, 226, 227, 0], [228, 229, 230, 0], [231, 232, 233, 0], [234, 235, 236, 0], [237, 238, 239, 0], [240, 241, 242, 0], [243, 244, 245, 0], [246, 247, 248, 0], [249, 250, 251, 0], [252, 253, 254, 0], [68, 85, 102, 15], [119, 136, 153, 15], [170, 187, 204, 15], [221, 238, 0, 15], [85, 170, 0, 3], [85, 170, 0, 6], [85, 170, 0, 9], [85, 170, 0, 12], [85, 170, 0, 18], [85, 170, 0, 21], [85, 170, 0, 24], [85, 170, 0, 27], [85, 170, 0, 33], [85, 170, 0, 36], [85, 170, 0, 39], [85, 170, 0, 42], [85, 170, 0, 48], [85, 170, 0, 54], [85, 170, 0, 57], [85, 170, 0, 63], [85, 170, 0, 66], [85, 170, 0, 72], [85, 170, 0, 78], [85, 170, 0, 84], [85, 170, 0, 96], [85, 170, 0, 108], [85, 170, 0, 114], [85, 170, 0, 126], [102, 153, 204, 5], [153, 204, 0, 10], ]; let rgb_pixels = vec![ [190, 152, 114], [76, 38, 190], [188, 188, 188], [186, 124, 62], [184, 184, 184], [182, 182, 182], [180, 168, 156], [144, 132, 120], [108, 96, 84], [72, 60, 48], [36, 24, 12], [178, 178, 178], [176, 176, 176], [174, 116, 58], [172, 172, 172], [170, 168, 166], [164, 162, 160], [158, 156, 154], [152, 150, 148], [146, 144, 142], [140, 138, 136], [134, 132, 130], [128, 126, 124], [122, 120, 118], [116, 114, 112], [110, 108, 106], [104, 102, 100], [98, 96, 94], [92, 90, 88], [86, 84, 82], [80, 78, 76], [74, 72, 70], [68, 66, 64], [62, 60, 58], [56, 54, 52], [50, 48, 46], [44, 42, 40], [38, 36, 34], [32, 30, 28], [26, 24, 22], [20, 18, 16], [14, 12, 10], [8, 6, 4], [2, 170, 170], [168, 112, 56], [166, 166, 166], [164, 164, 164], [162, 108, 54], [160, 128, 96], [64, 32, 160], [158, 158, 158], [156, 104, 52], [154, 154, 154], [152, 152, 152], [150, 140, 130], [120, 110, 100], [90, 80, 70], [60, 50, 40], [30, 20, 10], [148, 148, 148], [146, 146, 146], [144, 96, 48], [142, 142, 142], [140, 112, 84], [56, 28, 140], [138, 92, 46], [136, 128, 120], [112, 104, 96], [88, 80, 72], [64, 56, 48], [40, 32, 24], [16, 8, 136], [134, 134, 134], [132, 88, 44], [130, 104, 78], [52, 26, 130], [128, 128, 128], [127, 127, 127], [126, 84, 42], [125, 100, 75], [50, 25, 125], [124, 124, 124], [123, 82, 41], [122, 122, 122], [121, 121, 121], [120, 112, 104], [96, 88, 80], [72, 64, 56], [48, 40, 32], [24, 16, 8], [119, 112, 105], [98, 91, 84], [77, 70, 63], [56, 49, 42], [35, 28, 21], [14, 7, 119], [118, 118, 118], [117, 78, 39], [116, 116, 116], [115, 92, 69], [46, 23, 115], [114, 76, 38], [113, 113, 113], [112, 112, 112], [111, 74, 37], [110, 88, 66], [44, 22, 110], [109, 109, 109], [108, 72, 36], [107, 107, 107], [106, 106, 106], [105, 98, 91], [84, 77, 70], [63, 56, 49], [42, 35, 28], [21, 14, 7], [104, 104, 104], [103, 103, 103], [102, 100, 98], [96, 94, 92], [90, 88, 86], [84, 82, 80], [78, 76, 74], [72, 70, 68], [66, 64, 62], [60, 58, 56], [54, 52, 50], [48, 46, 44], [42, 40, 38], [36, 34, 32], [30, 28, 26], [24, 22, 20], [18, 16, 14], [12, 10, 8], [6, 4, 2], [101, 101, 101], [100, 80, 60], [40, 20, 100], [99, 66, 33], [98, 98, 98], [97, 97, 97], [96, 64, 32], [95, 76, 57], [38, 19, 95], [94, 94, 94], [93, 62, 31], [92, 92, 92], [91, 91, 91], [90, 84, 78], [72, 66, 60], [54, 48, 42], [36, 30, 24], [18, 12, 6], [89, 89, 89], [88, 88, 88], [87, 58, 29], [86, 86, 86], [85, 84, 83], [82, 81, 80], [79, 78, 77], [76, 75, 74], [73, 72, 71], [70, 69, 68], [67, 66, 65], [64, 63, 62], [61, 60, 59], [58, 57, 56], [55, 54, 53], [52, 51, 50], [49, 48, 47], [46, 45, 44], [43, 42, 41], [40, 39, 38], [37, 36, 35], [34, 33, 32], [31, 30, 29], [28, 27, 26], [25, 24, 23], [22, 21, 20], [19, 18, 17], [16, 15, 14], [13, 12, 11], [10, 9, 8], [7, 6, 5], [4, 3, 2], [1, 85, 85], [84, 56, 28], [83, 83, 83], [82, 82, 82], [81, 54, 27], [80, 64, 48], [32, 16, 80], [79, 79, 79], [78, 52, 26], [77, 77, 77], [76, 76, 76], [75, 70, 65], [60, 55, 50], [45, 40, 35], [30, 25, 20], [15, 10, 5], [74, 74, 74], [73, 73, 73], [72, 48, 24], [71, 71, 71], [70, 56, 42], [28, 14, 70], [69, 46, 23], [68, 64, 60], [56, 52, 48], [44, 40, 36], [32, 28, 24], [20, 16, 12], [8, 4, 68], [67, 67, 67], [66, 44, 22], [65, 52, 39], [26, 13, 65], [64, 64, 64], [63, 42, 21], [62, 62, 62], [61, 61, 61], [60, 56, 52], [48, 44, 40], [36, 32, 28], [24, 20, 16], [12, 8, 4], [59, 59, 59], [58, 58, 58], [57, 38, 19], [56, 56, 56], [55, 44, 33], [22, 11, 55], [54, 36, 18], [53, 53, 53], [52, 52, 52], [51, 50, 49], [48, 47, 46], [45, 44, 43], [42, 41, 40], [39, 38, 37], [36, 35, 34], [33, 32, 31], [30, 29, 28], [27, 26, 25], [24, 23, 22], [21, 20, 19], [18, 17, 16], [15, 14, 13], [12, 11, 10], [9, 8, 7], [6, 5, 4], [3, 2, 1], [50, 40, 30], [20, 10, 50], [49, 49, 49], [48, 32, 16], [47, 47, 47], [46, 46, 46], [45, 42, 39], [36, 33, 30], [27, 24, 21], [18, 15, 12], [9, 6, 3], [44, 44, 44], [43, 43, 43], [42, 28, 14], [41, 41, 41], [40, 32, 24], [16, 8, 40], [39, 26, 13], [38, 38, 38], [37, 37, 37], [36, 24, 12], [35, 28, 21], [14, 7, 35], [34, 32, 30], [28, 26, 24], [22, 20, 18], [16, 14, 12], [10, 8, 6], [4, 2, 34], [33, 22, 11], [32, 32, 32], [31, 31, 31], [30, 28, 26], [24, 22, 20], [18, 16, 14], [12, 10, 8], [6, 4, 2], [29, 29, 29], [28, 28, 28], [27, 18, 9], [26, 26, 26], [25, 20, 15], [10, 5, 25], [24, 16, 8], [23, 23, 23], [22, 22, 22], [21, 14, 7], [20, 16, 12], [8, 4, 20], [19, 19, 19], [18, 12, 6], [17, 16, 15], [14, 13, 12], [11, 10, 9], [8, 7, 6], [5, 4, 3], [2, 1, 17], [16, 16, 16], [15, 14, 13], [12, 11, 10], [9, 8, 7], [6, 5, 4], [3, 2, 1], [14, 14, 14], [13, 13, 13], [12, 8, 4], [11, 11, 11], [10, 8, 6], [4, 2, 10], [9, 6, 3], [8, 8, 8], [7, 7, 7], [6, 4, 2], [5, 4, 3], [2, 1, 5], [4, 4, 4], [3, 2, 1], [2, 2, 2], [1, 1, 1], [150, 144, 138], [132, 129, 126], [123, 120, 117], [114, 111, 108], [105, 102, 99], [96, 93, 90], [87, 84, 81], [78, 75, 72], [69, 66, 63], [60, 57, 54], [51, 48, 45], [42, 39, 36], [33, 30, 27], [24, 21, 18], [15, 12, 9], [6, 3, 153], [176, 154, 143], [132, 121, 110], [99, 88, 77], [66, 55, 44], [33, 22, 11], [196, 182, 168], [154, 140, 126], [112, 98, 84], [70, 56, 42], [28, 14, 210], [182, 156, 143], [130, 117, 104], [91, 78, 65], [52, 39, 26], [13, 195, 195], [154, 143, 132], [121, 110, 99], [88, 77, 66], [55, 44, 33], [22, 11, 165], [126, 117, 108], [99, 90, 81], [72, 63, 54], [45, 36, 27], [18, 9, 135], [188, 184, 180], [176, 172, 168], [164, 160, 156], [152, 148, 144], [140, 136, 132], [128, 124, 120], [116, 112, 108], [104, 100, 96], [92, 88, 84], [80, 76, 72], [68, 64, 60], [56, 52, 48], [44, 40, 36], [32, 28, 24], [20, 16, 12], [8, 4, 204], [196, 182, 168], [154, 140, 126], [112, 98, 84], [70, 56, 42], [28, 14, 238], [182, 156, 143], [130, 117, 104], [91, 78, 65], [52, 39, 26], [13, 221, 221], [188, 94, 47], [184, 138, 92], [46, 230, 230], [180, 150, 120], [105, 90, 75], [60, 45, 30], [15, 225, 225], [176, 132, 88], [44, 220, 220], [172, 129, 86], [43, 215, 215], [164, 123, 82], [41, 205, 205], [160, 120, 80], [40, 200, 200], [148, 111, 74], [37, 185, 185], [140, 105, 70], [35, 175, 175], [124, 93, 62], [31, 155, 155], [116, 87, 58], [29, 145, 145], [190, 188, 186], [184, 182, 180], [178, 176, 174], [172, 170, 168], [166, 164, 162], [160, 158, 156], [154, 152, 150], [148, 146, 144], [142, 140, 138], [136, 134, 132], [130, 128, 127], [126, 125, 124], [123, 122, 121], [120, 119, 118], [117, 116, 115], [114, 113, 112], [111, 110, 109], [108, 107, 106], [105, 104, 103], [102, 101, 100], [99, 98, 97], [96, 95, 94], [93, 92, 91], [90, 89, 88], [87, 86, 85], [84, 83, 82], [81, 80, 79], [78, 77, 76], [75, 74, 73], [72, 71, 70], [69, 68, 67], [66, 65, 64], [63, 62, 61], [60, 59, 58], [57, 56, 55], [54, 53, 52], [51, 50, 49], [48, 47, 46], [45, 44, 43], [42, 41, 40], [39, 38, 37], [36, 35, 34], [33, 32, 31], [30, 29, 28], [27, 26, 25], [24, 23, 22], [21, 20, 19], [18, 17, 16], [15, 14, 13], [12, 11, 10], [9, 8, 7], [6, 5, 4], [3, 2, 1], [176, 160, 144], [128, 112, 96], [80, 64, 48], [32, 16, 240], [168, 84, 252], [166, 83, 249], [164, 82, 246], [162, 81, 243], [158, 79, 237], [156, 78, 234], [154, 77, 231], [152, 76, 228], [148, 74, 222], [146, 73, 219], [144, 72, 216], [142, 71, 213], [138, 69, 207], [134, 67, 201], [132, 66, 198], [128, 64, 192], [126, 63, 189], [122, 61, 183], [118, 59, 177], [114, 57, 171], [106, 53, 159], [98, 49, 147], [94, 47, 141], [86, 43, 129], [150, 100, 50], [98, 49, 245], ]; for (&cmyk_pixel, rgb_pixel) in cmyk_pixels.iter().zip(rgb_pixels) { single_pix_correct(cmyk_pixel, rgb_pixel); } } #[cfg(feature = "benchmarks")] #[bench] fn bench_cmyk_to_rgb(b: &mut Bencher) { let mut v = Vec::with_capacity((W * H * 4) as usize); for c in 0..=255 { for k in 0..=255 { v.push(c as u8); v.push(0); v.push(0); v.push(k as u8); } } b.iter(|| { cmyk_to_rgb(&v); }); } #[cfg(feature = "benchmarks")] #[bench] fn bench_cmyk_to_rgb_single(b: &mut Bencher) { b.iter(|| { cmyk_to_rgb(&[128, 128, 128, 128]); }); } } image-0.24.7/src/codecs/jpeg/encoder.rs000064400000000000000000001007371046102023000157510ustar 00000000000000#![allow(clippy::too_many_arguments)] use std::borrow::Cow; use std::convert::TryFrom; use std::io::{self, Write}; use crate::error::{ ImageError, ImageResult, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{ImageEncoder, ImageFormat}; use crate::utils::clamp; use crate::{ColorType, GenericImageView, ImageBuffer, Luma, LumaA, Pixel, Rgb, Rgba}; use super::entropy::build_huff_lut_const; use super::transform; use crate::traits::PixelWithColorType; // Markers // Baseline DCT static SOF0: u8 = 0xC0; // Huffman Tables static DHT: u8 = 0xC4; // Start of Image (standalone) static SOI: u8 = 0xD8; // End of image (standalone) static EOI: u8 = 0xD9; // Start of Scan static SOS: u8 = 0xDA; // Quantization Tables static DQT: u8 = 0xDB; // Application segments start and end static APP0: u8 = 0xE0; // section K.1 // table K.1 #[rustfmt::skip] static STD_LUMA_QTABLE: [u8; 64] = [ 16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, 14, 13, 16, 24, 40, 57, 69, 56, 14, 17, 22, 29, 51, 87, 80, 62, 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104, 113, 92, 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99, ]; // table K.2 #[rustfmt::skip] static STD_CHROMA_QTABLE: [u8; 64] = [ 17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, 24, 26, 56, 99, 99, 99, 99, 99, 47, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, ]; // section K.3 // Code lengths and values for table K.3 static STD_LUMA_DC_CODE_LENGTHS: [u8; 16] = [ 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; static STD_LUMA_DC_VALUES: [u8; 12] = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, ]; static STD_LUMA_DC_HUFF_LUT: [(u8, u16); 256] = build_huff_lut_const(&STD_LUMA_DC_CODE_LENGTHS, &STD_LUMA_DC_VALUES); // Code lengths and values for table K.4 static STD_CHROMA_DC_CODE_LENGTHS: [u8; 16] = [ 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, ]; static STD_CHROMA_DC_VALUES: [u8; 12] = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, ]; static STD_CHROMA_DC_HUFF_LUT: [(u8, u16); 256] = build_huff_lut_const(&STD_CHROMA_DC_CODE_LENGTHS, &STD_CHROMA_DC_VALUES); // Code lengths and values for table k.5 static STD_LUMA_AC_CODE_LENGTHS: [u8; 16] = [ 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, ]; static STD_LUMA_AC_VALUES: [u8; 162] = [ 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, ]; static STD_LUMA_AC_HUFF_LUT: [(u8, u16); 256] = build_huff_lut_const(&STD_LUMA_AC_CODE_LENGTHS, &STD_LUMA_AC_VALUES); // Code lengths and values for table k.6 static STD_CHROMA_AC_CODE_LENGTHS: [u8; 16] = [ 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, ]; static STD_CHROMA_AC_VALUES: [u8; 162] = [ 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, ]; static STD_CHROMA_AC_HUFF_LUT: [(u8, u16); 256] = build_huff_lut_const(&STD_CHROMA_AC_CODE_LENGTHS, &STD_CHROMA_AC_VALUES); static DCCLASS: u8 = 0; static ACCLASS: u8 = 1; static LUMADESTINATION: u8 = 0; static CHROMADESTINATION: u8 = 1; static LUMAID: u8 = 1; static CHROMABLUEID: u8 = 2; static CHROMAREDID: u8 = 3; /// The permutation of dct coefficients. #[rustfmt::skip] static UNZIGZAG: [u8; 64] = [ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, ]; /// A representation of a JPEG component #[derive(Copy, Clone)] struct Component { /// The Component's identifier id: u8, /// Horizontal sampling factor h: u8, /// Vertical sampling factor v: u8, /// The quantization table selector tq: u8, /// Index to the Huffman DC Table dc_table: u8, /// Index to the AC Huffman Table ac_table: u8, /// The dc prediction of the component _dc_pred: i32, } pub(crate) struct BitWriter { w: W, accumulator: u32, nbits: u8, } impl BitWriter { fn new(w: W) -> Self { BitWriter { w, accumulator: 0, nbits: 0, } } fn write_bits(&mut self, bits: u16, size: u8) -> io::Result<()> { if size == 0 { return Ok(()); } self.nbits += size; self.accumulator |= u32::from(bits) << (32 - self.nbits) as usize; while self.nbits >= 8 { let byte = self.accumulator >> 24; self.w.write_all(&[byte as u8])?; if byte == 0xFF { self.w.write_all(&[0x00])?; } self.nbits -= 8; self.accumulator <<= 8; } Ok(()) } fn pad_byte(&mut self) -> io::Result<()> { self.write_bits(0x7F, 7) } fn huffman_encode(&mut self, val: u8, table: &[(u8, u16); 256]) -> io::Result<()> { let (size, code) = table[val as usize]; if size > 16 { panic!("bad huffman value"); } self.write_bits(code, size) } fn write_block( &mut self, block: &[i32; 64], prevdc: i32, dctable: &[(u8, u16); 256], actable: &[(u8, u16); 256], ) -> io::Result { // Differential DC encoding let dcval = block[0]; let diff = dcval - prevdc; let (size, value) = encode_coefficient(diff); self.huffman_encode(size, dctable)?; self.write_bits(value, size)?; // Figure F.2 let mut zero_run = 0; for &k in &UNZIGZAG[1..] { if block[k as usize] == 0 { zero_run += 1; } else { while zero_run > 15 { self.huffman_encode(0xF0, actable)?; zero_run -= 16; } let (size, value) = encode_coefficient(block[k as usize]); let symbol = (zero_run << 4) | size; self.huffman_encode(symbol, actable)?; self.write_bits(value, size)?; zero_run = 0; } } if block[UNZIGZAG[63] as usize] == 0 { self.huffman_encode(0x00, actable)?; } Ok(dcval) } fn write_marker(&mut self, marker: u8) -> io::Result<()> { self.w.write_all(&[0xFF, marker]) } fn write_segment(&mut self, marker: u8, data: &[u8]) -> io::Result<()> { self.w.write_all(&[0xFF, marker])?; self.w.write_all(&(data.len() as u16 + 2).to_be_bytes())?; self.w.write_all(data) } } /// Represents a unit in which the density of an image is measured #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PixelDensityUnit { /// Represents the absence of a unit, the values indicate only a /// [pixel aspect ratio](https://en.wikipedia.org/wiki/Pixel_aspect_ratio) PixelAspectRatio, /// Pixels per inch (2.54 cm) Inches, /// Pixels per centimeter Centimeters, } /// Represents the pixel density of an image /// /// For example, a 300 DPI image is represented by: /// /// ```rust /// use image::codecs::jpeg::*; /// let hdpi = PixelDensity::dpi(300); /// assert_eq!(hdpi, PixelDensity {density: (300,300), unit: PixelDensityUnit::Inches}) /// ``` #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PixelDensity { /// A couple of values for (Xdensity, Ydensity) pub density: (u16, u16), /// The unit in which the density is measured pub unit: PixelDensityUnit, } impl PixelDensity { /// Creates the most common pixel density type: /// the horizontal and the vertical density are equal, /// and measured in pixels per inch. pub fn dpi(density: u16) -> Self { PixelDensity { density: (density, density), unit: PixelDensityUnit::Inches, } } } impl Default for PixelDensity { /// Returns a pixel density with a pixel aspect ratio of 1 fn default() -> Self { PixelDensity { density: (1, 1), unit: PixelDensityUnit::PixelAspectRatio, } } } /// The representation of a JPEG encoder pub struct JpegEncoder { writer: BitWriter, components: Vec, tables: Vec<[u8; 64]>, luma_dctable: Cow<'static, [(u8, u16); 256]>, luma_actable: Cow<'static, [(u8, u16); 256]>, chroma_dctable: Cow<'static, [(u8, u16); 256]>, chroma_actable: Cow<'static, [(u8, u16); 256]>, pixel_density: PixelDensity, } impl JpegEncoder { /// Create a new encoder that writes its output to ```w``` pub fn new(w: W) -> JpegEncoder { JpegEncoder::new_with_quality(w, 75) } /// Create a new encoder that writes its output to ```w```, and has /// the quality parameter ```quality``` with a value in the range 1-100 /// where 1 is the worst and 100 is the best. pub fn new_with_quality(w: W, quality: u8) -> JpegEncoder { let components = vec![ Component { id: LUMAID, h: 1, v: 1, tq: LUMADESTINATION, dc_table: LUMADESTINATION, ac_table: LUMADESTINATION, _dc_pred: 0, }, Component { id: CHROMABLUEID, h: 1, v: 1, tq: CHROMADESTINATION, dc_table: CHROMADESTINATION, ac_table: CHROMADESTINATION, _dc_pred: 0, }, Component { id: CHROMAREDID, h: 1, v: 1, tq: CHROMADESTINATION, dc_table: CHROMADESTINATION, ac_table: CHROMADESTINATION, _dc_pred: 0, }, ]; // Derive our quantization table scaling value using the libjpeg algorithm let scale = u32::from(clamp(quality, 1, 100)); let scale = if scale < 50 { 5000 / scale } else { 200 - scale * 2 }; let mut tables = vec![STD_LUMA_QTABLE, STD_CHROMA_QTABLE]; tables.iter_mut().for_each(|t| { t.iter_mut().for_each(|v| { *v = clamp( (u32::from(*v) * scale + 50) / 100, 1, u32::from(u8::max_value()), ) as u8; }) }); JpegEncoder { writer: BitWriter::new(w), components, tables, luma_dctable: Cow::Borrowed(&STD_LUMA_DC_HUFF_LUT), luma_actable: Cow::Borrowed(&STD_LUMA_AC_HUFF_LUT), chroma_dctable: Cow::Borrowed(&STD_CHROMA_DC_HUFF_LUT), chroma_actable: Cow::Borrowed(&STD_CHROMA_AC_HUFF_LUT), pixel_density: PixelDensity::default(), } } /// Set the pixel density of the images the encoder will encode. /// If this method is not called, then a default pixel aspect ratio of 1x1 will be applied, /// and no DPI information will be stored in the image. pub fn set_pixel_density(&mut self, pixel_density: PixelDensity) { self.pixel_density = pixel_density; } /// Encodes the image stored in the raw byte buffer ```image``` /// that has dimensions ```width``` and ```height``` /// and ```ColorType``` ```c``` /// /// The Image in encoded with subsampling ratio 4:2:2 pub fn encode( &mut self, image: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()> { match color_type { ColorType::L8 => { let image: ImageBuffer, _> = ImageBuffer::from_raw(width, height, image).unwrap(); self.encode_image(&image) } ColorType::La8 => { let image: ImageBuffer, _> = ImageBuffer::from_raw(width, height, image).unwrap(); self.encode_image(&image) } ColorType::Rgb8 => { let image: ImageBuffer, _> = ImageBuffer::from_raw(width, height, image).unwrap(); self.encode_image(&image) } ColorType::Rgba8 => { let image: ImageBuffer, _> = ImageBuffer::from_raw(width, height, image).unwrap(); self.encode_image(&image) } _ => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Jpeg.into(), UnsupportedErrorKind::Color(color_type.into()), ), )), } } /// Encodes the given image. /// /// As a special feature this does not require the whole image to be present in memory at the /// same time such that it may be computed on the fly, which is why this method exists on this /// encoder but not on others. Instead the encoder will iterate over 8-by-8 blocks of pixels at /// a time, inspecting each pixel exactly once. You can rely on this behaviour when calling /// this method. /// /// The Image in encoded with subsampling ratio 4:2:2 pub fn encode_image(&mut self, image: &I) -> ImageResult<()> where I::Pixel: PixelWithColorType, { let n = I::Pixel::CHANNEL_COUNT; let color_type = I::Pixel::COLOR_TYPE; let num_components = if n == 1 || n == 2 { 1 } else { 3 }; self.writer.write_marker(SOI)?; let mut buf = Vec::new(); build_jfif_header(&mut buf, self.pixel_density); self.writer.write_segment(APP0, &buf)?; build_frame_header( &mut buf, 8, // TODO: not idiomatic yet. Should be an EncodingError and mention jpg. Further it // should check dimensions prior to writing. u16::try_from(image.width()).map_err(|_| { ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, )) })?, u16::try_from(image.height()).map_err(|_| { ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, )) })?, &self.components[..num_components], ); self.writer.write_segment(SOF0, &buf)?; assert_eq!(self.tables.len(), 2); let numtables = if num_components == 1 { 1 } else { 2 }; for (i, table) in self.tables[..numtables].iter().enumerate() { build_quantization_segment(&mut buf, 8, i as u8, table); self.writer.write_segment(DQT, &buf)?; } build_huffman_segment( &mut buf, DCCLASS, LUMADESTINATION, &STD_LUMA_DC_CODE_LENGTHS, &STD_LUMA_DC_VALUES, ); self.writer.write_segment(DHT, &buf)?; build_huffman_segment( &mut buf, ACCLASS, LUMADESTINATION, &STD_LUMA_AC_CODE_LENGTHS, &STD_LUMA_AC_VALUES, ); self.writer.write_segment(DHT, &buf)?; if num_components == 3 { build_huffman_segment( &mut buf, DCCLASS, CHROMADESTINATION, &STD_CHROMA_DC_CODE_LENGTHS, &STD_CHROMA_DC_VALUES, ); self.writer.write_segment(DHT, &buf)?; build_huffman_segment( &mut buf, ACCLASS, CHROMADESTINATION, &STD_CHROMA_AC_CODE_LENGTHS, &STD_CHROMA_AC_VALUES, ); self.writer.write_segment(DHT, &buf)?; } build_scan_header(&mut buf, &self.components[..num_components]); self.writer.write_segment(SOS, &buf)?; if color_type.has_color() { self.encode_rgb(image) } else { self.encode_gray(image) }?; self.writer.pad_byte()?; self.writer.write_marker(EOI)?; Ok(()) } fn encode_gray(&mut self, image: &I) -> io::Result<()> { let mut yblock = [0u8; 64]; let mut y_dcprev = 0; let mut dct_yblock = [0i32; 64]; for y in (0..image.height()).step_by(8) { for x in (0..image.width()).step_by(8) { copy_blocks_gray(image, x, y, &mut yblock); // Level shift and fdct // Coeffs are scaled by 8 transform::fdct(&yblock, &mut dct_yblock); // Quantization for (i, dct) in dct_yblock.iter_mut().enumerate() { *dct = ((*dct / 8) as f32 / f32::from(self.tables[0][i])).round() as i32; } let la = &*self.luma_actable; let ld = &*self.luma_dctable; y_dcprev = self.writer.write_block(&dct_yblock, y_dcprev, ld, la)?; } } Ok(()) } fn encode_rgb(&mut self, image: &I) -> io::Result<()> { let mut y_dcprev = 0; let mut cb_dcprev = 0; let mut cr_dcprev = 0; let mut dct_yblock = [0i32; 64]; let mut dct_cb_block = [0i32; 64]; let mut dct_cr_block = [0i32; 64]; let mut yblock = [0u8; 64]; let mut cb_block = [0u8; 64]; let mut cr_block = [0u8; 64]; for y in (0..image.height()).step_by(8) { for x in (0..image.width()).step_by(8) { // RGB -> YCbCr copy_blocks_ycbcr(image, x, y, &mut yblock, &mut cb_block, &mut cr_block); // Level shift and fdct // Coeffs are scaled by 8 transform::fdct(&yblock, &mut dct_yblock); transform::fdct(&cb_block, &mut dct_cb_block); transform::fdct(&cr_block, &mut dct_cr_block); // Quantization for i in 0usize..64 { dct_yblock[i] = ((dct_yblock[i] / 8) as f32 / f32::from(self.tables[0][i])).round() as i32; dct_cb_block[i] = ((dct_cb_block[i] / 8) as f32 / f32::from(self.tables[1][i])) .round() as i32; dct_cr_block[i] = ((dct_cr_block[i] / 8) as f32 / f32::from(self.tables[1][i])) .round() as i32; } let la = &*self.luma_actable; let ld = &*self.luma_dctable; let cd = &*self.chroma_dctable; let ca = &*self.chroma_actable; y_dcprev = self.writer.write_block(&dct_yblock, y_dcprev, ld, la)?; cb_dcprev = self.writer.write_block(&dct_cb_block, cb_dcprev, cd, ca)?; cr_dcprev = self.writer.write_block(&dct_cr_block, cr_dcprev, cd, ca)?; } } Ok(()) } } impl ImageEncoder for JpegEncoder { fn write_image( mut self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()> { self.encode(buf, width, height, color_type) } } fn build_jfif_header(m: &mut Vec, density: PixelDensity) { m.clear(); m.extend_from_slice(b"JFIF"); m.extend_from_slice(&[ 0, 0x01, 0x02, match density.unit { PixelDensityUnit::PixelAspectRatio => 0x00, PixelDensityUnit::Inches => 0x01, PixelDensityUnit::Centimeters => 0x02, }, ]); m.extend_from_slice(&density.density.0.to_be_bytes()); m.extend_from_slice(&density.density.1.to_be_bytes()); m.extend_from_slice(&[0, 0]); } fn build_frame_header( m: &mut Vec, precision: u8, width: u16, height: u16, components: &[Component], ) { m.clear(); m.push(precision); m.extend_from_slice(&height.to_be_bytes()); m.extend_from_slice(&width.to_be_bytes()); m.push(components.len() as u8); for &comp in components.iter() { let hv = (comp.h << 4) | comp.v; m.extend_from_slice(&[comp.id, hv, comp.tq]); } } fn build_scan_header(m: &mut Vec, components: &[Component]) { m.clear(); m.push(components.len() as u8); for &comp in components.iter() { let tables = (comp.dc_table << 4) | comp.ac_table; m.extend_from_slice(&[comp.id, tables]); } // spectral start and end, approx. high and low m.extend_from_slice(&[0, 63, 0]); } fn build_huffman_segment( m: &mut Vec, class: u8, destination: u8, numcodes: &[u8; 16], values: &[u8], ) { m.clear(); let tcth = (class << 4) | destination; m.push(tcth); m.extend_from_slice(numcodes); let sum: usize = numcodes.iter().map(|&x| x as usize).sum(); assert_eq!(sum, values.len()); m.extend_from_slice(values); } fn build_quantization_segment(m: &mut Vec, precision: u8, identifier: u8, qtable: &[u8; 64]) { m.clear(); let p = if precision == 8 { 0 } else { 1 }; let pqtq = (p << 4) | identifier; m.push(pqtq); for &i in &UNZIGZAG[..] { m.push(qtable[i as usize]); } } fn encode_coefficient(coefficient: i32) -> (u8, u16) { let mut magnitude = coefficient.unsigned_abs() as u16; let mut num_bits = 0u8; while magnitude > 0 { magnitude >>= 1; num_bits += 1; } let mask = (1 << num_bits as usize) - 1; let val = if coefficient < 0 { (coefficient - 1) as u16 & mask } else { coefficient as u16 & mask }; (num_bits, val) } #[inline] fn rgb_to_ycbcr(pixel: P) -> (u8, u8, u8) { use crate::traits::Primitive; use num_traits::cast::ToPrimitive; let [r, g, b] = pixel.to_rgb().0; let max: f32 = P::Subpixel::DEFAULT_MAX_VALUE.to_f32().unwrap(); let r: f32 = r.to_f32().unwrap(); let g: f32 = g.to_f32().unwrap(); let b: f32 = b.to_f32().unwrap(); // Coefficients from JPEG File Interchange Format (Version 1.02), multiplied for 255 maximum. let y = 76.245 / max * r + 149.685 / max * g + 29.07 / max * b; let cb = -43.0185 / max * r - 84.4815 / max * g + 127.5 / max * b + 128.; let cr = 127.5 / max * r - 106.7685 / max * g - 20.7315 / max * b + 128.; (y as u8, cb as u8, cr as u8) } /// Returns the pixel at (x,y) if (x,y) is in the image, /// otherwise the closest pixel in the image #[inline] fn pixel_at_or_near(source: &I, x: u32, y: u32) -> I::Pixel { if source.in_bounds(x, y) { source.get_pixel(x, y) } else { source.get_pixel(x.min(source.width() - 1), y.min(source.height() - 1)) } } fn copy_blocks_ycbcr( source: &I, x0: u32, y0: u32, yb: &mut [u8; 64], cbb: &mut [u8; 64], crb: &mut [u8; 64], ) { for y in 0..8 { for x in 0..8 { let pixel = pixel_at_or_near(source, x + x0, y + y0); let (yc, cb, cr) = rgb_to_ycbcr(pixel); yb[(y * 8 + x) as usize] = yc; cbb[(y * 8 + x) as usize] = cb; crb[(y * 8 + x) as usize] = cr; } } } fn copy_blocks_gray(source: &I, x0: u32, y0: u32, gb: &mut [u8; 64]) { use num_traits::cast::ToPrimitive; for y in 0..8 { for x in 0..8 { let pixel = pixel_at_or_near(source, x0 + x, y0 + y); let [luma] = pixel.to_luma().0; gb[(y * 8 + x) as usize] = luma.to_u8().unwrap(); } } } #[cfg(test)] mod tests { use std::io::Cursor; #[cfg(feature = "benchmarks")] extern crate test; #[cfg(feature = "benchmarks")] use test::Bencher; use crate::color::ColorType; use crate::error::ParameterErrorKind::DimensionMismatch; use crate::image::ImageDecoder; use crate::{ImageEncoder, ImageError}; use super::super::JpegDecoder; use super::{ build_frame_header, build_huffman_segment, build_jfif_header, build_quantization_segment, build_scan_header, Component, JpegEncoder, PixelDensity, DCCLASS, LUMADESTINATION, STD_LUMA_DC_CODE_LENGTHS, STD_LUMA_DC_VALUES, }; fn decode(encoded: &[u8]) -> Vec { let decoder = JpegDecoder::new(Cursor::new(encoded)).expect("Could not decode image"); let mut decoded = vec![0; decoder.total_bytes() as usize]; decoder .read_image(&mut decoded) .expect("Could not decode image"); decoded } #[test] fn roundtrip_sanity_check() { // create a 1x1 8-bit image buffer containing a single red pixel let img = [255u8, 0, 0]; // encode it into a memory buffer let mut encoded_img = Vec::new(); { let encoder = JpegEncoder::new_with_quality(&mut encoded_img, 100); encoder .write_image(&img, 1, 1, ColorType::Rgb8) .expect("Could not encode image"); } // decode it from the memory buffer { let decoded = decode(&encoded_img); // note that, even with the encode quality set to 100, we do not get the same image // back. Therefore, we're going to assert that it's at least red-ish: assert_eq!(3, decoded.len()); assert!(decoded[0] > 0x80); assert!(decoded[1] < 0x80); assert!(decoded[2] < 0x80); } } #[test] fn grayscale_roundtrip_sanity_check() { // create a 2x2 8-bit image buffer containing a white diagonal let img = [255u8, 0, 0, 255]; // encode it into a memory buffer let mut encoded_img = Vec::new(); { let encoder = JpegEncoder::new_with_quality(&mut encoded_img, 100); encoder .write_image(&img[..], 2, 2, ColorType::L8) .expect("Could not encode image"); } // decode it from the memory buffer { let decoded = decode(&encoded_img); // note that, even with the encode quality set to 100, we do not get the same image // back. Therefore, we're going to assert that the diagonal is at least white-ish: assert_eq!(4, decoded.len()); assert!(decoded[0] > 0x80); assert!(decoded[1] < 0x80); assert!(decoded[2] < 0x80); assert!(decoded[3] > 0x80); } } #[test] fn jfif_header_density_check() { let mut buffer = Vec::new(); build_jfif_header(&mut buffer, PixelDensity::dpi(300)); assert_eq!( buffer, vec![ b'J', b'F', b'I', b'F', 0, 1, 2, // JFIF version 1.2 1, // density is in dpi 300u16.to_be_bytes()[0], 300u16.to_be_bytes()[1], 300u16.to_be_bytes()[0], 300u16.to_be_bytes()[1], 0, 0, // No thumbnail ] ); } #[test] fn test_image_too_large() { // JPEG cannot encode images larger than 65,535×65,535 // create a 65,536×1 8-bit black image buffer let img = [0; 65_536]; // Try to encode an image that is too large let mut encoded = Vec::new(); let encoder = JpegEncoder::new_with_quality(&mut encoded, 100); let result = encoder.write_image(&img, 65_536, 1, ColorType::L8); match result { Err(ImageError::Parameter(err)) => { assert_eq!(err.kind(), DimensionMismatch) } other => { assert!( false, "Encoding an image that is too large should return a DimensionError \ it returned {:?} instead", other ) } } } #[test] fn test_build_jfif_header() { let mut buf = vec![]; let density = PixelDensity::dpi(100); build_jfif_header(&mut buf, density); assert_eq!( buf, [0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x02, 0x01, 0, 100, 0, 100, 0, 0] ); } #[test] fn test_build_frame_header() { let mut buf = vec![]; let components = vec![ Component { id: 1, h: 1, v: 1, tq: 5, dc_table: 5, ac_table: 5, _dc_pred: 0, }, Component { id: 2, h: 1, v: 1, tq: 4, dc_table: 4, ac_table: 4, _dc_pred: 0, }, ]; build_frame_header(&mut buf, 5, 100, 150, &components); assert_eq!( buf, [5, 0, 150, 0, 100, 2, 1, 1 << 4 | 1, 5, 2, 1 << 4 | 1, 4] ); } #[test] fn test_build_scan_header() { let mut buf = vec![]; let components = vec![ Component { id: 1, h: 1, v: 1, tq: 5, dc_table: 5, ac_table: 5, _dc_pred: 0, }, Component { id: 2, h: 1, v: 1, tq: 4, dc_table: 4, ac_table: 4, _dc_pred: 0, }, ]; build_scan_header(&mut buf, &components); assert_eq!(buf, [2, 1, 5 << 4 | 5, 2, 4 << 4 | 4, 0, 63, 0]); } #[test] fn test_build_huffman_segment() { let mut buf = vec![]; build_huffman_segment( &mut buf, DCCLASS, LUMADESTINATION, &STD_LUMA_DC_CODE_LENGTHS, &STD_LUMA_DC_VALUES, ); assert_eq!( buf, vec![ 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ] ); } #[test] fn test_build_quantization_segment() { let mut buf = vec![]; let qtable = [0u8; 64]; build_quantization_segment(&mut buf, 8, 1, &qtable); let mut expected = vec![]; expected.push(0 << 4 | 1); expected.extend_from_slice(&[0; 64]); assert_eq!(buf, expected) } #[cfg(feature = "benchmarks")] #[bench] fn bench_jpeg_encoder_new(b: &mut Bencher) { b.iter(|| { let mut y = vec![]; let x = JpegEncoder::new(&mut y); }) } } image-0.24.7/src/codecs/jpeg/entropy.rs000064400000000000000000000030401046102023000160170ustar 00000000000000/// Given an array containing the number of codes of each code length, /// this function generates the huffman codes lengths and their respective /// code lengths as specified by the JPEG spec. const fn derive_codes_and_sizes(bits: &[u8; 16]) -> ([u8; 256], [u16; 256]) { let mut huffsize = [0u8; 256]; let mut huffcode = [0u16; 256]; let mut k = 0; // Annex C.2 // Figure C.1 // Generate table of individual code lengths let mut i = 0; while i < 16 { let mut j = 0; while j < bits[i as usize] { huffsize[k] = i + 1; k += 1; j += 1; } i += 1; } huffsize[k] = 0; // Annex C.2 // Figure C.2 // Generate table of huffman codes k = 0; let mut code = 0u16; let mut size = huffsize[0]; while huffsize[k] != 0 { huffcode[k] = code; code += 1; k += 1; if huffsize[k] == size { continue; } // FIXME there is something wrong with this code let diff = huffsize[k].wrapping_sub(size); code = if diff < 16 { code << diff as usize } else { 0 }; size = size.wrapping_add(diff); } (huffsize, huffcode) } pub(crate) const fn build_huff_lut_const(bits: &[u8; 16], huffval: &[u8]) -> [(u8, u16); 256] { let mut lut = [(17u8, 0u16); 256]; let (huffsize, huffcode) = derive_codes_and_sizes(bits); let mut i = 0; while i < huffval.len() { lut[huffval[i] as usize] = (huffsize[i], huffcode[i]); i += 1; } lut } image-0.24.7/src/codecs/jpeg/mod.rs000064400000000000000000000007171046102023000151060ustar 00000000000000//! Decoding and Encoding of JPEG Images //! //! JPEG (Joint Photographic Experts Group) is an image format that supports lossy compression. //! This module implements the Baseline JPEG standard. //! //! # Related Links //! * - The JPEG specification //! pub use self::decoder::JpegDecoder; pub use self::encoder::{JpegEncoder, PixelDensity, PixelDensityUnit}; mod decoder; mod encoder; mod entropy; mod transform; image-0.24.7/src/codecs/jpeg/transform.rs000064400000000000000000000173311046102023000163420ustar 00000000000000/* fdct is a Rust translation of jfdctint.c from the Independent JPEG Group's libjpeg version 9a obtained from http://www.ijg.org/files/jpegsr9a.zip It comes with the following conditions of distribution and use: In plain English: 1. We don't promise that this software works. (But if you find any bugs, please let us know!) 2. You can use this software for whatever you want. You don't have to pay us. 3. You may not pretend that you wrote this software. If you use it in a program, you must acknowledge somewhere in your documentation that you've used the IJG code. In legalese: The authors make NO WARRANTY or representation, either express or implied, with respect to this software, its quality, accuracy, merchantability, or fitness for a particular purpose. This software is provided "AS IS", and you, its user, assume the entire risk as to its quality and accuracy. This software is copyright (C) 1991-2014, Thomas G. Lane, Guido Vollbeding. All Rights Reserved except as specified below. Permission is hereby granted to use, copy, modify, and distribute this software (or portions thereof) for any purpose, without fee, subject to these conditions: (1) If any part of the source code for this software is distributed, then this README file must be included, with this copyright and no-warranty notice unaltered; and any additions, deletions, or changes to the original files must be clearly indicated in accompanying documentation. (2) If only executable code is distributed, then the accompanying documentation must state that "this software is based in part on the work of the Independent JPEG Group". (3) Permission for use of this software is granted only if the user accepts full responsibility for any undesirable consequences; the authors accept NO LIABILITY for damages of any kind. These conditions apply to any software derived from or based on the IJG code, not just to the unmodified library. If you use our work, you ought to acknowledge us. Permission is NOT granted for the use of any IJG author's name or company name in advertising or publicity relating to this software or products derived from it. This software may be referred to only as "the Independent JPEG Group's software". We specifically permit and encourage the use of this software as the basis of commercial products, provided that all warranty or liability claims are assumed by the product vendor. */ static CONST_BITS: i32 = 13; static PASS1_BITS: i32 = 2; static FIX_0_298631336: i32 = 2446; static FIX_0_390180644: i32 = 3196; static FIX_0_541196100: i32 = 4433; static FIX_0_765366865: i32 = 6270; static FIX_0_899976223: i32 = 7373; static FIX_1_175875602: i32 = 9633; static FIX_1_501321110: i32 = 12_299; static FIX_1_847759065: i32 = 15_137; static FIX_1_961570560: i32 = 16_069; static FIX_2_053119869: i32 = 16_819; static FIX_2_562915447: i32 = 20_995; static FIX_3_072711026: i32 = 25_172; pub(crate) fn fdct(samples: &[u8; 64], coeffs: &mut [i32; 64]) { // Pass 1: process rows. // Results are scaled by sqrt(8) compared to a true DCT // furthermore we scale the results by 2**PASS1_BITS for y in 0usize..8 { let y0 = y * 8; // Even part let t0 = i32::from(samples[y0]) + i32::from(samples[y0 + 7]); let t1 = i32::from(samples[y0 + 1]) + i32::from(samples[y0 + 6]); let t2 = i32::from(samples[y0 + 2]) + i32::from(samples[y0 + 5]); let t3 = i32::from(samples[y0 + 3]) + i32::from(samples[y0 + 4]); let t10 = t0 + t3; let t12 = t0 - t3; let t11 = t1 + t2; let t13 = t1 - t2; let t0 = i32::from(samples[y0]) - i32::from(samples[y0 + 7]); let t1 = i32::from(samples[y0 + 1]) - i32::from(samples[y0 + 6]); let t2 = i32::from(samples[y0 + 2]) - i32::from(samples[y0 + 5]); let t3 = i32::from(samples[y0 + 3]) - i32::from(samples[y0 + 4]); // Apply unsigned -> signed conversion coeffs[y0] = (t10 + t11 - 8 * 128) << PASS1_BITS as usize; coeffs[y0 + 4] = (t10 - t11) << PASS1_BITS as usize; let mut z1 = (t12 + t13) * FIX_0_541196100; // Add fudge factor here for final descale z1 += 1 << (CONST_BITS - PASS1_BITS - 1) as usize; coeffs[y0 + 2] = (z1 + t12 * FIX_0_765366865) >> (CONST_BITS - PASS1_BITS) as usize; coeffs[y0 + 6] = (z1 - t13 * FIX_1_847759065) >> (CONST_BITS - PASS1_BITS) as usize; // Odd part let t12 = t0 + t2; let t13 = t1 + t3; let mut z1 = (t12 + t13) * FIX_1_175875602; // Add fudge factor here for final descale z1 += 1 << (CONST_BITS - PASS1_BITS - 1) as usize; let mut t12 = t12 * (-FIX_0_390180644); let mut t13 = t13 * (-FIX_1_961570560); t12 += z1; t13 += z1; let z1 = (t0 + t3) * (-FIX_0_899976223); let mut t0 = t0 * FIX_1_501321110; let mut t3 = t3 * FIX_0_298631336; t0 += z1 + t12; t3 += z1 + t13; let z1 = (t1 + t2) * (-FIX_2_562915447); let mut t1 = t1 * FIX_3_072711026; let mut t2 = t2 * FIX_2_053119869; t1 += z1 + t13; t2 += z1 + t12; coeffs[y0 + 1] = t0 >> (CONST_BITS - PASS1_BITS) as usize; coeffs[y0 + 3] = t1 >> (CONST_BITS - PASS1_BITS) as usize; coeffs[y0 + 5] = t2 >> (CONST_BITS - PASS1_BITS) as usize; coeffs[y0 + 7] = t3 >> (CONST_BITS - PASS1_BITS) as usize; } // Pass 2: process columns // We remove the PASS1_BITS scaling but leave the results scaled up an // overall factor of 8 for x in (0usize..8).rev() { // Even part let t0 = coeffs[x] + coeffs[x + 8 * 7]; let t1 = coeffs[x + 8] + coeffs[x + 8 * 6]; let t2 = coeffs[x + 8 * 2] + coeffs[x + 8 * 5]; let t3 = coeffs[x + 8 * 3] + coeffs[x + 8 * 4]; // Add fudge factor here for final descale let t10 = t0 + t3 + (1 << (PASS1_BITS - 1) as usize); let t12 = t0 - t3; let t11 = t1 + t2; let t13 = t1 - t2; let t0 = coeffs[x] - coeffs[x + 8 * 7]; let t1 = coeffs[x + 8] - coeffs[x + 8 * 6]; let t2 = coeffs[x + 8 * 2] - coeffs[x + 8 * 5]; let t3 = coeffs[x + 8 * 3] - coeffs[x + 8 * 4]; coeffs[x] = (t10 + t11) >> PASS1_BITS as usize; coeffs[x + 8 * 4] = (t10 - t11) >> PASS1_BITS as usize; let mut z1 = (t12 + t13) * FIX_0_541196100; // Add fudge factor here for final descale z1 += 1 << (CONST_BITS + PASS1_BITS - 1) as usize; coeffs[x + 8 * 2] = (z1 + t12 * FIX_0_765366865) >> (CONST_BITS + PASS1_BITS) as usize; coeffs[x + 8 * 6] = (z1 - t13 * FIX_1_847759065) >> (CONST_BITS + PASS1_BITS) as usize; // Odd part let t12 = t0 + t2; let t13 = t1 + t3; let mut z1 = (t12 + t13) * FIX_1_175875602; // Add fudge factor here for final descale z1 += 1 << (CONST_BITS - PASS1_BITS - 1) as usize; let mut t12 = t12 * (-FIX_0_390180644); let mut t13 = t13 * (-FIX_1_961570560); t12 += z1; t13 += z1; let z1 = (t0 + t3) * (-FIX_0_899976223); let mut t0 = t0 * FIX_1_501321110; let mut t3 = t3 * FIX_0_298631336; t0 += z1 + t12; t3 += z1 + t13; let z1 = (t1 + t2) * (-FIX_2_562915447); let mut t1 = t1 * FIX_3_072711026; let mut t2 = t2 * FIX_2_053119869; t1 += z1 + t13; t2 += z1 + t12; coeffs[x + 8] = t0 >> (CONST_BITS + PASS1_BITS) as usize; coeffs[x + 8 * 3] = t1 >> (CONST_BITS + PASS1_BITS) as usize; coeffs[x + 8 * 5] = t2 >> (CONST_BITS + PASS1_BITS) as usize; coeffs[x + 8 * 7] = t3 >> (CONST_BITS + PASS1_BITS) as usize; } } image-0.24.7/src/codecs/openexr.rs000064400000000000000000000561521046102023000150660ustar 00000000000000//! Decoding of OpenEXR (.exr) Images //! //! OpenEXR is an image format that is widely used, especially in VFX, //! because it supports lossless and lossy compression for float data. //! //! This decoder only supports RGB and RGBA images. //! If an image does not contain alpha information, //! it is defaulted to `1.0` (no transparency). //! //! # Related Links //! * - The OpenEXR reference. //! //! //! Current limitations (July 2021): //! - only pixel type `Rgba32F` and `Rgba16F` are supported //! - only non-deep rgb/rgba files supported, no conversion from/to YCbCr or similar //! - only the first non-deep rgb layer is used //! - only the largest mip map level is used //! - pixels outside display window are lost //! - meta data is lost //! - dwaa/dwab compressed images not supported yet by the exr library //! - (chroma) subsampling not supported yet by the exr library use exr::prelude::*; use crate::error::{DecodingError, EncodingError, ImageFormatHint}; use crate::image::decoder_to_vec; use crate::{ ColorType, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageFormat, ImageResult, Progress, }; use std::convert::TryInto; use std::io::{Cursor, Read, Seek, Write}; /// An OpenEXR decoder. Immediately reads the meta data from the file. #[derive(Debug)] pub struct OpenExrDecoder { exr_reader: exr::block::reader::Reader, // select a header that is rgb and not deep header_index: usize, // decode either rgb or rgba. // can be specified to include or discard alpha channels. // if none, the alpha channel will only be allocated where the file contains data for it. alpha_preference: Option, alpha_present_in_file: bool, } impl OpenExrDecoder { /// Create a decoder. Consumes the first few bytes of the source to extract image dimensions. /// Assumes the reader is buffered. In most cases, /// you should wrap your reader in a `BufReader` for best performance. /// Loads an alpha channel if the file has alpha samples. /// Use `with_alpha_preference` if you want to load or not load alpha unconditionally. pub fn new(source: R) -> ImageResult { Self::with_alpha_preference(source, None) } /// Create a decoder. Consumes the first few bytes of the source to extract image dimensions. /// Assumes the reader is buffered. In most cases, /// you should wrap your reader in a `BufReader` for best performance. /// If alpha preference is specified, an alpha channel will /// always be present or always be not present in the returned image. /// If alpha preference is none, the alpha channel will only be returned if it is found in the file. pub fn with_alpha_preference(source: R, alpha_preference: Option) -> ImageResult { // read meta data, then wait for further instructions, keeping the file open and ready let exr_reader = exr::block::read(source, false).map_err(to_image_err)?; let header_index = exr_reader .headers() .iter() .position(|header| { // check if r/g/b exists in the channels let has_rgb = ["R", "G", "B"] .iter() .all(|&required| // alpha will be optional header.channels.find_index_of_channel(&Text::from(required)).is_some()); // we currently dont support deep images, or images with other color spaces than rgb !header.deep && has_rgb }) .ok_or_else(|| { ImageError::Decoding(DecodingError::new( ImageFormatHint::Exact(ImageFormat::OpenExr), "image does not contain non-deep rgb channels", )) })?; let has_alpha = exr_reader.headers()[header_index] .channels .find_index_of_channel(&Text::from("A")) .is_some(); Ok(Self { alpha_preference, exr_reader, header_index, alpha_present_in_file: has_alpha, }) } // does not leak exrs-specific meta data into public api, just does it for this module fn selected_exr_header(&self) -> &exr::meta::header::Header { &self.exr_reader.meta_data().headers[self.header_index] } } impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for OpenExrDecoder { type Reader = Cursor>; fn dimensions(&self) -> (u32, u32) { let size = self .selected_exr_header() .shared_attributes .display_window .size; (size.width() as u32, size.height() as u32) } fn color_type(&self) -> ColorType { let returns_alpha = self.alpha_preference.unwrap_or(self.alpha_present_in_file); if returns_alpha { ColorType::Rgba32F } else { ColorType::Rgb32F } } fn original_color_type(&self) -> ExtendedColorType { if self.alpha_present_in_file { ExtendedColorType::Rgba32F } else { ExtendedColorType::Rgb32F } } /// Use `read_image` instead if possible, /// as this method creates a whole new buffer just to contain the entire image. fn into_reader(self) -> ImageResult { Ok(Cursor::new(decoder_to_vec(self)?)) } fn scanline_bytes(&self) -> u64 { // we cannot always read individual scan lines for every file, // as the tiles or lines in the file could be in random or reversed order. // therefore we currently read all lines at once // Todo: optimize for specific exr.line_order? self.total_bytes() } // reads with or without alpha, depending on `self.alpha_preference` and `self.alpha_present_in_file` fn read_image_with_progress( self, unaligned_bytes: &mut [u8], progress_callback: F, ) -> ImageResult<()> { let blocks_in_header = self.selected_exr_header().chunk_count as u64; let channel_count = self.color_type().channel_count() as usize; let display_window = self.selected_exr_header().shared_attributes.display_window; let data_window_offset = self.selected_exr_header().own_attributes.layer_position - display_window.position; { // check whether the buffer is large enough for the dimensions of the file let (width, height) = self.dimensions(); let bytes_per_pixel = self.color_type().bytes_per_pixel() as usize; let expected_byte_count = (width as usize) .checked_mul(height as usize) .and_then(|size| size.checked_mul(bytes_per_pixel)); // if the width and height does not match the length of the bytes, the arguments are invalid let has_invalid_size_or_overflowed = expected_byte_count .map(|expected_byte_count| unaligned_bytes.len() != expected_byte_count) // otherwise, size calculation overflowed, is bigger than memory, // therefore data is too small, so it is invalid. .unwrap_or(true); if has_invalid_size_or_overflowed { panic!("byte buffer not large enough for the specified dimensions and f32 pixels"); } } let result = read() .no_deep_data() .largest_resolution_level() .rgba_channels( move |_size, _channels| vec![0_f32; display_window.size.area() * channel_count], move |buffer, index_in_data_window, (r, g, b, a_or_1): (f32, f32, f32, f32)| { let index_in_display_window = index_in_data_window.to_i32() + data_window_offset; // only keep pixels inside the data window // TODO filter chunks based on this if index_in_display_window.x() >= 0 && index_in_display_window.y() >= 0 && index_in_display_window.x() < display_window.size.width() as i32 && index_in_display_window.y() < display_window.size.height() as i32 { let index_in_display_window = index_in_display_window.to_usize("index bug").unwrap(); let first_f32_index = index_in_display_window.flat_index_for_size(display_window.size); buffer[first_f32_index * channel_count ..(first_f32_index + 1) * channel_count] .copy_from_slice(&[r, g, b, a_or_1][0..channel_count]); // TODO white point chromaticities + srgb/linear conversion? } }, ) .first_valid_layer() // TODO select exact layer by self.header_index? .all_attributes() .on_progress(|progress| { progress_callback( Progress::new( (progress * blocks_in_header as f64) as u64, blocks_in_header, ), // TODO precision errors? ); }) .from_chunks(self.exr_reader) .map_err(to_image_err)?; // TODO this copy is strictly not necessary, but the exr api is a little too simple for reading into a borrowed target slice // this cast is safe and works with any alignment, as bytes are copied, and not f32 values. // note: buffer slice length is checked in the beginning of this function and will be correct at this point unaligned_bytes.copy_from_slice(bytemuck::cast_slice( result.layer_data.channel_data.pixels.as_slice(), )); Ok(()) } } /// Write a raw byte buffer of pixels, /// returning an Error if it has an invalid length. /// /// Assumes the writer is buffered. In most cases, /// you should wrap your writer in a `BufWriter` for best performance. // private. access via `OpenExrEncoder` fn write_buffer( mut buffered_write: impl Write + Seek, unaligned_bytes: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()> { let width = width as usize; let height = height as usize; { // check whether the buffer is large enough for the specified dimensions let expected_byte_count = width .checked_mul(height) .and_then(|size| size.checked_mul(color_type.bytes_per_pixel() as usize)); // if the width and height does not match the length of the bytes, the arguments are invalid let has_invalid_size_or_overflowed = expected_byte_count .map(|expected_byte_count| unaligned_bytes.len() < expected_byte_count) // otherwise, size calculation overflowed, is bigger than memory, // therefore data is too small, so it is invalid. .unwrap_or(true); if has_invalid_size_or_overflowed { return Err(ImageError::Encoding(EncodingError::new( ImageFormatHint::Exact(ImageFormat::OpenExr), "byte buffer not large enough for the specified dimensions and f32 pixels", ))); } } // bytes might be unaligned so we cannot cast the whole thing, instead lookup each f32 individually let lookup_f32 = move |f32_index: usize| { let unaligned_f32_bytes_slice = &unaligned_bytes[f32_index * 4..(f32_index + 1) * 4]; let f32_bytes_array = unaligned_f32_bytes_slice .try_into() .expect("indexing error"); f32::from_ne_bytes(f32_bytes_array) }; match color_type { ColorType::Rgb32F => { exr::prelude::Image // TODO compression method zip?? ::from_channels( (width, height), SpecificChannels::rgb(|pixel: Vec2| { let pixel_index = 3 * pixel.flat_index_for_size(Vec2(width, height)); ( lookup_f32(pixel_index), lookup_f32(pixel_index + 1), lookup_f32(pixel_index + 2), ) }), ) .write() // .on_progress(|progress| todo!()) .to_buffered(&mut buffered_write) .map_err(to_image_err)?; } ColorType::Rgba32F => { exr::prelude::Image // TODO compression method zip?? ::from_channels( (width, height), SpecificChannels::rgba(|pixel: Vec2| { let pixel_index = 4 * pixel.flat_index_for_size(Vec2(width, height)); ( lookup_f32(pixel_index), lookup_f32(pixel_index + 1), lookup_f32(pixel_index + 2), lookup_f32(pixel_index + 3), ) }), ) .write() // .on_progress(|progress| todo!()) .to_buffered(&mut buffered_write) .map_err(to_image_err)?; } // TODO other color types and channel types unsupported_color_type => { return Err(ImageError::Encoding(EncodingError::new( ImageFormatHint::Exact(ImageFormat::OpenExr), format!( "writing color type {:?} not yet supported", unsupported_color_type ), ))) } } Ok(()) } // TODO is this struct and trait actually used anywhere? /// A thin wrapper that implements `ImageEncoder` for OpenEXR images. Will behave like `image::codecs::openexr::write_buffer`. #[derive(Debug)] pub struct OpenExrEncoder(W); impl OpenExrEncoder { /// Create an `ImageEncoder`. Does not write anything yet. Writing later will behave like `image::codecs::openexr::write_buffer`. // use constructor, not public field, for future backwards-compatibility pub fn new(write: W) -> Self { Self(write) } } impl ImageEncoder for OpenExrEncoder where W: Write + Seek, { /// Writes the complete image. /// /// Returns an Error if it has an invalid length. /// Assumes the writer is buffered. In most cases, /// you should wrap your writer in a `BufWriter` for best performance. fn write_image( self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()> { write_buffer(self.0, buf, width, height, color_type) } } fn to_image_err(exr_error: Error) -> ImageError { ImageError::Decoding(DecodingError::new( ImageFormatHint::Exact(ImageFormat::OpenExr), exr_error.to_string(), )) } #[cfg(test)] mod test { use super::*; use std::io::BufReader; use std::path::{Path, PathBuf}; use crate::buffer_::{Rgb32FImage, Rgba32FImage}; use crate::error::{LimitError, LimitErrorKind}; use crate::{ImageBuffer, Rgb, Rgba}; const BASE_PATH: &[&str] = &[".", "tests", "images", "exr"]; /// Write an `Rgb32FImage`. /// Assumes the writer is buffered. In most cases, /// you should wrap your writer in a `BufWriter` for best performance. fn write_rgb_image(write: impl Write + Seek, image: &Rgb32FImage) -> ImageResult<()> { write_buffer( write, bytemuck::cast_slice(image.as_raw().as_slice()), image.width(), image.height(), ColorType::Rgb32F, ) } /// Write an `Rgba32FImage`. /// Assumes the writer is buffered. In most cases, /// you should wrap your writer in a `BufWriter` for best performance. fn write_rgba_image(write: impl Write + Seek, image: &Rgba32FImage) -> ImageResult<()> { write_buffer( write, bytemuck::cast_slice(image.as_raw().as_slice()), image.width(), image.height(), ColorType::Rgba32F, ) } /// Read the file from the specified path into an `Rgba32FImage`. fn read_as_rgba_image_from_file(path: impl AsRef) -> ImageResult { read_as_rgba_image(BufReader::new(std::fs::File::open(path)?)) } /// Read the file from the specified path into an `Rgb32FImage`. fn read_as_rgb_image_from_file(path: impl AsRef) -> ImageResult { read_as_rgb_image(BufReader::new(std::fs::File::open(path)?)) } /// Read the file from the specified path into an `Rgb32FImage`. fn read_as_rgb_image(read: impl Read + Seek) -> ImageResult { let decoder = OpenExrDecoder::with_alpha_preference(read, Some(false))?; let (width, height) = decoder.dimensions(); let buffer: Vec = decoder_to_vec(decoder)?; ImageBuffer::from_raw(width, height, buffer) // this should be the only reason for the "from raw" call to fail, // even though such a large allocation would probably cause an error much earlier .ok_or_else(|| { ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) }) } /// Read the file from the specified path into an `Rgba32FImage`. fn read_as_rgba_image(read: impl Read + Seek) -> ImageResult { let decoder = OpenExrDecoder::with_alpha_preference(read, Some(true))?; let (width, height) = decoder.dimensions(); let buffer: Vec = decoder_to_vec(decoder)?; ImageBuffer::from_raw(width, height, buffer) // this should be the only reason for the "from raw" call to fail, // even though such a large allocation would probably cause an error much earlier .ok_or_else(|| { ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) }) } #[test] fn compare_exr_hdr() { if cfg!(not(feature = "hdr")) { eprintln!("warning: to run all the openexr tests, activate the hdr feature flag"); } #[cfg(feature = "hdr")] { let folder = BASE_PATH.iter().collect::(); let reference_path = folder.clone().join("overexposed gradient.hdr"); let exr_path = folder .clone() .join("overexposed gradient - data window equals display window.exr"); let hdr: Vec> = crate::codecs::hdr::HdrDecoder::new(std::io::BufReader::new( std::fs::File::open(&reference_path).unwrap(), )) .unwrap() .read_image_hdr() .unwrap(); let exr_pixels: Rgb32FImage = read_as_rgb_image_from_file(exr_path).unwrap(); assert_eq!( exr_pixels.dimensions().0 * exr_pixels.dimensions().1, hdr.len() as u32 ); for (expected, found) in hdr.iter().zip(exr_pixels.pixels()) { for (expected, found) in expected.0.iter().zip(found.0.iter()) { // the large tolerance seems to be caused by // the RGBE u8x4 pixel quantization of the hdr image format assert!( (expected - found).abs() < 0.1, "expected {}, found {}", expected, found ); } } } } #[test] fn roundtrip_rgba() { let mut next_random = vec![1.0, 0.0, -1.0, -3.14, 27.0, 11.0, 31.0] .into_iter() .cycle(); let mut next_random = move || next_random.next().unwrap(); let generated_image: Rgba32FImage = ImageBuffer::from_fn(9, 31, |_x, _y| { Rgba([next_random(), next_random(), next_random(), next_random()]) }); let mut bytes = vec![]; write_rgba_image(Cursor::new(&mut bytes), &generated_image).unwrap(); let decoded_image = read_as_rgba_image(Cursor::new(bytes)).unwrap(); debug_assert_eq!(generated_image, decoded_image); } #[test] fn roundtrip_rgb() { let mut next_random = vec![1.0, 0.0, -1.0, -3.14, 27.0, 11.0, 31.0] .into_iter() .cycle(); let mut next_random = move || next_random.next().unwrap(); let generated_image: Rgb32FImage = ImageBuffer::from_fn(9, 31, |_x, _y| { Rgb([next_random(), next_random(), next_random()]) }); let mut bytes = vec![]; write_rgb_image(Cursor::new(&mut bytes), &generated_image).unwrap(); let decoded_image = read_as_rgb_image(Cursor::new(bytes)).unwrap(); debug_assert_eq!(generated_image, decoded_image); } #[test] fn compare_rgba_rgb() { let exr_path = BASE_PATH .iter() .collect::() .join("overexposed gradient - data window equals display window.exr"); let rgb: Rgb32FImage = read_as_rgb_image_from_file(&exr_path).unwrap(); let rgba: Rgba32FImage = read_as_rgba_image_from_file(&exr_path).unwrap(); assert_eq!(rgba.dimensions(), rgb.dimensions()); for (Rgb(rgb), Rgba(rgba)) in rgb.pixels().zip(rgba.pixels()) { assert_eq!(rgb, &rgba[..3]); } } #[test] fn compare_cropped() { // like in photoshop, exr images may have layers placed anywhere in a canvas. // we don't want to load the pixels from the layer, but we want to load the pixels from the canvas. // a layer might be smaller than the canvas, in that case the canvas should be transparent black // where no layer was covering it. a layer might also be larger than the canvas, // these pixels should be discarded. // // in this test we want to make sure that an // auto-cropped image will be reproduced to the original. let exr_path = BASE_PATH.iter().collect::(); let original = exr_path.clone().join("cropping - uncropped original.exr"); let cropped = exr_path .clone() .join("cropping - data window differs display window.exr"); // smoke-check that the exr files are actually not the same { let original_exr = read_first_flat_layer_from_file(&original).unwrap(); let cropped_exr = read_first_flat_layer_from_file(&cropped).unwrap(); assert_eq!( original_exr.attributes.display_window, cropped_exr.attributes.display_window ); assert_ne!( original_exr.layer_data.attributes.layer_position, cropped_exr.layer_data.attributes.layer_position ); assert_ne!(original_exr.layer_data.size, cropped_exr.layer_data.size); } // check that they result in the same image let original: Rgba32FImage = read_as_rgba_image_from_file(&original).unwrap(); let cropped: Rgba32FImage = read_as_rgba_image_from_file(&cropped).unwrap(); assert_eq!(original.dimensions(), cropped.dimensions()); // the following is not a simple assert_eq, as in case of an error, // the whole image would be printed to the console, which takes forever assert!(original.pixels().zip(cropped.pixels()).all(|(a, b)| a == b)); } } image-0.24.7/src/codecs/png.rs000064400000000000000000000700461046102023000141700ustar 00000000000000//! Decoding and Encoding of PNG Images //! //! PNG (Portable Network Graphics) is an image format that supports lossless compression. //! //! # Related Links //! * - The PNG Specification //! use std::convert::TryFrom; use std::fmt; use std::io::{self, Read, Write}; use num_rational::Ratio; use png::{BlendOp, DisposeOp}; use crate::animation::{Delay, Frame, Frames}; use crate::color::{Blend, ColorType, ExtendedColorType}; use crate::error::{ DecodingError, EncodingError, ImageError, ImageResult, LimitError, LimitErrorKind, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{AnimationDecoder, ImageDecoder, ImageEncoder, ImageFormat}; use crate::io::Limits; use crate::{DynamicImage, GenericImage, ImageBuffer, Luma, LumaA, Rgb, Rgba, RgbaImage}; // http://www.w3.org/TR/PNG-Structure.html // The first eight bytes of a PNG file always contain the following (decimal) values: pub(crate) const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10]; /// Png Reader /// /// This reader will try to read the png one row at a time, /// however for interlaced png files this is not possible and /// these are therefore read at once. pub struct PngReader { reader: png::Reader, buffer: Vec, index: usize, } impl PngReader { fn new(mut reader: png::Reader) -> ImageResult> { let len = reader.output_buffer_size(); // Since interlaced images do not come in // scanline order it is almost impossible to // read them in a streaming fashion, however // this shouldn't be a too big of a problem // as most interlaced images should fit in memory. let buffer = if reader.info().interlaced { let mut buffer = vec![0; len]; reader .next_frame(&mut buffer) .map_err(ImageError::from_png)?; buffer } else { Vec::new() }; Ok(PngReader { reader, buffer, index: 0, }) } } impl Read for PngReader { fn read(&mut self, mut buf: &mut [u8]) -> io::Result { // io::Write::write for slice cannot fail let readed = buf.write(&self.buffer[self.index..]).unwrap(); let mut bytes = readed; self.index += readed; while self.index >= self.buffer.len() { match self.reader.next_row()? { Some(row) => { // Faster to copy directly to external buffer let readed = buf.write(row.data()).unwrap(); bytes += readed; self.buffer = row.data()[readed..].to_owned(); self.index = 0; } None => return Ok(bytes), } } Ok(bytes) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { let mut bytes = self.buffer.len(); if buf.is_empty() { std::mem::swap(&mut self.buffer, buf); } else { buf.extend_from_slice(&self.buffer); self.buffer.clear(); } self.index = 0; while let Some(row) = self.reader.next_row()? { buf.extend_from_slice(row.data()); bytes += row.data().len(); } Ok(bytes) } } /// PNG decoder pub struct PngDecoder { color_type: ColorType, reader: png::Reader, } impl PngDecoder { /// Creates a new decoder that decodes from the stream ```r``` pub fn new(r: R) -> ImageResult> { Self::with_limits(r, Limits::default()) } /// Creates a new decoder that decodes from the stream ```r``` with the given limits. pub fn with_limits(r: R, limits: Limits) -> ImageResult> { limits.check_support(&crate::io::LimitSupport::default())?; let max_bytes = usize::try_from(limits.max_alloc.unwrap_or(u64::MAX)).unwrap_or(usize::MAX); let mut decoder = png::Decoder::new_with_limits(r, png::Limits { bytes: max_bytes }); let info = decoder.read_header_info().map_err(ImageError::from_png)?; limits.check_dimensions(info.width, info.height)?; // By default the PNG decoder will scale 16 bpc to 8 bpc, so custom // transformations must be set. EXPAND preserves the default behavior // expanding bpc < 8 to 8 bpc. decoder.set_transformations(png::Transformations::EXPAND); let reader = decoder.read_info().map_err(ImageError::from_png)?; let (color_type, bits) = reader.output_color_type(); let color_type = match (color_type, bits) { (png::ColorType::Grayscale, png::BitDepth::Eight) => ColorType::L8, (png::ColorType::Grayscale, png::BitDepth::Sixteen) => ColorType::L16, (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight) => ColorType::La8, (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen) => ColorType::La16, (png::ColorType::Rgb, png::BitDepth::Eight) => ColorType::Rgb8, (png::ColorType::Rgb, png::BitDepth::Sixteen) => ColorType::Rgb16, (png::ColorType::Rgba, png::BitDepth::Eight) => ColorType::Rgba8, (png::ColorType::Rgba, png::BitDepth::Sixteen) => ColorType::Rgba16, (png::ColorType::Grayscale, png::BitDepth::One) => { return Err(unsupported_color(ExtendedColorType::L1)) } (png::ColorType::GrayscaleAlpha, png::BitDepth::One) => { return Err(unsupported_color(ExtendedColorType::La1)) } (png::ColorType::Rgb, png::BitDepth::One) => { return Err(unsupported_color(ExtendedColorType::Rgb1)) } (png::ColorType::Rgba, png::BitDepth::One) => { return Err(unsupported_color(ExtendedColorType::Rgba1)) } (png::ColorType::Grayscale, png::BitDepth::Two) => { return Err(unsupported_color(ExtendedColorType::L2)) } (png::ColorType::GrayscaleAlpha, png::BitDepth::Two) => { return Err(unsupported_color(ExtendedColorType::La2)) } (png::ColorType::Rgb, png::BitDepth::Two) => { return Err(unsupported_color(ExtendedColorType::Rgb2)) } (png::ColorType::Rgba, png::BitDepth::Two) => { return Err(unsupported_color(ExtendedColorType::Rgba2)) } (png::ColorType::Grayscale, png::BitDepth::Four) => { return Err(unsupported_color(ExtendedColorType::L4)) } (png::ColorType::GrayscaleAlpha, png::BitDepth::Four) => { return Err(unsupported_color(ExtendedColorType::La4)) } (png::ColorType::Rgb, png::BitDepth::Four) => { return Err(unsupported_color(ExtendedColorType::Rgb4)) } (png::ColorType::Rgba, png::BitDepth::Four) => { return Err(unsupported_color(ExtendedColorType::Rgba4)) } (png::ColorType::Indexed, bits) => { return Err(unsupported_color(ExtendedColorType::Unknown(bits as u8))) } }; Ok(PngDecoder { color_type, reader }) } /// Turn this into an iterator over the animation frames. /// /// Reading the complete animation requires more memory than reading the data from the IDAT /// frame–multiple frame buffers need to be reserved at the same time. We further do not /// support compositing 16-bit colors. In any case this would be lossy as the interface of /// animation decoders does not support 16-bit colors. /// /// If something is not supported or a limit is violated then the decoding step that requires /// them will fail and an error will be returned instead of the frame. No further frames will /// be returned. pub fn apng(self) -> ApngDecoder { ApngDecoder::new(self) } /// Returns if the image contains an animation. /// /// Note that the file itself decides if the default image is considered to be part of the /// animation. When it is not the common interpretation is to use it as a thumbnail. /// /// If a non-animated image is converted into an `ApngDecoder` then its iterator is empty. pub fn is_apng(&self) -> bool { self.reader.info().animation_control.is_some() } } fn unsupported_color(ect: ExtendedColorType) -> ImageError { ImageError::Unsupported(UnsupportedError::from_format_and_kind( ImageFormat::Png.into(), UnsupportedErrorKind::Color(ect), )) } impl<'a, R: 'a + Read> ImageDecoder<'a> for PngDecoder { type Reader = PngReader; fn dimensions(&self) -> (u32, u32) { self.reader.info().size() } fn color_type(&self) -> ColorType { self.color_type } fn icc_profile(&mut self) -> Option> { self.reader.info().icc_profile.as_ref().map(|x| x.to_vec()) } fn into_reader(self) -> ImageResult { PngReader::new(self.reader) } fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { use byteorder::{BigEndian, ByteOrder, NativeEndian}; assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); self.reader.next_frame(buf).map_err(ImageError::from_png)?; // PNG images are big endian. For 16 bit per channel and larger types, // the buffer may need to be reordered to native endianness per the // contract of `read_image`. // TODO: assumes equal channel bit depth. let bpc = self.color_type().bytes_per_pixel() / self.color_type().channel_count(); match bpc { 1 => (), // No reodering necessary for u8 2 => buf.chunks_mut(2).for_each(|c| { let v = BigEndian::read_u16(c); NativeEndian::write_u16(c, v) }), _ => unreachable!(), } Ok(()) } fn scanline_bytes(&self) -> u64 { let width = self.reader.info().width; self.reader.output_line_size(width) as u64 } } /// An [`AnimationDecoder`] adapter of [`PngDecoder`]. /// /// See [`PngDecoder::apng`] for more information. /// /// [`AnimationDecoder`]: ../trait.AnimationDecoder.html /// [`PngDecoder`]: struct.PngDecoder.html /// [`PngDecoder::apng`]: struct.PngDecoder.html#method.apng pub struct ApngDecoder { inner: PngDecoder, /// The current output buffer. current: RgbaImage, /// The previous output buffer, used for dispose op previous. previous: RgbaImage, /// The dispose op of the current frame. dispose: DisposeOp, /// The number of image still expected to be able to load. remaining: u32, /// The next (first) image is the thumbnail. has_thumbnail: bool, } impl ApngDecoder { fn new(inner: PngDecoder) -> Self { let (width, height) = inner.dimensions(); let info = inner.reader.info(); let remaining = match info.animation_control() { // The expected number of fcTL in the remaining image. Some(actl) => actl.num_frames, None => 0, }; // If the IDAT has no fcTL then it is not part of the animation counted by // num_frames. All following fdAT chunks must be preceded by an fcTL let has_thumbnail = info.frame_control.is_none(); ApngDecoder { inner, // TODO: should we delay this allocation? At least if we support limits we should. current: RgbaImage::new(width, height), previous: RgbaImage::new(width, height), dispose: DisposeOp::Background, remaining, has_thumbnail, } } // TODO: thumbnail(&mut self) -> Option> /// Decode one subframe and overlay it on the canvas. fn mix_next_frame(&mut self) -> Result, ImageError> { // Remove this image from remaining. self.remaining = match self.remaining.checked_sub(1) { None => return Ok(None), Some(next) => next, }; // Shorten ourselves to 0 in case of error. let remaining = self.remaining; self.remaining = 0; // Skip the thumbnail that is not part of the animation. if self.has_thumbnail { self.has_thumbnail = false; let mut buffer = vec![0; self.inner.reader.output_buffer_size()]; self.inner .reader .next_frame(&mut buffer) .map_err(ImageError::from_png)?; } self.animatable_color_type()?; // Dispose of the previous frame. match self.dispose { DisposeOp::None => { self.previous.clone_from(&self.current); } DisposeOp::Background => { self.previous.clone_from(&self.current); self.current .pixels_mut() .for_each(|pixel| *pixel = Rgba([0, 0, 0, 0])); } DisposeOp::Previous => { self.current.clone_from(&self.previous); } } // Read next frame data. let mut buffer = vec![0; self.inner.reader.output_buffer_size()]; self.inner .reader .next_frame(&mut buffer) .map_err(ImageError::from_png)?; let info = self.inner.reader.info(); // Find out how to interpret the decoded frame. let (width, height, px, py, blend); match info.frame_control() { None => { width = info.width; height = info.height; px = 0; py = 0; blend = BlendOp::Source; } Some(fc) => { width = fc.width; height = fc.height; px = fc.x_offset; py = fc.y_offset; blend = fc.blend_op; self.dispose = fc.dispose_op; } }; // Turn the data into an rgba image proper. let source = match self.inner.color_type { ColorType::L8 => { let image = ImageBuffer::, _>::from_raw(width, height, buffer).unwrap(); DynamicImage::ImageLuma8(image).into_rgba8() } ColorType::La8 => { let image = ImageBuffer::, _>::from_raw(width, height, buffer).unwrap(); DynamicImage::ImageLumaA8(image).into_rgba8() } ColorType::Rgb8 => { let image = ImageBuffer::, _>::from_raw(width, height, buffer).unwrap(); DynamicImage::ImageRgb8(image).into_rgba8() } ColorType::Rgba8 => ImageBuffer::, _>::from_raw(width, height, buffer).unwrap(), ColorType::L16 | ColorType::Rgb16 | ColorType::La16 | ColorType::Rgba16 => { // TODO: to enable remove restriction in `animatable_color_type` method. unreachable!("16-bit apng not yet support") } _ => unreachable!("Invalid png color"), }; match blend { BlendOp::Source => { self.current .copy_from(&source, px, py) .expect("Invalid png image not detected in png"); } BlendOp::Over => { // TODO: investigate speed, speed-ups, and bounds-checks. for (x, y, p) in source.enumerate_pixels() { self.current.get_pixel_mut(x + px, y + py).blend(p); } } } // Ok, we can proceed with actually remaining images. self.remaining = remaining; // Return composited output buffer. Ok(Some(&self.current)) } fn animatable_color_type(&self) -> Result<(), ImageError> { match self.inner.color_type { ColorType::L8 | ColorType::Rgb8 | ColorType::La8 | ColorType::Rgba8 => Ok(()), // TODO: do not handle multi-byte colors. Remember to implement it in `mix_next_frame`. ColorType::L16 | ColorType::Rgb16 | ColorType::La16 | ColorType::Rgba16 => { Err(unsupported_color(self.inner.color_type.into())) } _ => unreachable!("{:?} not a valid png color", self.inner.color_type), } } } impl<'a, R: Read + 'a> AnimationDecoder<'a> for ApngDecoder { fn into_frames(self) -> Frames<'a> { struct FrameIterator(ApngDecoder); impl Iterator for FrameIterator { type Item = ImageResult; fn next(&mut self) -> Option { let image = match self.0.mix_next_frame() { Ok(Some(image)) => image.clone(), Ok(None) => return None, Err(err) => return Some(Err(err)), }; let info = self.0.inner.reader.info(); let fc = info.frame_control().unwrap(); // PNG delays are rations in seconds. let num = u32::from(fc.delay_num) * 1_000u32; let denom = match fc.delay_den { // The standard dictates to replace by 100 when the denominator is 0. 0 => 100, d => u32::from(d), }; let delay = Delay::from_ratio(Ratio::new(num, denom)); Some(Ok(Frame::from_parts(image, 0, 0, delay))) } } Frames::new(Box::new(FrameIterator(self))) } } /// PNG encoder pub struct PngEncoder { w: W, compression: CompressionType, filter: FilterType, } /// Compression level of a PNG encoder. The default setting is `Fast`. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[non_exhaustive] pub enum CompressionType { /// Default compression level Default, /// Fast, minimal compression Fast, /// High compression level Best, /// Huffman coding compression #[deprecated(note = "use one of the other compression levels instead, such as 'Fast'")] Huffman, /// Run-length encoding compression #[deprecated(note = "use one of the other compression levels instead, such as 'Fast'")] Rle, } /// Filter algorithms used to process image data to improve compression. /// /// The default filter is `Adaptive`. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[non_exhaustive] pub enum FilterType { /// No processing done, best used for low bit depth grayscale or data with a /// low color count NoFilter, /// Filters based on previous pixel in the same scanline Sub, /// Filters based on the scanline above Up, /// Filters based on the average of left and right neighbor pixels Avg, /// Algorithm that takes into account the left, upper left, and above pixels Paeth, /// Uses a heuristic to select one of the preceding filters for each /// scanline rather than one filter for the entire image Adaptive, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[non_exhaustive] enum BadPngRepresentation { ColorType(ColorType), } impl PngEncoder { /// Create a new encoder that writes its output to ```w``` pub fn new(w: W) -> PngEncoder { PngEncoder { w, compression: CompressionType::default(), filter: FilterType::default(), } } /// Create a new encoder that writes its output to `w` with `CompressionType` `compression` and /// `FilterType` `filter`. /// /// It is best to view the options as a _hint_ to the implementation on the smallest or fastest /// option for encoding a particular image. That is, using options that map directly to a PNG /// image parameter will use this parameter where possible. But variants that have no direct /// mapping may be interpreted differently in minor versions. The exact output is expressly /// __not__ part the SemVer stability guarantee. /// /// Note that it is not optimal to use a single filter type, so an adaptive /// filter type is selected as the default. The filter which best minimizes /// file size may change with the type of compression used. pub fn new_with_quality( w: W, compression: CompressionType, filter: FilterType, ) -> PngEncoder { PngEncoder { w, compression, filter, } } /// Encodes the image `data` that has dimensions `width` and `height` and `ColorType` `c`. /// /// Expects data in big endian. #[deprecated = "Use `PngEncoder::write_image` instead. Beware that `write_image` has a different endianness convention"] pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> { self.encode_inner(data, width, height, color) } fn encode_inner( self, data: &[u8], width: u32, height: u32, color: ColorType, ) -> ImageResult<()> { let (ct, bits) = match color { ColorType::L8 => (png::ColorType::Grayscale, png::BitDepth::Eight), ColorType::L16 => (png::ColorType::Grayscale, png::BitDepth::Sixteen), ColorType::La8 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight), ColorType::La16 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen), ColorType::Rgb8 => (png::ColorType::Rgb, png::BitDepth::Eight), ColorType::Rgb16 => (png::ColorType::Rgb, png::BitDepth::Sixteen), ColorType::Rgba8 => (png::ColorType::Rgba, png::BitDepth::Eight), ColorType::Rgba16 => (png::ColorType::Rgba, png::BitDepth::Sixteen), _ => { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Png.into(), UnsupportedErrorKind::Color(color.into()), ), )) } }; let comp = match self.compression { CompressionType::Default => png::Compression::Default, CompressionType::Best => png::Compression::Best, _ => png::Compression::Fast, }; let (filter, adaptive_filter) = match self.filter { FilterType::NoFilter => ( png::FilterType::NoFilter, png::AdaptiveFilterType::NonAdaptive, ), FilterType::Sub => (png::FilterType::Sub, png::AdaptiveFilterType::NonAdaptive), FilterType::Up => (png::FilterType::Up, png::AdaptiveFilterType::NonAdaptive), FilterType::Avg => (png::FilterType::Avg, png::AdaptiveFilterType::NonAdaptive), FilterType::Paeth => (png::FilterType::Paeth, png::AdaptiveFilterType::NonAdaptive), FilterType::Adaptive => (png::FilterType::Sub, png::AdaptiveFilterType::Adaptive), }; let mut encoder = png::Encoder::new(self.w, width, height); encoder.set_color(ct); encoder.set_depth(bits); encoder.set_compression(comp); encoder.set_filter(filter); encoder.set_adaptive_filter(adaptive_filter); let mut writer = encoder .write_header() .map_err(|e| ImageError::IoError(e.into()))?; writer .write_image_data(data) .map_err(|e| ImageError::IoError(e.into())) } } impl ImageEncoder for PngEncoder { /// Write a PNG image with the specified width, height, and color type. /// /// For color types with 16-bit per channel or larger, the contents of `buf` should be in /// native endian. PngEncoder will automatically convert to big endian as required by the /// underlying PNG format. fn write_image( self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()> { use byteorder::{BigEndian, ByteOrder, NativeEndian}; use ColorType::*; // PNG images are big endian. For 16 bit per channel and larger types, // the buffer may need to be reordered to big endian per the // contract of `write_image`. // TODO: assumes equal channel bit depth. match color_type { L8 | La8 | Rgb8 | Rgba8 => { // No reodering necessary for u8 self.encode_inner(buf, width, height, color_type) } L16 | La16 | Rgb16 | Rgba16 => { // Because the buffer is immutable and the PNG encoder does not // yet take Write/Read traits, create a temporary buffer for // big endian reordering. let mut reordered = vec![0; buf.len()]; buf.chunks(2) .zip(reordered.chunks_mut(2)) .for_each(|(b, r)| BigEndian::write_u16(r, NativeEndian::read_u16(b))); self.encode_inner(&reordered, width, height, color_type) } _ => Err(ImageError::Encoding(EncodingError::new( ImageFormat::Png.into(), BadPngRepresentation::ColorType(color_type), ))), } } } impl ImageError { fn from_png(err: png::DecodingError) -> ImageError { use png::DecodingError::*; match err { IoError(err) => ImageError::IoError(err), // The input image was not a valid PNG. err @ Format(_) => { ImageError::Decoding(DecodingError::new(ImageFormat::Png.into(), err)) } // Other is used when: // - The decoder is polled for more animation frames despite being done (or not being animated // in the first place). // - The output buffer does not have the required size. err @ Parameter(_) => ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::Generic(err.to_string()), )), LimitsExceeded => { ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) } } } } impl Default for CompressionType { fn default() -> Self { CompressionType::Fast } } impl Default for FilterType { fn default() -> Self { FilterType::Adaptive } } impl fmt::Display for BadPngRepresentation { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::ColorType(color_type) => write!( f, "The color {:?} can not be represented in PNG.", color_type ), } } } impl std::error::Error for BadPngRepresentation {} #[cfg(test)] mod tests { use super::*; use crate::image::ImageDecoder; use crate::ImageOutputFormat; use std::io::{Cursor, Read}; #[test] fn ensure_no_decoder_off_by_one() { let dec = PngDecoder::new( std::fs::File::open("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png") .unwrap(), ) .expect("Unable to read PNG file (does it exist?)"); assert_eq![(2000, 1000), dec.dimensions()]; assert_eq![ ColorType::Rgb8, dec.color_type(), "Image MUST have the Rgb8 format" ]; let correct_bytes = dec .into_reader() .expect("Unable to read file") .bytes() .map(|x| x.expect("Unable to read byte")) .collect::>(); assert_eq![6_000_000, correct_bytes.len()]; } #[test] fn underlying_error() { use std::error::Error; let mut not_png = std::fs::read("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png") .unwrap(); not_png[0] = 0; let error = PngDecoder::new(¬_png[..]).err().unwrap(); let _ = error .source() .unwrap() .downcast_ref::() .expect("Caused by a png error"); } #[test] fn encode_bad_color_type() { // regression test for issue #1663 let image = DynamicImage::new_rgb32f(1, 1); let mut target = Cursor::new(vec![]); let _ = image.write_to(&mut target, ImageOutputFormat::Png); } } image-0.24.7/src/codecs/pnm/autobreak.rs000064400000000000000000000070071046102023000161500ustar 00000000000000//! Insert line breaks between written buffers when they would overflow the line length. use std::io; // The pnm standard says to insert line breaks after 70 characters. Assumes that no line breaks // are actually written. We have to be careful to fully commit buffers or not commit them at all, // otherwise we might insert a newline in the middle of a token. pub(crate) struct AutoBreak { wrapped: W, line_capacity: usize, line: Vec, has_newline: bool, panicked: bool, // see https://github.com/rust-lang/rust/issues/30888 } impl AutoBreak { pub(crate) fn new(writer: W, line_capacity: usize) -> Self { AutoBreak { wrapped: writer, line_capacity, line: Vec::with_capacity(line_capacity + 1), has_newline: false, panicked: false, } } fn flush_buf(&mut self) -> io::Result<()> { // from BufWriter let mut written = 0; let len = self.line.len(); let mut ret = Ok(()); while written < len { self.panicked = true; let r = self.wrapped.write(&self.line[written..]); self.panicked = false; match r { Ok(0) => { ret = Err(io::Error::new( io::ErrorKind::WriteZero, "failed to write the buffered data", )); break; } Ok(n) => written += n, Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} Err(e) => { ret = Err(e); break; } } } if written > 0 { self.line.drain(..written); } ret } } impl io::Write for AutoBreak { fn write(&mut self, buffer: &[u8]) -> io::Result { if self.has_newline { self.flush()?; self.has_newline = false; } if !self.line.is_empty() && self.line.len() + buffer.len() > self.line_capacity { self.line.push(b'\n'); self.has_newline = true; self.flush()?; self.has_newline = false; } self.line.extend_from_slice(buffer); Ok(buffer.len()) } fn flush(&mut self) -> io::Result<()> { self.flush_buf()?; self.wrapped.flush() } } impl Drop for AutoBreak { fn drop(&mut self) { if !self.panicked { let _r = self.flush_buf(); // internal writer flushed automatically by Drop } } } #[cfg(test)] mod tests { use super::*; use std::io::Write; #[test] fn test_aligned_writes() { let mut output = Vec::new(); { let mut writer = AutoBreak::new(&mut output, 10); writer.write_all(b"0123456789").unwrap(); writer.write_all(b"0123456789").unwrap(); } assert_eq!(output.as_slice(), b"0123456789\n0123456789"); } #[test] fn test_greater_writes() { let mut output = Vec::new(); { let mut writer = AutoBreak::new(&mut output, 10); writer.write_all(b"012").unwrap(); writer.write_all(b"345").unwrap(); writer.write_all(b"0123456789").unwrap(); writer.write_all(b"012345678910").unwrap(); writer.write_all(b"_").unwrap(); } assert_eq!(output.as_slice(), b"012345\n0123456789\n012345678910\n_"); } } image-0.24.7/src/codecs/pnm/decoder.rs000064400000000000000000001316141046102023000156020ustar 00000000000000use std::convert::TryFrom; use std::convert::TryInto; use std::error; use std::fmt::{self, Display}; use std::io::{self, BufRead, Cursor, Read}; use std::marker::PhantomData; use std::mem; use std::num::ParseIntError; use std::str::{self, FromStr}; use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader}; use super::{HeaderRecord, PnmHeader, PnmSubtype, SampleEncoding}; use crate::color::{ColorType, ExtendedColorType}; use crate::error::{ DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{self, ImageDecoder, ImageFormat}; use crate::utils; use byteorder::{BigEndian, ByteOrder, NativeEndian}; /// All errors that can occur when attempting to parse a PNM #[derive(Debug, Clone)] enum DecoderError { /// PNM's "P[123456]" signature wrong or missing PnmMagicInvalid([u8; 2]), /// Couldn't parse the specified string as an integer from the specified source UnparsableValue(ErrorDataSource, String, ParseIntError), /// More than the exactly one allowed plane specified by the format NonAsciiByteInHeader(u8), /// The PAM header contained a non-ASCII byte NonAsciiLineInPamHeader, /// A sample string contained a non-ASCII byte NonAsciiSample, /// The byte after the P7 magic was not 0x0A NEWLINE NotNewlineAfterP7Magic(u8), /// The PNM header had too few lines UnexpectedPnmHeaderEnd, /// The specified line was specified twice HeaderLineDuplicated(PnmHeaderLine), /// The line with the specified ID was not understood HeaderLineUnknown(String), /// At least one of the required lines were missing from the header (are `None` here) /// /// Same names as [`PnmHeaderLine`](enum.PnmHeaderLine.html) #[allow(missing_docs)] HeaderLineMissing { height: Option, width: Option, depth: Option, maxval: Option, }, /// Not enough data was provided to the Decoder to decode the image InputTooShort, /// Sample raster contained unexpected byte UnexpectedByteInRaster(u8), /// Specified sample was out of bounds (e.g. >1 in B&W) SampleOutOfBounds(u8), /// The image's maxval exceeds 0xFFFF MaxvalTooBig(u32), /// The specified tuple type supports restricted depths and maxvals, those restrictions were not met InvalidDepthOrMaxval { tuple_type: ArbitraryTuplType, depth: u32, maxval: u32, }, /// The specified tuple type supports restricted depths, those restrictions were not met InvalidDepth { tuple_type: ArbitraryTuplType, depth: u32, }, /// The tuple type was not recognised by the parser TupleTypeUnrecognised, /// Overflowed the specified value when parsing Overflow, } impl Display for DecoderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DecoderError::PnmMagicInvalid(magic) => f.write_fmt(format_args!( "Expected magic constant for PNM: P1..P7, got [{:#04X?}, {:#04X?}]", magic[0], magic[1] )), DecoderError::UnparsableValue(src, data, err) => { f.write_fmt(format_args!("Error parsing {:?} as {}: {}", data, src, err)) } DecoderError::NonAsciiByteInHeader(c) => { f.write_fmt(format_args!("Non-ASCII character {:#04X?} in header", c)) } DecoderError::NonAsciiLineInPamHeader => f.write_str("Non-ASCII line in PAM header"), DecoderError::NonAsciiSample => { f.write_str("Non-ASCII character where sample value was expected") } DecoderError::NotNewlineAfterP7Magic(c) => f.write_fmt(format_args!( "Expected newline after P7 magic, got {:#04X?}", c )), DecoderError::UnexpectedPnmHeaderEnd => f.write_str("Unexpected end of PNM header"), DecoderError::HeaderLineDuplicated(line) => { f.write_fmt(format_args!("Duplicate {} line", line)) } DecoderError::HeaderLineUnknown(identifier) => f.write_fmt(format_args!( "Unknown header line with identifier {:?}", identifier )), DecoderError::HeaderLineMissing { height, width, depth, maxval, } => f.write_fmt(format_args!( "Missing header line: have height={:?}, width={:?}, depth={:?}, maxval={:?}", height, width, depth, maxval )), DecoderError::InputTooShort => { f.write_str("Not enough data was provided to the Decoder to decode the image") } DecoderError::UnexpectedByteInRaster(c) => f.write_fmt(format_args!( "Unexpected character {:#04X?} within sample raster", c )), DecoderError::SampleOutOfBounds(val) => { f.write_fmt(format_args!("Sample value {} outside of bounds", val)) } DecoderError::MaxvalTooBig(maxval) => { f.write_fmt(format_args!("Image MAXVAL exceeds {}: {}", 0xFFFF, maxval)) } DecoderError::InvalidDepthOrMaxval { tuple_type, depth, maxval, } => f.write_fmt(format_args!( "Invalid depth ({}) or maxval ({}) for tuple type {}", depth, maxval, tuple_type.name() )), DecoderError::InvalidDepth { tuple_type, depth } => f.write_fmt(format_args!( "Invalid depth ({}) for tuple type {}", depth, tuple_type.name() )), DecoderError::TupleTypeUnrecognised => f.write_str("Tuple type not recognized"), DecoderError::Overflow => f.write_str("Overflow when parsing value"), } } } /// Note: should `pnm` be extracted into a separate crate, /// this will need to be hidden until that crate hits version `1.0`. impl From for ImageError { fn from(e: DecoderError) -> ImageError { ImageError::Decoding(DecodingError::new(ImageFormat::Pnm.into(), e)) } } impl error::Error for DecoderError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { DecoderError::UnparsableValue(_, _, err) => Some(err), _ => None, } } } /// Single-value lines in a PNM header #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] enum PnmHeaderLine { /// "HEIGHT" Height, /// "WIDTH" Width, /// "DEPTH" Depth, /// "MAXVAL", a.k.a. `maxwhite` Maxval, } impl Display for PnmHeaderLine { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { PnmHeaderLine::Height => "HEIGHT", PnmHeaderLine::Width => "WIDTH", PnmHeaderLine::Depth => "DEPTH", PnmHeaderLine::Maxval => "MAXVAL", }) } } /// Single-value lines in a PNM header #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] enum ErrorDataSource { /// One of the header lines Line(PnmHeaderLine), /// Value in the preamble Preamble, /// Sample/pixel data Sample, } impl Display for ErrorDataSource { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ErrorDataSource::Line(l) => l.fmt(f), ErrorDataSource::Preamble => f.write_str("number in preamble"), ErrorDataSource::Sample => f.write_str("sample"), } } } /// Dynamic representation, represents all decodable (sample, depth) combinations. #[derive(Clone, Copy)] enum TupleType { PbmBit, BWBit, GrayU8, GrayU16, RGBU8, RGBU16, } trait Sample { fn bytelen(width: u32, height: u32, samples: u32) -> ImageResult; fn from_bytes(bytes: &[u8], row_size: usize, output_buf: &mut [u8]) -> ImageResult<()>; fn from_ascii(reader: &mut dyn Read, output_buf: &mut [u8]) -> ImageResult<()>; } struct U8; struct U16; struct PbmBit; struct BWBit; trait DecodableImageHeader { fn tuple_type(&self) -> ImageResult; } /// PNM decoder pub struct PnmDecoder { reader: R, header: PnmHeader, tuple: TupleType, } impl PnmDecoder { /// Create a new decoder that decodes from the stream ```read``` pub fn new(mut buffered_read: R) -> ImageResult> { let magic = buffered_read.read_magic_constant()?; let subtype = match magic { [b'P', b'1'] => PnmSubtype::Bitmap(SampleEncoding::Ascii), [b'P', b'2'] => PnmSubtype::Graymap(SampleEncoding::Ascii), [b'P', b'3'] => PnmSubtype::Pixmap(SampleEncoding::Ascii), [b'P', b'4'] => PnmSubtype::Bitmap(SampleEncoding::Binary), [b'P', b'5'] => PnmSubtype::Graymap(SampleEncoding::Binary), [b'P', b'6'] => PnmSubtype::Pixmap(SampleEncoding::Binary), [b'P', b'7'] => PnmSubtype::ArbitraryMap, _ => return Err(DecoderError::PnmMagicInvalid(magic).into()), }; let decoder = match subtype { PnmSubtype::Bitmap(enc) => PnmDecoder::read_bitmap_header(buffered_read, enc), PnmSubtype::Graymap(enc) => PnmDecoder::read_graymap_header(buffered_read, enc), PnmSubtype::Pixmap(enc) => PnmDecoder::read_pixmap_header(buffered_read, enc), PnmSubtype::ArbitraryMap => PnmDecoder::read_arbitrary_header(buffered_read), }?; if utils::check_dimension_overflow( decoder.dimensions().0, decoder.dimensions().1, decoder.color_type().bytes_per_pixel(), ) { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::GenericFeature(format!( "Image dimensions ({}x{}) are too large", decoder.dimensions().0, decoder.dimensions().1 )), ), )); } Ok(decoder) } /// Extract the reader and header after an image has been read. pub fn into_inner(self) -> (R, PnmHeader) { (self.reader, self.header) } fn read_bitmap_header(mut reader: R, encoding: SampleEncoding) -> ImageResult> { let header = reader.read_bitmap_header(encoding)?; Ok(PnmDecoder { reader, tuple: TupleType::PbmBit, header: PnmHeader { decoded: HeaderRecord::Bitmap(header), encoded: None, }, }) } fn read_graymap_header(mut reader: R, encoding: SampleEncoding) -> ImageResult> { let header = reader.read_graymap_header(encoding)?; let tuple_type = header.tuple_type()?; Ok(PnmDecoder { reader, tuple: tuple_type, header: PnmHeader { decoded: HeaderRecord::Graymap(header), encoded: None, }, }) } fn read_pixmap_header(mut reader: R, encoding: SampleEncoding) -> ImageResult> { let header = reader.read_pixmap_header(encoding)?; let tuple_type = header.tuple_type()?; Ok(PnmDecoder { reader, tuple: tuple_type, header: PnmHeader { decoded: HeaderRecord::Pixmap(header), encoded: None, }, }) } fn read_arbitrary_header(mut reader: R) -> ImageResult> { let header = reader.read_arbitrary_header()?; let tuple_type = header.tuple_type()?; Ok(PnmDecoder { reader, tuple: tuple_type, header: PnmHeader { decoded: HeaderRecord::Arbitrary(header), encoded: None, }, }) } } trait HeaderReader: BufRead { /// Reads the two magic constant bytes fn read_magic_constant(&mut self) -> ImageResult<[u8; 2]> { let mut magic: [u8; 2] = [0, 0]; self.read_exact(&mut magic)?; Ok(magic) } /// Reads a string as well as a single whitespace after it, ignoring comments fn read_next_string(&mut self) -> ImageResult { let mut bytes = Vec::new(); // pair input bytes with a bool mask to remove comments let mark_comments = self.bytes().scan(true, |partof, read| { let byte = match read { Err(err) => return Some((*partof, Err(err))), Ok(byte) => byte, }; let cur_enabled = *partof && byte != b'#'; let next_enabled = cur_enabled || (byte == b'\r' || byte == b'\n'); *partof = next_enabled; Some((cur_enabled, Ok(byte))) }); for (_, byte) in mark_comments.filter(|e| e.0) { match byte { Ok(b'\t') | Ok(b'\n') | Ok(b'\x0b') | Ok(b'\x0c') | Ok(b'\r') | Ok(b' ') => { if !bytes.is_empty() { break; // We're done as we already have some content } } Ok(byte) if !byte.is_ascii() => { return Err(DecoderError::NonAsciiByteInHeader(byte).into()) } Ok(byte) => { bytes.push(byte); } Err(_) => break, } } if bytes.is_empty() { return Err(ImageError::IoError(io::ErrorKind::UnexpectedEof.into())); } if !bytes.as_slice().is_ascii() { // We have only filled the buffer with characters for which `byte.is_ascii()` holds. unreachable!("Non-ASCII character should have returned sooner") } let string = String::from_utf8(bytes) // We checked the precondition ourselves a few lines before, `bytes.as_slice().is_ascii()`. .unwrap_or_else(|_| unreachable!("Only ASCII characters should be decoded")); Ok(string) } /// Read the next line fn read_next_line(&mut self) -> ImageResult { let mut buffer = String::new(); self.read_line(&mut buffer)?; Ok(buffer) } fn read_next_u32(&mut self) -> ImageResult { let s = self.read_next_string()?; s.parse::() .map_err(|err| DecoderError::UnparsableValue(ErrorDataSource::Preamble, s, err).into()) } fn read_bitmap_header(&mut self, encoding: SampleEncoding) -> ImageResult { let width = self.read_next_u32()?; let height = self.read_next_u32()?; Ok(BitmapHeader { encoding, width, height, }) } fn read_graymap_header(&mut self, encoding: SampleEncoding) -> ImageResult { self.read_pixmap_header(encoding).map( |PixmapHeader { encoding, width, height, maxval, }| GraymapHeader { encoding, width, height, maxwhite: maxval, }, ) } fn read_pixmap_header(&mut self, encoding: SampleEncoding) -> ImageResult { let width = self.read_next_u32()?; let height = self.read_next_u32()?; let maxval = self.read_next_u32()?; Ok(PixmapHeader { encoding, width, height, maxval, }) } fn read_arbitrary_header(&mut self) -> ImageResult { fn parse_single_value_line( line_val: &mut Option, rest: &str, line: PnmHeaderLine, ) -> ImageResult<()> { if line_val.is_some() { Err(DecoderError::HeaderLineDuplicated(line).into()) } else { let v = rest.trim().parse().map_err(|err| { DecoderError::UnparsableValue(ErrorDataSource::Line(line), rest.to_owned(), err) })?; *line_val = Some(v); Ok(()) } } match self.bytes().next() { None => return Err(ImageError::IoError(io::ErrorKind::UnexpectedEof.into())), Some(Err(io)) => return Err(ImageError::IoError(io)), Some(Ok(b'\n')) => (), Some(Ok(c)) => return Err(DecoderError::NotNewlineAfterP7Magic(c).into()), } let mut line = String::new(); let mut height: Option = None; let mut width: Option = None; let mut depth: Option = None; let mut maxval: Option = None; let mut tupltype: Option = None; loop { line.truncate(0); let len = self.read_line(&mut line)?; if len == 0 { return Err(DecoderError::UnexpectedPnmHeaderEnd.into()); } if line.as_bytes()[0] == b'#' { continue; } if !line.is_ascii() { return Err(DecoderError::NonAsciiLineInPamHeader.into()); } #[allow(deprecated)] let (identifier, rest) = line .trim_left() .split_at(line.find(char::is_whitespace).unwrap_or(line.len())); match identifier { "ENDHDR" => break, "HEIGHT" => parse_single_value_line(&mut height, rest, PnmHeaderLine::Height)?, "WIDTH" => parse_single_value_line(&mut width, rest, PnmHeaderLine::Width)?, "DEPTH" => parse_single_value_line(&mut depth, rest, PnmHeaderLine::Depth)?, "MAXVAL" => parse_single_value_line(&mut maxval, rest, PnmHeaderLine::Maxval)?, "TUPLTYPE" => { let identifier = rest.trim(); if tupltype.is_some() { let appended = tupltype.take().map(|mut v| { v.push(' '); v.push_str(identifier); v }); tupltype = appended; } else { tupltype = Some(identifier.to_string()); } } _ => return Err(DecoderError::HeaderLineUnknown(identifier.to_string()).into()), } } let (h, w, d, m) = match (height, width, depth, maxval) { (Some(h), Some(w), Some(d), Some(m)) => (h, w, d, m), _ => { return Err(DecoderError::HeaderLineMissing { height, width, depth, maxval, } .into()) } }; let tupltype = match tupltype { None => None, Some(ref t) if t == "BLACKANDWHITE" => Some(ArbitraryTuplType::BlackAndWhite), Some(ref t) if t == "BLACKANDWHITE_ALPHA" => { Some(ArbitraryTuplType::BlackAndWhiteAlpha) } Some(ref t) if t == "GRAYSCALE" => Some(ArbitraryTuplType::Grayscale), Some(ref t) if t == "GRAYSCALE_ALPHA" => Some(ArbitraryTuplType::GrayscaleAlpha), Some(ref t) if t == "RGB" => Some(ArbitraryTuplType::RGB), Some(ref t) if t == "RGB_ALPHA" => Some(ArbitraryTuplType::RGBAlpha), Some(other) => Some(ArbitraryTuplType::Custom(other)), }; Ok(ArbitraryHeader { height: h, width: w, depth: d, maxval: m, tupltype, }) } } impl HeaderReader for R where R: BufRead {} /// Wrapper struct around a `Cursor>` pub struct PnmReader(Cursor>, PhantomData); impl Read for PnmReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { if self.0.position() == 0 && buf.is_empty() { mem::swap(buf, self.0.get_mut()); Ok(buf.len()) } else { self.0.read_to_end(buf) } } } impl<'a, R: 'a + Read> ImageDecoder<'a> for PnmDecoder { type Reader = PnmReader; fn dimensions(&self) -> (u32, u32) { (self.header.width(), self.header.height()) } fn color_type(&self) -> ColorType { match self.tuple { TupleType::PbmBit => ColorType::L8, TupleType::BWBit => ColorType::L8, TupleType::GrayU8 => ColorType::L8, TupleType::GrayU16 => ColorType::L16, TupleType::RGBU8 => ColorType::Rgb8, TupleType::RGBU16 => ColorType::Rgb16, } } fn original_color_type(&self) -> ExtendedColorType { match self.tuple { TupleType::PbmBit => ExtendedColorType::L1, TupleType::BWBit => ExtendedColorType::L1, TupleType::GrayU8 => ExtendedColorType::L8, TupleType::GrayU16 => ExtendedColorType::L16, TupleType::RGBU8 => ExtendedColorType::Rgb8, TupleType::RGBU16 => ExtendedColorType::Rgb16, } } fn into_reader(self) -> ImageResult { Ok(PnmReader( Cursor::new(image::decoder_to_vec(self)?), PhantomData, )) } fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); match self.tuple { TupleType::PbmBit => self.read_samples::(1, buf), TupleType::BWBit => self.read_samples::(1, buf), TupleType::RGBU8 => self.read_samples::(3, buf), TupleType::RGBU16 => self.read_samples::(3, buf), TupleType::GrayU8 => self.read_samples::(1, buf), TupleType::GrayU16 => self.read_samples::(1, buf), } } } impl PnmDecoder { fn read_samples(&mut self, components: u32, buf: &mut [u8]) -> ImageResult<()> { match self.subtype().sample_encoding() { SampleEncoding::Binary => { let width = self.header.width(); let height = self.header.height(); let bytecount = S::bytelen(width, height, components)?; let mut bytes = vec![]; self.reader .by_ref() // This conversion is potentially lossy but unlikely and in that case we error // later anyways. .take(bytecount as u64) .read_to_end(&mut bytes)?; if bytes.len() != bytecount { return Err(DecoderError::InputTooShort.into()); } let width: usize = width.try_into().map_err(|_| DecoderError::Overflow)?; let components: usize = components.try_into().map_err(|_| DecoderError::Overflow)?; let row_size = width .checked_mul(components) .ok_or(DecoderError::Overflow)?; S::from_bytes(&bytes, row_size, buf) } SampleEncoding::Ascii => self.read_ascii::(buf), } } fn read_ascii(&mut self, output_buf: &mut [u8]) -> ImageResult<()> { Basic::from_ascii(&mut self.reader, output_buf) } /// Get the pnm subtype, depending on the magic constant contained in the header pub fn subtype(&self) -> PnmSubtype { self.header.subtype() } } fn read_separated_ascii>(reader: &mut dyn Read) -> ImageResult where T::Err: Display, { let is_separator = |v: &u8| matches! { *v, b'\t' | b'\n' | b'\x0b' | b'\x0c' | b'\r' | b' ' }; let token = reader .bytes() .skip_while(|v| v.as_ref().ok().map(is_separator).unwrap_or(false)) .take_while(|v| v.as_ref().ok().map(|c| !is_separator(c)).unwrap_or(false)) .collect::, _>>()?; if !token.is_ascii() { return Err(DecoderError::NonAsciiSample.into()); } let string = str::from_utf8(&token) // We checked the precondition ourselves a few lines before with `token.is_ascii()`. .unwrap_or_else(|_| unreachable!("Only ASCII characters should be decoded")); string.parse().map_err(|err| { DecoderError::UnparsableValue(ErrorDataSource::Sample, string.to_owned(), err).into() }) } impl Sample for U8 { fn bytelen(width: u32, height: u32, samples: u32) -> ImageResult { Ok((width * height * samples) as usize) } fn from_bytes(bytes: &[u8], _row_size: usize, output_buf: &mut [u8]) -> ImageResult<()> { output_buf.copy_from_slice(bytes); Ok(()) } fn from_ascii(reader: &mut dyn Read, output_buf: &mut [u8]) -> ImageResult<()> { for b in output_buf { *b = read_separated_ascii(reader)?; } Ok(()) } } impl Sample for U16 { fn bytelen(width: u32, height: u32, samples: u32) -> ImageResult { Ok((width * height * samples * 2) as usize) } fn from_bytes(bytes: &[u8], _row_size: usize, output_buf: &mut [u8]) -> ImageResult<()> { output_buf.copy_from_slice(bytes); for chunk in output_buf.chunks_exact_mut(2) { let v = BigEndian::read_u16(chunk); NativeEndian::write_u16(chunk, v); } Ok(()) } fn from_ascii(reader: &mut dyn Read, output_buf: &mut [u8]) -> ImageResult<()> { for chunk in output_buf.chunks_exact_mut(2) { let v = read_separated_ascii::(reader)?; NativeEndian::write_u16(chunk, v); } Ok(()) } } // The image is encoded in rows of bits, high order bits first. Any bits beyond the row bits should // be ignored. Also, contrary to rgb, black pixels are encoded as a 1 while white is 0. This will // need to be reversed for the grayscale output. impl Sample for PbmBit { fn bytelen(width: u32, height: u32, samples: u32) -> ImageResult { let count = width * samples; let linelen = (count / 8) + ((count % 8) != 0) as u32; Ok((linelen * height) as usize) } fn from_bytes(bytes: &[u8], row_size: usize, output_buf: &mut [u8]) -> ImageResult<()> { let mut expanded = utils::expand_bits(1, row_size.try_into().unwrap(), bytes); for b in expanded.iter_mut() { *b = !*b; } output_buf.copy_from_slice(&expanded); Ok(()) } fn from_ascii(reader: &mut dyn Read, output_buf: &mut [u8]) -> ImageResult<()> { let mut bytes = reader.bytes(); for b in output_buf { loop { let byte = bytes .next() .ok_or_else::(|| DecoderError::InputTooShort.into())??; match byte { b'\t' | b'\n' | b'\x0b' | b'\x0c' | b'\r' | b' ' => continue, b'0' => *b = 255, b'1' => *b = 0, c => return Err(DecoderError::UnexpectedByteInRaster(c).into()), } break; } } Ok(()) } } // Encoded just like a normal U8 but we check the values. impl Sample for BWBit { fn bytelen(width: u32, height: u32, samples: u32) -> ImageResult { U8::bytelen(width, height, samples) } fn from_bytes(bytes: &[u8], row_size: usize, output_buf: &mut [u8]) -> ImageResult<()> { U8::from_bytes(bytes, row_size, output_buf)?; if let Some(val) = output_buf.iter().find(|&val| *val > 1) { return Err(DecoderError::SampleOutOfBounds(*val).into()); } Ok(()) } fn from_ascii(_reader: &mut dyn Read, _output_buf: &mut [u8]) -> ImageResult<()> { unreachable!("BW bits from anymaps are never encoded as ASCII") } } impl DecodableImageHeader for BitmapHeader { fn tuple_type(&self) -> ImageResult { Ok(TupleType::PbmBit) } } impl DecodableImageHeader for GraymapHeader { fn tuple_type(&self) -> ImageResult { match self.maxwhite { v if v <= 0xFF => Ok(TupleType::GrayU8), v if v <= 0xFFFF => Ok(TupleType::GrayU16), _ => Err(DecoderError::MaxvalTooBig(self.maxwhite).into()), } } } impl DecodableImageHeader for PixmapHeader { fn tuple_type(&self) -> ImageResult { match self.maxval { v if v <= 0xFF => Ok(TupleType::RGBU8), v if v <= 0xFFFF => Ok(TupleType::RGBU16), _ => Err(DecoderError::MaxvalTooBig(self.maxval).into()), } } } impl DecodableImageHeader for ArbitraryHeader { fn tuple_type(&self) -> ImageResult { match self.tupltype { None if self.depth == 1 => Ok(TupleType::GrayU8), None if self.depth == 2 => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::Color(ExtendedColorType::La8), ), )), None if self.depth == 3 => Ok(TupleType::RGBU8), None if self.depth == 4 => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::Color(ExtendedColorType::Rgba8), ), )), Some(ArbitraryTuplType::BlackAndWhite) if self.maxval == 1 && self.depth == 1 => { Ok(TupleType::BWBit) } Some(ArbitraryTuplType::BlackAndWhite) => Err(DecoderError::InvalidDepthOrMaxval { tuple_type: ArbitraryTuplType::BlackAndWhite, maxval: self.maxval, depth: self.depth, } .into()), Some(ArbitraryTuplType::Grayscale) if self.depth == 1 && self.maxval <= 0xFF => { Ok(TupleType::GrayU8) } Some(ArbitraryTuplType::Grayscale) if self.depth <= 1 && self.maxval <= 0xFFFF => { Ok(TupleType::GrayU16) } Some(ArbitraryTuplType::Grayscale) => Err(DecoderError::InvalidDepthOrMaxval { tuple_type: ArbitraryTuplType::Grayscale, maxval: self.maxval, depth: self.depth, } .into()), Some(ArbitraryTuplType::RGB) if self.depth == 3 && self.maxval <= 0xFF => { Ok(TupleType::RGBU8) } Some(ArbitraryTuplType::RGB) if self.depth == 3 && self.maxval <= 0xFFFF => { Ok(TupleType::RGBU16) } Some(ArbitraryTuplType::RGB) => Err(DecoderError::InvalidDepth { tuple_type: ArbitraryTuplType::RGB, depth: self.depth, } .into()), Some(ArbitraryTuplType::BlackAndWhiteAlpha) => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::GenericFeature(format!( "Color type {}", ArbitraryTuplType::BlackAndWhiteAlpha.name() )), ), )), Some(ArbitraryTuplType::GrayscaleAlpha) => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::Color(ExtendedColorType::La8), ), )), Some(ArbitraryTuplType::RGBAlpha) => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::Color(ExtendedColorType::Rgba8), ), )), Some(ArbitraryTuplType::Custom(ref custom)) => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::GenericFeature(format!("Tuple type {:?}", custom)), ), )), None => Err(DecoderError::TupleTypeUnrecognised.into()), } } } #[cfg(test)] mod tests { use super::*; /// Tests reading of a valid blackandwhite pam #[test] fn pam_blackandwhite() { let pamdata = b"P7 WIDTH 4 HEIGHT 4 DEPTH 1 MAXVAL 1 TUPLTYPE BLACKANDWHITE # Comment line ENDHDR \x01\x00\x00\x01\x01\x00\x00\x01\x01\x00\x00\x01\x01\x00\x00\x01"; let decoder = PnmDecoder::new(&pamdata[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.original_color_type(), ExtendedColorType::L1); assert_eq!(decoder.dimensions(), (4, 4)); assert_eq!(decoder.subtype(), PnmSubtype::ArbitraryMap); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!( image, vec![ 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01 ] ); match PnmDecoder::new(&pamdata[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Arbitrary(ArbitraryHeader { width: 4, height: 4, maxval: 1, depth: 1, tupltype: Some(ArbitraryTuplType::BlackAndWhite), }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } /// Tests reading of a valid grayscale pam #[test] fn pam_grayscale() { let pamdata = b"P7 WIDTH 4 HEIGHT 4 DEPTH 1 MAXVAL 255 TUPLTYPE GRAYSCALE # Comment line ENDHDR \xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef"; let decoder = PnmDecoder::new(&pamdata[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.dimensions(), (4, 4)); assert_eq!(decoder.subtype(), PnmSubtype::ArbitraryMap); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!( image, vec![ 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef ] ); match PnmDecoder::new(&pamdata[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Arbitrary(ArbitraryHeader { width: 4, height: 4, depth: 1, maxval: 255, tupltype: Some(ArbitraryTuplType::Grayscale), }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } /// Tests reading of a valid rgb pam #[test] fn pam_rgb() { let pamdata = b"P7 # Comment line MAXVAL 255 TUPLTYPE RGB DEPTH 3 WIDTH 2 HEIGHT 2 ENDHDR \xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef"; let decoder = PnmDecoder::new(&pamdata[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::Rgb8); assert_eq!(decoder.dimensions(), (2, 2)); assert_eq!(decoder.subtype(), PnmSubtype::ArbitraryMap); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!( image, vec![0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef] ); match PnmDecoder::new(&pamdata[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Arbitrary(ArbitraryHeader { maxval: 255, tupltype: Some(ArbitraryTuplType::RGB), depth: 3, width: 2, height: 2, }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } #[test] fn pbm_binary() { // The data contains two rows of the image (each line is padded to the full byte). For // comments on its format, see documentation of `impl SampleType for PbmBit`. let pbmbinary = [&b"P4 6 2\n"[..], &[0b01101100 as u8, 0b10110111]].concat(); let decoder = PnmDecoder::new(&pbmbinary[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.original_color_type(), ExtendedColorType::L1); assert_eq!(decoder.dimensions(), (6, 2)); assert_eq!( decoder.subtype(), PnmSubtype::Bitmap(SampleEncoding::Binary) ); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!(image, vec![255, 0, 0, 255, 0, 0, 0, 255, 0, 0, 255, 0]); match PnmDecoder::new(&pbmbinary[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Bitmap(BitmapHeader { encoding: SampleEncoding::Binary, width: 6, height: 2, }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } /// A previous infinite loop. #[test] fn pbm_binary_ascii_termination() { use std::io::{BufReader, Cursor, Error, ErrorKind, Read, Result}; struct FailRead(Cursor<&'static [u8]>); impl Read for FailRead { fn read(&mut self, buf: &mut [u8]) -> Result { match self.0.read(buf) { Ok(n) if n > 0 => Ok(n), _ => Err(Error::new( ErrorKind::BrokenPipe, "Simulated broken pipe error", )), } } } let pbmbinary = BufReader::new(FailRead(Cursor::new(b"P1 1 1\n"))); let decoder = PnmDecoder::new(pbmbinary).unwrap(); let mut image = vec![0; decoder.total_bytes() as usize]; decoder .read_image(&mut image) .expect_err("Image is malformed"); } #[test] fn pbm_ascii() { // The data contains two rows of the image (each line is padded to the full byte). For // comments on its format, see documentation of `impl SampleType for PbmBit`. Tests all // whitespace characters that should be allowed (the 6 characters according to POSIX). let pbmbinary = b"P1 6 2\n 0 1 1 0 1 1\n1 0 1 1 0\t\n\x0b\x0c\r1"; let decoder = PnmDecoder::new(&pbmbinary[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.original_color_type(), ExtendedColorType::L1); assert_eq!(decoder.dimensions(), (6, 2)); assert_eq!(decoder.subtype(), PnmSubtype::Bitmap(SampleEncoding::Ascii)); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!(image, vec![255, 0, 0, 255, 0, 0, 0, 255, 0, 0, 255, 0]); match PnmDecoder::new(&pbmbinary[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Bitmap(BitmapHeader { encoding: SampleEncoding::Ascii, width: 6, height: 2, }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } #[test] fn pbm_ascii_nospace() { // The data contains two rows of the image (each line is padded to the full byte). Notably, // it is completely within specification for the ascii data not to contain separating // whitespace for the pbm format or any mix. let pbmbinary = b"P1 6 2\n011011101101"; let decoder = PnmDecoder::new(&pbmbinary[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.original_color_type(), ExtendedColorType::L1); assert_eq!(decoder.dimensions(), (6, 2)); assert_eq!(decoder.subtype(), PnmSubtype::Bitmap(SampleEncoding::Ascii)); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!(image, vec![255, 0, 0, 255, 0, 0, 0, 255, 0, 0, 255, 0]); match PnmDecoder::new(&pbmbinary[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Bitmap(BitmapHeader { encoding: SampleEncoding::Ascii, width: 6, height: 2, }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } #[test] fn pgm_binary() { // The data contains two rows of the image (each line is padded to the full byte). For // comments on its format, see documentation of `impl SampleType for PbmBit`. let elements = (0..16).collect::>(); let pbmbinary = [&b"P5 4 4 255\n"[..], &elements].concat(); let decoder = PnmDecoder::new(&pbmbinary[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.dimensions(), (4, 4)); assert_eq!( decoder.subtype(), PnmSubtype::Graymap(SampleEncoding::Binary) ); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!(image, elements); match PnmDecoder::new(&pbmbinary[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Graymap(GraymapHeader { encoding: SampleEncoding::Binary, width: 4, height: 4, maxwhite: 255, }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } #[test] fn pgm_ascii() { // The data contains two rows of the image (each line is padded to the full byte). For // comments on its format, see documentation of `impl SampleType for PbmBit`. let pbmbinary = b"P2 4 4 255\n 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15"; let decoder = PnmDecoder::new(&pbmbinary[..]).unwrap(); assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.dimensions(), (4, 4)); assert_eq!( decoder.subtype(), PnmSubtype::Graymap(SampleEncoding::Ascii) ); let mut image = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut image).unwrap(); assert_eq!(image, (0..16).collect::>()); match PnmDecoder::new(&pbmbinary[..]).unwrap().into_inner() { ( _, PnmHeader { decoded: HeaderRecord::Graymap(GraymapHeader { encoding: SampleEncoding::Ascii, width: 4, height: 4, maxwhite: 255, }), encoded: _, }, ) => (), _ => panic!("Decoded header is incorrect"), } } #[test] fn dimension_overflow() { let pamdata = b"P7 # Comment line MAXVAL 255 TUPLTYPE RGB DEPTH 3 WIDTH 4294967295 HEIGHT 4294967295 ENDHDR \xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef"; assert!(PnmDecoder::new(&pamdata[..]).is_err()); } #[test] fn issue_1508() { let _ = crate::load_from_memory(b"P391919 16999 1 1 9 919 16999 1 9999 999* 99999 N"); } #[test] fn issue_1616_overflow() { let data = vec![ 80, 54, 10, 52, 50, 57, 52, 56, 50, 57, 52, 56, 35, 56, 10, 52, 10, 48, 10, 12, 12, 56, ]; // Validate: we have a header. Note: we might already calculate that this will fail but // then we could not return information about the header to the caller. let decoder = PnmDecoder::new(&data[..]).unwrap(); let mut image = vec![0; decoder.total_bytes() as usize]; let _ = decoder.read_image(&mut image); } } image-0.24.7/src/codecs/pnm/encoder.rs000064400000000000000000000552301046102023000156130ustar 00000000000000//! Encoding of PNM Images use std::fmt; use std::io; use std::io::Write; use super::AutoBreak; use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader}; use super::{HeaderRecord, PnmHeader, PnmSubtype, SampleEncoding}; use crate::color::{ColorType, ExtendedColorType}; use crate::error::{ ImageError, ImageResult, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{ImageEncoder, ImageFormat}; use byteorder::{BigEndian, WriteBytesExt}; enum HeaderStrategy { Dynamic, Subtype(PnmSubtype), Chosen(PnmHeader), } #[derive(Clone, Copy)] pub enum FlatSamples<'a> { U8(&'a [u8]), U16(&'a [u16]), } /// Encodes images to any of the `pnm` image formats. pub struct PnmEncoder { writer: W, header: HeaderStrategy, } /// Encapsulate the checking system in the type system. Non of the fields are actually accessed /// but requiring them forces us to validly construct the struct anyways. struct CheckedImageBuffer<'a> { _image: FlatSamples<'a>, _width: u32, _height: u32, _color: ExtendedColorType, } // Check the header against the buffer. Each struct produces the next after a check. struct UncheckedHeader<'a> { header: &'a PnmHeader, } struct CheckedDimensions<'a> { unchecked: UncheckedHeader<'a>, width: u32, height: u32, } struct CheckedHeaderColor<'a> { dimensions: CheckedDimensions<'a>, color: ExtendedColorType, } struct CheckedHeader<'a> { color: CheckedHeaderColor<'a>, encoding: TupleEncoding<'a>, _image: CheckedImageBuffer<'a>, } enum TupleEncoding<'a> { PbmBits { samples: FlatSamples<'a>, width: u32, }, Ascii { samples: FlatSamples<'a>, }, Bytes { samples: FlatSamples<'a>, }, } impl PnmEncoder { /// Create new PnmEncoder from the `writer`. /// /// The encoded images will have some `pnm` format. If more control over the image type is /// required, use either one of `with_subtype` or `with_header`. For more information on the /// behaviour, see `with_dynamic_header`. pub fn new(writer: W) -> Self { PnmEncoder { writer, header: HeaderStrategy::Dynamic, } } /// Encode a specific pnm subtype image. /// /// The magic number and encoding type will be chosen as provided while the rest of the header /// data will be generated dynamically. Trying to encode incompatible images (e.g. encoding an /// RGB image as Graymap) will result in an error. /// /// This will overwrite the effect of earlier calls to `with_header` and `with_dynamic_header`. pub fn with_subtype(self, subtype: PnmSubtype) -> Self { PnmEncoder { writer: self.writer, header: HeaderStrategy::Subtype(subtype), } } /// Enforce the use of a chosen header. /// /// While this option gives the most control over the actual written data, the encoding process /// will error in case the header data and image parameters do not agree. It is the users /// obligation to ensure that the width and height are set accordingly, for example. /// /// Choose this option if you want a lossless decoding/encoding round trip. /// /// This will overwrite the effect of earlier calls to `with_subtype` and `with_dynamic_header`. pub fn with_header(self, header: PnmHeader) -> Self { PnmEncoder { writer: self.writer, header: HeaderStrategy::Chosen(header), } } /// Create the header dynamically for each image. /// /// This is the default option upon creation of the encoder. With this, most images should be /// encodable but the specific format chosen is out of the users control. The pnm subtype is /// chosen arbitrarily by the library. /// /// This will overwrite the effect of earlier calls to `with_subtype` and `with_header`. pub fn with_dynamic_header(self) -> Self { PnmEncoder { writer: self.writer, header: HeaderStrategy::Dynamic, } } /// Encode an image whose samples are represented as `u8`. /// /// Some `pnm` subtypes are incompatible with some color options, a chosen header most /// certainly with any deviation from the original decoded image. pub fn encode<'s, S>( &mut self, image: S, width: u32, height: u32, color: ColorType, ) -> ImageResult<()> where S: Into>, { let image = image.into(); match self.header { HeaderStrategy::Dynamic => { self.write_dynamic_header(image, width, height, color.into()) } HeaderStrategy::Subtype(subtype) => { self.write_subtyped_header(subtype, image, width, height, color.into()) } HeaderStrategy::Chosen(ref header) => Self::write_with_header( &mut self.writer, header, image, width, height, color.into(), ), } } /// Choose any valid pnm format that the image can be expressed in and write its header. /// /// Returns how the body should be written if successful. fn write_dynamic_header( &mut self, image: FlatSamples, width: u32, height: u32, color: ExtendedColorType, ) -> ImageResult<()> { let depth = u32::from(color.channel_count()); let (maxval, tupltype) = match color { ExtendedColorType::L1 => (1, ArbitraryTuplType::BlackAndWhite), ExtendedColorType::L8 => (0xff, ArbitraryTuplType::Grayscale), ExtendedColorType::L16 => (0xffff, ArbitraryTuplType::Grayscale), ExtendedColorType::La1 => (1, ArbitraryTuplType::BlackAndWhiteAlpha), ExtendedColorType::La8 => (0xff, ArbitraryTuplType::GrayscaleAlpha), ExtendedColorType::La16 => (0xffff, ArbitraryTuplType::GrayscaleAlpha), ExtendedColorType::Rgb8 => (0xff, ArbitraryTuplType::RGB), ExtendedColorType::Rgb16 => (0xffff, ArbitraryTuplType::RGB), ExtendedColorType::Rgba8 => (0xff, ArbitraryTuplType::RGBAlpha), ExtendedColorType::Rgba16 => (0xffff, ArbitraryTuplType::RGBAlpha), _ => { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::Color(color), ), )) } }; let header = PnmHeader { decoded: HeaderRecord::Arbitrary(ArbitraryHeader { width, height, depth, maxval, tupltype: Some(tupltype), }), encoded: None, }; Self::write_with_header(&mut self.writer, &header, image, width, height, color) } /// Try to encode the image with the chosen format, give its corresponding pixel encoding type. fn write_subtyped_header( &mut self, subtype: PnmSubtype, image: FlatSamples, width: u32, height: u32, color: ExtendedColorType, ) -> ImageResult<()> { let header = match (subtype, color) { (PnmSubtype::ArbitraryMap, color) => { return self.write_dynamic_header(image, width, height, color) } (PnmSubtype::Pixmap(encoding), ExtendedColorType::Rgb8) => PnmHeader { decoded: HeaderRecord::Pixmap(PixmapHeader { encoding, width, height, maxval: 255, }), encoded: None, }, (PnmSubtype::Graymap(encoding), ExtendedColorType::L8) => PnmHeader { decoded: HeaderRecord::Graymap(GraymapHeader { encoding, width, height, maxwhite: 255, }), encoded: None, }, (PnmSubtype::Bitmap(encoding), ExtendedColorType::L8) | (PnmSubtype::Bitmap(encoding), ExtendedColorType::L1) => PnmHeader { decoded: HeaderRecord::Bitmap(BitmapHeader { encoding, width, height, }), encoded: None, }, (_, _) => { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::Generic( "Color type can not be represented in the chosen format".to_owned(), ), ))); } }; Self::write_with_header(&mut self.writer, &header, image, width, height, color) } /// Try to encode the image with the chosen header, checking if values are correct. /// /// Returns how the body should be written if successful. fn write_with_header( writer: &mut dyn Write, header: &PnmHeader, image: FlatSamples, width: u32, height: u32, color: ExtendedColorType, ) -> ImageResult<()> { let unchecked = UncheckedHeader { header }; unchecked .check_header_dimensions(width, height)? .check_header_color(color)? .check_sample_values(image)? .write_header(writer)? .write_image(writer) } } impl ImageEncoder for PnmEncoder { fn write_image( mut self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()> { self.encode(buf, width, height, color_type) } } impl<'a> CheckedImageBuffer<'a> { fn check( image: FlatSamples<'a>, width: u32, height: u32, color: ExtendedColorType, ) -> ImageResult> { let components = color.channel_count() as usize; let uwidth = width as usize; let uheight = height as usize; let expected_len = components .checked_mul(uwidth) .and_then(|v| v.checked_mul(uheight)); if Some(image.len()) != expected_len { // Image buffer does not correspond to size and colour. return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))); } Ok(CheckedImageBuffer { _image: image, _width: width, _height: height, _color: color, }) } } impl<'a> UncheckedHeader<'a> { fn check_header_dimensions( self, width: u32, height: u32, ) -> ImageResult> { if self.header.width() != width || self.header.height() != height { // Chosen header does not match Image dimensions. return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))); } Ok(CheckedDimensions { unchecked: self, width, height, }) } } impl<'a> CheckedDimensions<'a> { // Check color compatibility with the header. This will only error when we are certain that // the combination is bogus (e.g. combining Pixmap and Palette) but allows uncertain // combinations (basically a ArbitraryTuplType::Custom with any color of fitting depth). fn check_header_color(self, color: ExtendedColorType) -> ImageResult> { let components = u32::from(color.channel_count()); match *self.unchecked.header { PnmHeader { decoded: HeaderRecord::Bitmap(_), .. } => match color { ExtendedColorType::L1 | ExtendedColorType::L8 | ExtendedColorType::L16 => (), _ => { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::Generic( "PBM format only support luma color types".to_owned(), ), ))) } }, PnmHeader { decoded: HeaderRecord::Graymap(_), .. } => match color { ExtendedColorType::L1 | ExtendedColorType::L8 | ExtendedColorType::L16 => (), _ => { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::Generic( "PGM format only support luma color types".to_owned(), ), ))) } }, PnmHeader { decoded: HeaderRecord::Pixmap(_), .. } => match color { ExtendedColorType::Rgb8 => (), _ => { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::Generic( "PPM format only support ExtendedColorType::Rgb8".to_owned(), ), ))) } }, PnmHeader { decoded: HeaderRecord::Arbitrary(ArbitraryHeader { depth, ref tupltype, .. }), .. } => match (tupltype, color) { (&Some(ArbitraryTuplType::BlackAndWhite), ExtendedColorType::L1) => (), (&Some(ArbitraryTuplType::BlackAndWhiteAlpha), ExtendedColorType::La8) => (), (&Some(ArbitraryTuplType::Grayscale), ExtendedColorType::L1) => (), (&Some(ArbitraryTuplType::Grayscale), ExtendedColorType::L8) => (), (&Some(ArbitraryTuplType::Grayscale), ExtendedColorType::L16) => (), (&Some(ArbitraryTuplType::GrayscaleAlpha), ExtendedColorType::La8) => (), (&Some(ArbitraryTuplType::RGB), ExtendedColorType::Rgb8) => (), (&Some(ArbitraryTuplType::RGBAlpha), ExtendedColorType::Rgba8) => (), (&None, _) if depth == components => (), (&Some(ArbitraryTuplType::Custom(_)), _) if depth == components => (), _ if depth != components => { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::Generic(format!( "Depth mismatch: header {} vs. color {}", depth, components )), ))) } _ => { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::Generic( "Invalid color type for selected PAM color type".to_owned(), ), ))) } }, } Ok(CheckedHeaderColor { dimensions: self, color, }) } } impl<'a> CheckedHeaderColor<'a> { fn check_sample_values(self, image: FlatSamples<'a>) -> ImageResult> { let header_maxval = match self.dimensions.unchecked.header.decoded { HeaderRecord::Bitmap(_) => 1, HeaderRecord::Graymap(GraymapHeader { maxwhite, .. }) => maxwhite, HeaderRecord::Pixmap(PixmapHeader { maxval, .. }) => maxval, HeaderRecord::Arbitrary(ArbitraryHeader { maxval, .. }) => maxval, }; // We trust the image color bit count to be correct at least. let max_sample = match self.color { ExtendedColorType::Unknown(n) if n <= 16 => (1 << n) - 1, ExtendedColorType::L1 => 1, ExtendedColorType::L8 | ExtendedColorType::La8 | ExtendedColorType::Rgb8 | ExtendedColorType::Rgba8 | ExtendedColorType::Bgr8 | ExtendedColorType::Bgra8 => 0xff, ExtendedColorType::L16 | ExtendedColorType::La16 | ExtendedColorType::Rgb16 | ExtendedColorType::Rgba16 => 0xffff, _ => { // Unsupported target color type. return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::Color(self.color), ), )); } }; // Avoid the performance heavy check if possible, e.g. if the header has been chosen by us. if header_maxval < max_sample && !image.all_smaller(header_maxval) { // Sample value greater than allowed for chosen header. return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Pnm.into(), UnsupportedErrorKind::GenericFeature( "Sample value greater than allowed for chosen header".to_owned(), ), ), )); } let encoding = image.encoding_for(&self.dimensions.unchecked.header.decoded); let image = CheckedImageBuffer::check( image, self.dimensions.width, self.dimensions.height, self.color, )?; Ok(CheckedHeader { color: self, encoding, _image: image, }) } } impl<'a> CheckedHeader<'a> { fn write_header(self, writer: &mut dyn Write) -> ImageResult> { self.header().write(writer)?; Ok(self.encoding) } fn header(&self) -> &PnmHeader { self.color.dimensions.unchecked.header } } struct SampleWriter<'a>(&'a mut dyn Write); impl<'a> SampleWriter<'a> { fn write_samples_ascii(self, samples: V) -> io::Result<()> where V: Iterator, V::Item: fmt::Display, { let mut auto_break_writer = AutoBreak::new(self.0, 70); for value in samples { write!(auto_break_writer, "{} ", value)?; } auto_break_writer.flush() } fn write_pbm_bits(self, samples: &[V], width: u32) -> io::Result<()> /* Default gives 0 for all primitives. TODO: replace this with `Zeroable` once it hits stable */ where V: Default + Eq + Copy, { // The length of an encoded scanline let line_width = (width - 1) / 8 + 1; // We'll be writing single bytes, so buffer let mut line_buffer = Vec::with_capacity(line_width as usize); for line in samples.chunks(width as usize) { for byte_bits in line.chunks(8) { let mut byte = 0u8; for i in 0..8 { // Black pixels are encoded as 1s if let Some(&v) = byte_bits.get(i) { if v == V::default() { byte |= 1u8 << (7 - i) } } } line_buffer.push(byte) } self.0.write_all(line_buffer.as_slice())?; line_buffer.clear(); } self.0.flush() } } impl<'a> FlatSamples<'a> { fn len(&self) -> usize { match *self { FlatSamples::U8(arr) => arr.len(), FlatSamples::U16(arr) => arr.len(), } } fn all_smaller(&self, max_val: u32) -> bool { match *self { FlatSamples::U8(arr) => arr.iter().any(|&val| u32::from(val) > max_val), FlatSamples::U16(arr) => arr.iter().any(|&val| u32::from(val) > max_val), } } fn encoding_for(&self, header: &HeaderRecord) -> TupleEncoding<'a> { match *header { HeaderRecord::Bitmap(BitmapHeader { encoding: SampleEncoding::Binary, width, .. }) => TupleEncoding::PbmBits { samples: *self, width, }, HeaderRecord::Bitmap(BitmapHeader { encoding: SampleEncoding::Ascii, .. }) => TupleEncoding::Ascii { samples: *self }, HeaderRecord::Arbitrary(_) => TupleEncoding::Bytes { samples: *self }, HeaderRecord::Graymap(GraymapHeader { encoding: SampleEncoding::Ascii, .. }) | HeaderRecord::Pixmap(PixmapHeader { encoding: SampleEncoding::Ascii, .. }) => TupleEncoding::Ascii { samples: *self }, HeaderRecord::Graymap(GraymapHeader { encoding: SampleEncoding::Binary, .. }) | HeaderRecord::Pixmap(PixmapHeader { encoding: SampleEncoding::Binary, .. }) => TupleEncoding::Bytes { samples: *self }, } } } impl<'a> From<&'a [u8]> for FlatSamples<'a> { fn from(samples: &'a [u8]) -> Self { FlatSamples::U8(samples) } } impl<'a> From<&'a [u16]> for FlatSamples<'a> { fn from(samples: &'a [u16]) -> Self { FlatSamples::U16(samples) } } impl<'a> TupleEncoding<'a> { fn write_image(&self, writer: &mut dyn Write) -> ImageResult<()> { match *self { TupleEncoding::PbmBits { samples: FlatSamples::U8(samples), width, } => SampleWriter(writer) .write_pbm_bits(samples, width) .map_err(ImageError::IoError), TupleEncoding::PbmBits { samples: FlatSamples::U16(samples), width, } => SampleWriter(writer) .write_pbm_bits(samples, width) .map_err(ImageError::IoError), TupleEncoding::Bytes { samples: FlatSamples::U8(samples), } => writer.write_all(samples).map_err(ImageError::IoError), TupleEncoding::Bytes { samples: FlatSamples::U16(samples), } => samples.iter().try_for_each(|&sample| { writer .write_u16::(sample) .map_err(ImageError::IoError) }), TupleEncoding::Ascii { samples: FlatSamples::U8(samples), } => SampleWriter(writer) .write_samples_ascii(samples.iter()) .map_err(ImageError::IoError), TupleEncoding::Ascii { samples: FlatSamples::U16(samples), } => SampleWriter(writer) .write_samples_ascii(samples.iter()) .map_err(ImageError::IoError), } } } image-0.24.7/src/codecs/pnm/header.rs000064400000000000000000000254221046102023000154240ustar 00000000000000use std::{fmt, io}; /// The kind of encoding used to store sample values #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum SampleEncoding { /// Samples are unsigned binary integers in big endian Binary, /// Samples are encoded as decimal ascii strings separated by whitespace Ascii, } /// Denotes the category of the magic number #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum PnmSubtype { /// Magic numbers P1 and P4 Bitmap(SampleEncoding), /// Magic numbers P2 and P5 Graymap(SampleEncoding), /// Magic numbers P3 and P6 Pixmap(SampleEncoding), /// Magic number P7 ArbitraryMap, } /// Stores the complete header data of a file. /// /// Internally, provides mechanisms for lossless reencoding. After reading a file with the decoder /// it is possible to recover the header and construct an encoder. Using the encoder on the just /// loaded image should result in a byte copy of the original file (for single image pnms without /// additional trailing data). pub struct PnmHeader { pub(crate) decoded: HeaderRecord, pub(crate) encoded: Option>, } pub(crate) enum HeaderRecord { Bitmap(BitmapHeader), Graymap(GraymapHeader), Pixmap(PixmapHeader), Arbitrary(ArbitraryHeader), } /// Header produced by a `pbm` file ("Portable Bit Map") #[derive(Clone, Copy, Debug)] pub struct BitmapHeader { /// Binary or Ascii encoded file pub encoding: SampleEncoding, /// Height of the image file pub height: u32, /// Width of the image file pub width: u32, } /// Header produced by a `pgm` file ("Portable Gray Map") #[derive(Clone, Copy, Debug)] pub struct GraymapHeader { /// Binary or Ascii encoded file pub encoding: SampleEncoding, /// Height of the image file pub height: u32, /// Width of the image file pub width: u32, /// Maximum sample value within the image pub maxwhite: u32, } /// Header produced by a `ppm` file ("Portable Pixel Map") #[derive(Clone, Copy, Debug)] pub struct PixmapHeader { /// Binary or Ascii encoded file pub encoding: SampleEncoding, /// Height of the image file pub height: u32, /// Width of the image file pub width: u32, /// Maximum sample value within the image pub maxval: u32, } /// Header produced by a `pam` file ("Portable Arbitrary Map") #[derive(Clone, Debug)] pub struct ArbitraryHeader { /// Height of the image file pub height: u32, /// Width of the image file pub width: u32, /// Number of color channels pub depth: u32, /// Maximum sample value within the image pub maxval: u32, /// Color interpretation of image pixels pub tupltype: Option, } /// Standardized tuple type specifiers in the header of a `pam`. #[derive(Clone, Debug)] pub enum ArbitraryTuplType { /// Pixels are either black (0) or white (1) BlackAndWhite, /// Pixels are either black (0) or white (1) and a second alpha channel BlackAndWhiteAlpha, /// Pixels represent the amount of white Grayscale, /// Grayscale with an additional alpha channel GrayscaleAlpha, /// Three channels: Red, Green, Blue RGB, /// Four channels: Red, Green, Blue, Alpha RGBAlpha, /// An image format which is not standardized Custom(String), } impl ArbitraryTuplType { pub(crate) fn name(&self) -> &str { match self { ArbitraryTuplType::BlackAndWhite => "BLACKANDWHITE", ArbitraryTuplType::BlackAndWhiteAlpha => "BLACKANDWHITE_ALPHA", ArbitraryTuplType::Grayscale => "GRAYSCALE", ArbitraryTuplType::GrayscaleAlpha => "GRAYSCALE_ALPHA", ArbitraryTuplType::RGB => "RGB", ArbitraryTuplType::RGBAlpha => "RGB_ALPHA", ArbitraryTuplType::Custom(custom) => custom, } } } impl PnmSubtype { /// Get the two magic constant bytes corresponding to this format subtype. pub fn magic_constant(self) -> &'static [u8; 2] { match self { PnmSubtype::Bitmap(SampleEncoding::Ascii) => b"P1", PnmSubtype::Graymap(SampleEncoding::Ascii) => b"P2", PnmSubtype::Pixmap(SampleEncoding::Ascii) => b"P3", PnmSubtype::Bitmap(SampleEncoding::Binary) => b"P4", PnmSubtype::Graymap(SampleEncoding::Binary) => b"P5", PnmSubtype::Pixmap(SampleEncoding::Binary) => b"P6", PnmSubtype::ArbitraryMap => b"P7", } } /// Whether samples are stored as binary or as decimal ascii pub fn sample_encoding(self) -> SampleEncoding { match self { PnmSubtype::ArbitraryMap => SampleEncoding::Binary, PnmSubtype::Bitmap(enc) => enc, PnmSubtype::Graymap(enc) => enc, PnmSubtype::Pixmap(enc) => enc, } } } impl PnmHeader { /// Retrieve the format subtype from which the header was created. pub fn subtype(&self) -> PnmSubtype { match self.decoded { HeaderRecord::Bitmap(BitmapHeader { encoding, .. }) => PnmSubtype::Bitmap(encoding), HeaderRecord::Graymap(GraymapHeader { encoding, .. }) => PnmSubtype::Graymap(encoding), HeaderRecord::Pixmap(PixmapHeader { encoding, .. }) => PnmSubtype::Pixmap(encoding), HeaderRecord::Arbitrary(ArbitraryHeader { .. }) => PnmSubtype::ArbitraryMap, } } /// The width of the image this header is for. pub fn width(&self) -> u32 { match self.decoded { HeaderRecord::Bitmap(BitmapHeader { width, .. }) => width, HeaderRecord::Graymap(GraymapHeader { width, .. }) => width, HeaderRecord::Pixmap(PixmapHeader { width, .. }) => width, HeaderRecord::Arbitrary(ArbitraryHeader { width, .. }) => width, } } /// The height of the image this header is for. pub fn height(&self) -> u32 { match self.decoded { HeaderRecord::Bitmap(BitmapHeader { height, .. }) => height, HeaderRecord::Graymap(GraymapHeader { height, .. }) => height, HeaderRecord::Pixmap(PixmapHeader { height, .. }) => height, HeaderRecord::Arbitrary(ArbitraryHeader { height, .. }) => height, } } /// The biggest value a sample can have. In other words, the colour resolution. pub fn maximal_sample(&self) -> u32 { match self.decoded { HeaderRecord::Bitmap(BitmapHeader { .. }) => 1, HeaderRecord::Graymap(GraymapHeader { maxwhite, .. }) => maxwhite, HeaderRecord::Pixmap(PixmapHeader { maxval, .. }) => maxval, HeaderRecord::Arbitrary(ArbitraryHeader { maxval, .. }) => maxval, } } /// Retrieve the underlying bitmap header if any pub fn as_bitmap(&self) -> Option<&BitmapHeader> { match self.decoded { HeaderRecord::Bitmap(ref bitmap) => Some(bitmap), _ => None, } } /// Retrieve the underlying graymap header if any pub fn as_graymap(&self) -> Option<&GraymapHeader> { match self.decoded { HeaderRecord::Graymap(ref graymap) => Some(graymap), _ => None, } } /// Retrieve the underlying pixmap header if any pub fn as_pixmap(&self) -> Option<&PixmapHeader> { match self.decoded { HeaderRecord::Pixmap(ref pixmap) => Some(pixmap), _ => None, } } /// Retrieve the underlying arbitrary header if any pub fn as_arbitrary(&self) -> Option<&ArbitraryHeader> { match self.decoded { HeaderRecord::Arbitrary(ref arbitrary) => Some(arbitrary), _ => None, } } /// Write the header back into a binary stream pub fn write(&self, writer: &mut dyn io::Write) -> io::Result<()> { writer.write_all(self.subtype().magic_constant())?; match *self { PnmHeader { encoded: Some(ref content), .. } => writer.write_all(content), PnmHeader { decoded: HeaderRecord::Bitmap(BitmapHeader { encoding: _encoding, width, height, }), .. } => writeln!(writer, "\n{} {}", width, height), PnmHeader { decoded: HeaderRecord::Graymap(GraymapHeader { encoding: _encoding, width, height, maxwhite, }), .. } => writeln!(writer, "\n{} {} {}", width, height, maxwhite), PnmHeader { decoded: HeaderRecord::Pixmap(PixmapHeader { encoding: _encoding, width, height, maxval, }), .. } => writeln!(writer, "\n{} {} {}", width, height, maxval), PnmHeader { decoded: HeaderRecord::Arbitrary(ArbitraryHeader { width, height, depth, maxval, ref tupltype, }), .. } => { struct TupltypeWriter<'a>(&'a Option); impl<'a> fmt::Display for TupltypeWriter<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { Some(tt) => writeln!(f, "TUPLTYPE {}", tt.name()), None => Ok(()), } } } writeln!( writer, "\nWIDTH {}\nHEIGHT {}\nDEPTH {}\nMAXVAL {}\n{}ENDHDR", width, height, depth, maxval, TupltypeWriter(tupltype) ) } } } } impl From for PnmHeader { fn from(header: BitmapHeader) -> Self { PnmHeader { decoded: HeaderRecord::Bitmap(header), encoded: None, } } } impl From for PnmHeader { fn from(header: GraymapHeader) -> Self { PnmHeader { decoded: HeaderRecord::Graymap(header), encoded: None, } } } impl From for PnmHeader { fn from(header: PixmapHeader) -> Self { PnmHeader { decoded: HeaderRecord::Pixmap(header), encoded: None, } } } impl From for PnmHeader { fn from(header: ArbitraryHeader) -> Self { PnmHeader { decoded: HeaderRecord::Arbitrary(header), encoded: None, } } } image-0.24.7/src/codecs/pnm/mod.rs000064400000000000000000000136501046102023000147530ustar 00000000000000//! Decoding of netpbm image formats (pbm, pgm, ppm and pam). //! //! The formats pbm, pgm and ppm are fully supported. The pam decoder recognizes the tuple types //! `BLACKANDWHITE`, `GRAYSCALE` and `RGB` and explicitly recognizes but rejects their `_ALPHA` //! variants for now as alpha color types are unsupported. use self::autobreak::AutoBreak; pub use self::decoder::PnmDecoder; pub use self::encoder::PnmEncoder; use self::header::HeaderRecord; pub use self::header::{ ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader, }; pub use self::header::{PnmHeader, PnmSubtype, SampleEncoding}; mod autobreak; mod decoder; mod encoder; mod header; #[cfg(test)] mod tests { use super::*; use crate::color::ColorType; use crate::image::ImageDecoder; use byteorder::{ByteOrder, NativeEndian}; fn execute_roundtrip_default(buffer: &[u8], width: u32, height: u32, color: ColorType) { let mut encoded_buffer = Vec::new(); { let mut encoder = PnmEncoder::new(&mut encoded_buffer); encoder .encode(buffer, width, height, color) .expect("Failed to encode the image buffer"); } let (header, loaded_color, loaded_image) = { let decoder = PnmDecoder::new(&encoded_buffer[..]).unwrap(); let color_type = decoder.color_type(); let mut image = vec![0; decoder.total_bytes() as usize]; decoder .read_image(&mut image) .expect("Failed to decode the image"); let (_, header) = PnmDecoder::new(&encoded_buffer[..]).unwrap().into_inner(); (header, color_type, image) }; assert_eq!(header.width(), width); assert_eq!(header.height(), height); assert_eq!(loaded_color, color); assert_eq!(loaded_image.as_slice(), buffer); } fn execute_roundtrip_with_subtype( buffer: &[u8], width: u32, height: u32, color: ColorType, subtype: PnmSubtype, ) { let mut encoded_buffer = Vec::new(); { let mut encoder = PnmEncoder::new(&mut encoded_buffer).with_subtype(subtype); encoder .encode(buffer, width, height, color) .expect("Failed to encode the image buffer"); } let (header, loaded_color, loaded_image) = { let decoder = PnmDecoder::new(&encoded_buffer[..]).unwrap(); let color_type = decoder.color_type(); let mut image = vec![0; decoder.total_bytes() as usize]; decoder .read_image(&mut image) .expect("Failed to decode the image"); let (_, header) = PnmDecoder::new(&encoded_buffer[..]).unwrap().into_inner(); (header, color_type, image) }; assert_eq!(header.width(), width); assert_eq!(header.height(), height); assert_eq!(header.subtype(), subtype); assert_eq!(loaded_color, color); assert_eq!(loaded_image.as_slice(), buffer); } fn execute_roundtrip_u16(buffer: &[u16], width: u32, height: u32, color: ColorType) { let mut encoded_buffer = Vec::new(); { let mut encoder = PnmEncoder::new(&mut encoded_buffer); encoder .encode(buffer, width, height, color) .expect("Failed to encode the image buffer"); } let (header, loaded_color, loaded_image) = { let decoder = PnmDecoder::new(&encoded_buffer[..]).unwrap(); let color_type = decoder.color_type(); let mut image = vec![0; decoder.total_bytes() as usize]; decoder .read_image(&mut image) .expect("Failed to decode the image"); let (_, header) = PnmDecoder::new(&encoded_buffer[..]).unwrap().into_inner(); (header, color_type, image) }; let mut buffer_u8 = vec![0; buffer.len() * 2]; NativeEndian::write_u16_into(buffer, &mut buffer_u8[..]); assert_eq!(header.width(), width); assert_eq!(header.height(), height); assert_eq!(loaded_color, color); assert_eq!(loaded_image, buffer_u8); } #[test] fn roundtrip_gray() { #[rustfmt::skip] let buf: [u8; 16] = [ 0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 255, 0, 255, 0, 0, 0, ]; execute_roundtrip_default(&buf, 4, 4, ColorType::L8); execute_roundtrip_with_subtype(&buf, 4, 4, ColorType::L8, PnmSubtype::ArbitraryMap); execute_roundtrip_with_subtype( &buf, 4, 4, ColorType::L8, PnmSubtype::Graymap(SampleEncoding::Ascii), ); execute_roundtrip_with_subtype( &buf, 4, 4, ColorType::L8, PnmSubtype::Graymap(SampleEncoding::Binary), ); } #[test] fn roundtrip_rgb() { #[rustfmt::skip] let buf: [u8; 27] = [ 0, 0, 0, 0, 0, 255, 0, 255, 0, 0, 255, 255, 255, 0, 0, 255, 0, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255, ]; execute_roundtrip_default(&buf, 3, 3, ColorType::Rgb8); execute_roundtrip_with_subtype(&buf, 3, 3, ColorType::Rgb8, PnmSubtype::ArbitraryMap); execute_roundtrip_with_subtype( &buf, 3, 3, ColorType::Rgb8, PnmSubtype::Pixmap(SampleEncoding::Binary), ); execute_roundtrip_with_subtype( &buf, 3, 3, ColorType::Rgb8, PnmSubtype::Pixmap(SampleEncoding::Ascii), ); } #[test] fn roundtrip_u16() { let buf: [u16; 6] = [0, 1, 0xFFFF, 0x1234, 0x3412, 0xBEAF]; execute_roundtrip_u16(&buf, 6, 1, ColorType::L16); } } image-0.24.7/src/codecs/qoi.rs000064400000000000000000000053471046102023000141760ustar 00000000000000//! Decoding and encoding of QOI images use crate::{ error::{DecodingError, EncodingError}, ColorType, ImageDecoder, ImageEncoder, ImageError, ImageFormat, ImageResult, }; use std::io::{Cursor, Read, Write}; /// QOI decoder pub struct QoiDecoder { decoder: qoi::Decoder, } impl QoiDecoder where R: Read, { /// Creates a new decoder that decodes from the stream ```reader``` pub fn new(reader: R) -> ImageResult { let decoder = qoi::Decoder::from_stream(reader).map_err(decoding_error)?; Ok(Self { decoder }) } } impl<'a, R: Read + 'a> ImageDecoder<'a> for QoiDecoder { type Reader = Cursor>; fn dimensions(&self) -> (u32, u32) { (self.decoder.header().width, self.decoder.header().height) } fn color_type(&self) -> ColorType { match self.decoder.header().channels { qoi::Channels::Rgb => ColorType::Rgb8, qoi::Channels::Rgba => ColorType::Rgba8, } } fn into_reader(mut self) -> ImageResult { let buffer = self.decoder.decode_to_vec().map_err(decoding_error)?; Ok(Cursor::new(buffer)) } } fn decoding_error(error: qoi::Error) -> ImageError { ImageError::Decoding(DecodingError::new(ImageFormat::Qoi.into(), error)) } fn encoding_error(error: qoi::Error) -> ImageError { ImageError::Encoding(EncodingError::new(ImageFormat::Qoi.into(), error)) } /// QOI encoder pub struct QoiEncoder { writer: W, } impl QoiEncoder { /// Creates a new encoder that writes its output to ```writer``` pub fn new(writer: W) -> Self { Self { writer } } } impl ImageEncoder for QoiEncoder { fn write_image( mut self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()> { if !matches!(color_type, ColorType::Rgba8 | ColorType::Rgb8) { return Err(ImageError::Encoding(EncodingError::new( ImageFormat::Qoi.into(), format!("unsupported color type {color_type:?}. Supported are Rgba8 and Rgb8."), ))); } // Encode data in QOI let data = qoi::encode_to_vec(buf, width, height).map_err(encoding_error)?; // Write data to buffer self.writer.write_all(&data[..])?; self.writer.flush()?; Ok(()) } } #[cfg(test)] mod tests { use super::*; use std::fs::File; #[test] fn decode_test_image() { let decoder = QoiDecoder::new(File::open("tests/images/qoi/basic-test.qoi").unwrap()) .expect("Unable to read QOI file"); assert_eq!((5, 5), decoder.dimensions()); assert_eq!(ColorType::Rgba8, decoder.color_type()); } } image-0.24.7/src/codecs/tga/decoder.rs000064400000000000000000000420601046102023000155570ustar 00000000000000use super::header::{Header, ImageType, ALPHA_BIT_MASK, SCREEN_ORIGIN_BIT_MASK}; use crate::{ color::{ColorType, ExtendedColorType}, error::{ ImageError, ImageResult, LimitError, LimitErrorKind, UnsupportedError, UnsupportedErrorKind, }, image::{ImageDecoder, ImageFormat, ImageReadBuffer}, }; use byteorder::ReadBytesExt; use std::{ convert::TryFrom, io::{self, Read, Seek}, mem, }; struct ColorMap { /// sizes in bytes start_offset: usize, entry_size: usize, bytes: Vec, } impl ColorMap { pub(crate) fn from_reader( r: &mut dyn Read, start_offset: u16, num_entries: u16, bits_per_entry: u8, ) -> ImageResult { let bytes_per_entry = (bits_per_entry as usize + 7) / 8; let mut bytes = vec![0; bytes_per_entry * num_entries as usize]; r.read_exact(&mut bytes)?; Ok(ColorMap { entry_size: bytes_per_entry, start_offset: start_offset as usize, bytes, }) } /// Get one entry from the color map pub(crate) fn get(&self, index: usize) -> Option<&[u8]> { let entry = self.start_offset + self.entry_size * index; self.bytes.get(entry..entry + self.entry_size) } } /// The representation of a TGA decoder pub struct TgaDecoder { r: R, width: usize, height: usize, bytes_per_pixel: usize, has_loaded_metadata: bool, image_type: ImageType, color_type: ColorType, original_color_type: Option, header: Header, color_map: Option, // Used in read_scanline line_read: Option, line_remain_buff: Vec, } impl TgaDecoder { /// Create a new decoder that decodes from the stream `r` pub fn new(r: R) -> ImageResult> { let mut decoder = TgaDecoder { r, width: 0, height: 0, bytes_per_pixel: 0, has_loaded_metadata: false, image_type: ImageType::Unknown, color_type: ColorType::L8, original_color_type: None, header: Header::default(), color_map: None, line_read: None, line_remain_buff: Vec::new(), }; decoder.read_metadata()?; Ok(decoder) } fn read_header(&mut self) -> ImageResult<()> { self.header = Header::from_reader(&mut self.r)?; self.image_type = ImageType::new(self.header.image_type); self.width = self.header.image_width as usize; self.height = self.header.image_height as usize; self.bytes_per_pixel = (self.header.pixel_depth as usize + 7) / 8; Ok(()) } fn read_metadata(&mut self) -> ImageResult<()> { if !self.has_loaded_metadata { self.read_header()?; self.read_image_id()?; self.read_color_map()?; self.read_color_information()?; self.has_loaded_metadata = true; } Ok(()) } /// Loads the color information for the decoder /// /// To keep things simple, we won't handle bit depths that aren't divisible /// by 8 and are larger than 32. fn read_color_information(&mut self) -> ImageResult<()> { if self.header.pixel_depth % 8 != 0 || self.header.pixel_depth > 32 { // Bit depth must be divisible by 8, and must be less than or equal // to 32. return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Tga.into(), UnsupportedErrorKind::Color(ExtendedColorType::Unknown( self.header.pixel_depth, )), ), )); } let num_alpha_bits = self.header.image_desc & ALPHA_BIT_MASK; let other_channel_bits = if self.header.map_type != 0 { self.header.map_entry_size } else { if num_alpha_bits > self.header.pixel_depth { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Tga.into(), UnsupportedErrorKind::Color(ExtendedColorType::Unknown( self.header.pixel_depth, )), ), )); } self.header.pixel_depth - num_alpha_bits }; let color = self.image_type.is_color(); match (num_alpha_bits, other_channel_bits, color) { // really, the encoding is BGR and BGRA, this is fixed // up with `TgaDecoder::reverse_encoding`. (0, 32, true) => self.color_type = ColorType::Rgba8, (8, 24, true) => self.color_type = ColorType::Rgba8, (0, 24, true) => self.color_type = ColorType::Rgb8, (8, 8, false) => self.color_type = ColorType::La8, (0, 8, false) => self.color_type = ColorType::L8, (8, 0, false) => { // alpha-only image is treated as L8 self.color_type = ColorType::L8; self.original_color_type = Some(ExtendedColorType::A8); } _ => { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Tga.into(), UnsupportedErrorKind::Color(ExtendedColorType::Unknown( self.header.pixel_depth, )), ), )) } } Ok(()) } /// Read the image id field /// /// We're not interested in this field, so this function skips it if it /// is present fn read_image_id(&mut self) -> ImageResult<()> { self.r .seek(io::SeekFrom::Current(i64::from(self.header.id_length)))?; Ok(()) } fn read_color_map(&mut self) -> ImageResult<()> { if self.header.map_type == 1 { // FIXME: we could reverse the map entries, which avoids having to reverse all pixels // in the final output individually. self.color_map = Some(ColorMap::from_reader( &mut self.r, self.header.map_origin, self.header.map_length, self.header.map_entry_size, )?); } Ok(()) } /// Expands indices into its mapped color fn expand_color_map(&self, pixel_data: &[u8]) -> io::Result> { #[inline] fn bytes_to_index(bytes: &[u8]) -> usize { let mut result = 0usize; for byte in bytes.iter() { result = result << 8 | *byte as usize; } result } let bytes_per_entry = (self.header.map_entry_size as usize + 7) / 8; let mut result = Vec::with_capacity(self.width * self.height * bytes_per_entry); if self.bytes_per_pixel == 0 { return Err(io::ErrorKind::Other.into()); } let color_map = self .color_map .as_ref() .ok_or_else(|| io::Error::from(io::ErrorKind::Other))?; for chunk in pixel_data.chunks(self.bytes_per_pixel) { let index = bytes_to_index(chunk); if let Some(color) = color_map.get(index) { result.extend_from_slice(color); } else { return Err(io::ErrorKind::Other.into()); } } Ok(result) } /// Reads a run length encoded data for given number of bytes fn read_encoded_data(&mut self, num_bytes: usize) -> io::Result> { let mut pixel_data = Vec::with_capacity(num_bytes); let mut repeat_buf = Vec::with_capacity(self.bytes_per_pixel); while pixel_data.len() < num_bytes { let run_packet = self.r.read_u8()?; // If the highest bit in `run_packet` is set, then we repeat pixels // // Note: the TGA format adds 1 to both counts because having a count // of 0 would be pointless. if (run_packet & 0x80) != 0 { // high bit set, so we will repeat the data let repeat_count = ((run_packet & !0x80) + 1) as usize; self.r .by_ref() .take(self.bytes_per_pixel as u64) .read_to_end(&mut repeat_buf)?; // get the repeating pixels from the bytes of the pixel stored in `repeat_buf` let data = repeat_buf .iter() .cycle() .take(repeat_count * self.bytes_per_pixel); pixel_data.extend(data); repeat_buf.clear(); } else { // not set, so `run_packet+1` is the number of non-encoded pixels let num_raw_bytes = (run_packet + 1) as usize * self.bytes_per_pixel; self.r .by_ref() .take(num_raw_bytes as u64) .read_to_end(&mut pixel_data)?; } } if pixel_data.len() > num_bytes { // FIXME: the last packet contained more data than we asked for! // This is at least a warning. We truncate the data since some methods rely on the // length to be accurate in the success case. pixel_data.truncate(num_bytes); } Ok(pixel_data) } /// Reads a run length encoded packet fn read_all_encoded_data(&mut self) -> ImageResult> { let num_bytes = self.width * self.height * self.bytes_per_pixel; Ok(self.read_encoded_data(num_bytes)?) } /// Reads a run length encoded line fn read_encoded_line(&mut self) -> io::Result> { let line_num_bytes = self.width * self.bytes_per_pixel; let remain_len = self.line_remain_buff.len(); if remain_len >= line_num_bytes { // `Vec::split_to` if std had it let bytes = { let bytes_after = self.line_remain_buff.split_off(line_num_bytes); mem::replace(&mut self.line_remain_buff, bytes_after) }; return Ok(bytes); } let num_bytes = line_num_bytes - remain_len; let line_data = self.read_encoded_data(num_bytes)?; let mut pixel_data = Vec::with_capacity(line_num_bytes); pixel_data.append(&mut self.line_remain_buff); pixel_data.extend_from_slice(&line_data[..num_bytes]); // put the remain data to line_remain_buff. // expects `self.line_remain_buff` to be empty from // the above `pixel_data.append` call debug_assert!(self.line_remain_buff.is_empty()); self.line_remain_buff .extend_from_slice(&line_data[num_bytes..]); Ok(pixel_data) } /// Reverse from BGR encoding to RGB encoding /// /// TGA files are stored in the BGRA encoding. This function swaps /// the blue and red bytes in the `pixels` array. fn reverse_encoding_in_output(&mut self, pixels: &mut [u8]) { // We only need to reverse the encoding of color images match self.color_type { ColorType::Rgb8 | ColorType::Rgba8 => { for chunk in pixels.chunks_mut(self.color_type.bytes_per_pixel().into()) { chunk.swap(0, 2); } } _ => {} } } /// Flip the image vertically depending on the screen origin bit /// /// The bit in position 5 of the image descriptor byte is the screen origin bit. /// If it's 1, the origin is in the top left corner. /// If it's 0, the origin is in the bottom left corner. /// This function checks the bit, and if it's 0, flips the image vertically. fn flip_vertically(&mut self, pixels: &mut [u8]) { if self.is_flipped_vertically() { if self.height == 0 { return; } let num_bytes = pixels.len(); let width_bytes = num_bytes / self.height; // Flip the image vertically. for vertical_index in 0..(self.height / 2) { let vertical_target = (self.height - vertical_index) * width_bytes - width_bytes; for horizontal_index in 0..width_bytes { let source = vertical_index * width_bytes + horizontal_index; let target = vertical_target + horizontal_index; pixels.swap(target, source); } } } } /// Check whether the image is vertically flipped /// /// The bit in position 5 of the image descriptor byte is the screen origin bit. /// If it's 1, the origin is in the top left corner. /// If it's 0, the origin is in the bottom left corner. /// This function checks the bit, and if it's 0, flips the image vertically. fn is_flipped_vertically(&self) -> bool { let screen_origin_bit = SCREEN_ORIGIN_BIT_MASK & self.header.image_desc != 0; !screen_origin_bit } fn read_scanline(&mut self, buf: &mut [u8]) -> io::Result { if let Some(line_read) = self.line_read { if line_read == self.height { return Ok(0); } } // read the pixels from the data region let mut pixel_data = if self.image_type.is_encoded() { self.read_encoded_line()? } else { let num_raw_bytes = self.width * self.bytes_per_pixel; let mut buf = vec![0; num_raw_bytes]; self.r.by_ref().read_exact(&mut buf)?; buf }; // expand the indices using the color map if necessary if self.image_type.is_color_mapped() { pixel_data = self.expand_color_map(&pixel_data)?; } self.reverse_encoding_in_output(&mut pixel_data); // copy to the output buffer buf[..pixel_data.len()].copy_from_slice(&pixel_data); self.line_read = Some(self.line_read.unwrap_or(0) + 1); Ok(pixel_data.len()) } } impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TgaDecoder { type Reader = TGAReader; fn dimensions(&self) -> (u32, u32) { (self.width as u32, self.height as u32) } fn color_type(&self) -> ColorType { self.color_type } fn original_color_type(&self) -> ExtendedColorType { self.original_color_type .unwrap_or_else(|| self.color_type().into()) } fn scanline_bytes(&self) -> u64 { // This cannot overflow because TGA has a maximum width of u16::MAX_VALUE and // `bytes_per_pixel` is a u8. u64::from(self.color_type.bytes_per_pixel()) * self.width as u64 } fn into_reader(self) -> ImageResult { Ok(TGAReader { buffer: ImageReadBuffer::new(self.scanline_bytes(), self.total_bytes()), decoder: self, }) } fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); // In indexed images, we might need more bytes than pixels to read them. That's nonsensical // to encode but we'll not want to crash. let mut fallback_buf = vec![]; // read the pixels from the data region let rawbuf = if self.image_type.is_encoded() { let pixel_data = self.read_all_encoded_data()?; if self.bytes_per_pixel <= usize::from(self.color_type.bytes_per_pixel()) { buf[..pixel_data.len()].copy_from_slice(&pixel_data); &buf[..pixel_data.len()] } else { fallback_buf = pixel_data; &fallback_buf[..] } } else { let num_raw_bytes = self.width * self.height * self.bytes_per_pixel; if self.bytes_per_pixel <= usize::from(self.color_type.bytes_per_pixel()) { self.r.by_ref().read_exact(&mut buf[..num_raw_bytes])?; &buf[..num_raw_bytes] } else { fallback_buf.resize(num_raw_bytes, 0u8); self.r .by_ref() .read_exact(&mut fallback_buf[..num_raw_bytes])?; &fallback_buf[..num_raw_bytes] } }; // expand the indices using the color map if necessary if self.image_type.is_color_mapped() { let pixel_data = self.expand_color_map(rawbuf)?; // not enough data to fill the buffer, or would overflow the buffer if pixel_data.len() != buf.len() { return Err(ImageError::Limits(LimitError::from_kind( LimitErrorKind::DimensionError, ))); } buf.copy_from_slice(&pixel_data); } self.reverse_encoding_in_output(buf); self.flip_vertically(buf); Ok(()) } } pub struct TGAReader { buffer: ImageReadBuffer, decoder: TgaDecoder, } impl Read for TGAReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { let decoder = &mut self.decoder; self.buffer.read(buf, |buf| decoder.read_scanline(buf)) } } image-0.24.7/src/codecs/tga/encoder.rs000064400000000000000000000154151046102023000155750ustar 00000000000000use super::header::Header; use crate::{error::EncodingError, ColorType, ImageEncoder, ImageError, ImageFormat, ImageResult}; use std::{convert::TryFrom, error, fmt, io::Write}; /// Errors that can occur during encoding and saving of a TGA image. #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] enum EncoderError { /// Invalid TGA width. WidthInvalid(u32), /// Invalid TGA height. HeightInvalid(u32), } impl fmt::Display for EncoderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { EncoderError::WidthInvalid(s) => f.write_fmt(format_args!("Invalid TGA width: {}", s)), EncoderError::HeightInvalid(s) => { f.write_fmt(format_args!("Invalid TGA height: {}", s)) } } } } impl From for ImageError { fn from(e: EncoderError) -> ImageError { ImageError::Encoding(EncodingError::new(ImageFormat::Tga.into(), e)) } } impl error::Error for EncoderError {} /// TGA encoder. pub struct TgaEncoder { writer: W, } impl TgaEncoder { /// Create a new encoder that writes its output to ```w```. pub fn new(w: W) -> TgaEncoder { TgaEncoder { writer: w } } /// Encodes the image ```buf``` that has dimensions ```width``` /// and ```height``` and ```ColorType``` ```color_type```. /// /// The dimensions of the image must be between 0 and 65535 (inclusive) or /// an error will be returned. pub fn encode( mut self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()> { // Validate dimensions. let width = u16::try_from(width) .map_err(|_| ImageError::from(EncoderError::WidthInvalid(width)))?; let height = u16::try_from(height) .map_err(|_| ImageError::from(EncoderError::HeightInvalid(height)))?; // Write out TGA header. let header = Header::from_pixel_info(color_type, width, height)?; header.write_to(&mut self.writer)?; // Write out Bgr(a)8 or L(a)8 image data. match color_type { ColorType::Rgb8 | ColorType::Rgba8 => { let mut image = Vec::from(buf); for chunk in image.chunks_mut(usize::from(color_type.bytes_per_pixel())) { chunk.swap(0, 2); } self.writer.write_all(&image)?; } _ => { self.writer.write_all(buf)?; } } Ok(()) } } impl ImageEncoder for TgaEncoder { fn write_image( self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()> { self.encode(buf, width, height, color_type) } } #[cfg(test)] mod tests { use super::{EncoderError, TgaEncoder}; use crate::{codecs::tga::TgaDecoder, ColorType, ImageDecoder, ImageError}; use std::{error::Error, io::Cursor}; fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec { let mut encoded_data = Vec::new(); { let encoder = TgaEncoder::new(&mut encoded_data); encoder .encode(&image, width, height, c) .expect("could not encode image"); } let decoder = TgaDecoder::new(Cursor::new(&encoded_data)).expect("failed to decode"); let mut buf = vec![0; decoder.total_bytes() as usize]; decoder.read_image(&mut buf).expect("failed to decode"); buf } #[test] fn test_image_width_too_large() { // TGA cannot encode images larger than 65,535×65,535 // create a 65,536×1 8-bit black image buffer let size = usize::from(u16::MAX) + 1; let dimension = size as u32; let img = vec![0u8; size]; // Try to encode an image that is too large let mut encoded = Vec::new(); let encoder = TgaEncoder::new(&mut encoded); let result = encoder.encode(&img, dimension, 1, ColorType::L8); match result { Err(ImageError::Encoding(err)) => { let err = err .source() .unwrap() .downcast_ref::() .unwrap(); assert_eq!(*err, EncoderError::WidthInvalid(dimension)); } other => panic!( "Encoding an image that is too wide should return a InvalidWidth \ it returned {:?} instead", other ), } } #[test] fn test_image_height_too_large() { // TGA cannot encode images larger than 65,535×65,535 // create a 65,536×1 8-bit black image buffer let size = usize::from(u16::MAX) + 1; let dimension = size as u32; let img = vec![0u8; size]; // Try to encode an image that is too large let mut encoded = Vec::new(); let encoder = TgaEncoder::new(&mut encoded); let result = encoder.encode(&img, 1, dimension, ColorType::L8); match result { Err(ImageError::Encoding(err)) => { let err = err .source() .unwrap() .downcast_ref::() .unwrap(); assert_eq!(*err, EncoderError::HeightInvalid(dimension)); } other => panic!( "Encoding an image that is too tall should return a InvalidHeight \ it returned {:?} instead", other ), } } #[test] fn round_trip_single_pixel_rgb() { let image = [0, 1, 2]; let decoded = round_trip_image(&image, 1, 1, ColorType::Rgb8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } #[test] fn round_trip_single_pixel_rgba() { let image = [0, 1, 2, 3]; let decoded = round_trip_image(&image, 1, 1, ColorType::Rgba8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } #[test] fn round_trip_gray() { let image = [0, 1, 2]; let decoded = round_trip_image(&image, 3, 1, ColorType::L8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } #[test] fn round_trip_graya() { let image = [0, 1, 2, 3, 4, 5]; let decoded = round_trip_image(&image, 1, 3, ColorType::La8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } #[test] fn round_trip_3px_rgb() { let image = [0; 3 * 3 * 3]; // 3x3 pixels, 3 bytes per pixel let _decoded = round_trip_image(&image, 3, 3, ColorType::Rgb8); } } image-0.24.7/src/codecs/tga/header.rs000064400000000000000000000122661046102023000154070ustar 00000000000000use crate::{ error::{UnsupportedError, UnsupportedErrorKind}, ColorType, ImageError, ImageFormat, ImageResult, }; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use std::io::{Read, Write}; pub(crate) const ALPHA_BIT_MASK: u8 = 0b1111; pub(crate) const SCREEN_ORIGIN_BIT_MASK: u8 = 0b10_0000; pub(crate) enum ImageType { NoImageData = 0, /// Uncompressed images. RawColorMap = 1, RawTrueColor = 2, RawGrayScale = 3, /// Run length encoded images. RunColorMap = 9, RunTrueColor = 10, RunGrayScale = 11, Unknown, } impl ImageType { /// Create a new image type from a u8. pub(crate) fn new(img_type: u8) -> ImageType { match img_type { 0 => ImageType::NoImageData, 1 => ImageType::RawColorMap, 2 => ImageType::RawTrueColor, 3 => ImageType::RawGrayScale, 9 => ImageType::RunColorMap, 10 => ImageType::RunTrueColor, 11 => ImageType::RunGrayScale, _ => ImageType::Unknown, } } /// Check if the image format uses colors as opposed to gray scale. pub(crate) fn is_color(&self) -> bool { matches! { *self, ImageType::RawColorMap | ImageType::RawTrueColor | ImageType::RunTrueColor | ImageType::RunColorMap } } /// Does the image use a color map. pub(crate) fn is_color_mapped(&self) -> bool { matches! { *self, ImageType::RawColorMap | ImageType::RunColorMap } } /// Is the image run length encoded. pub(crate) fn is_encoded(&self) -> bool { matches! {*self, ImageType::RunColorMap | ImageType::RunTrueColor | ImageType::RunGrayScale } } } /// Header used by TGA image files. #[derive(Debug, Default)] pub(crate) struct Header { pub(crate) id_length: u8, // length of ID string pub(crate) map_type: u8, // color map type pub(crate) image_type: u8, // image type code pub(crate) map_origin: u16, // starting index of map pub(crate) map_length: u16, // length of map pub(crate) map_entry_size: u8, // size of map entries in bits pub(crate) x_origin: u16, // x-origin of image pub(crate) y_origin: u16, // y-origin of image pub(crate) image_width: u16, // width of image pub(crate) image_height: u16, // height of image pub(crate) pixel_depth: u8, // bits per pixel pub(crate) image_desc: u8, // image descriptor } impl Header { /// Load the header with values from pixel information. pub(crate) fn from_pixel_info( color_type: ColorType, width: u16, height: u16, ) -> ImageResult { let mut header = Self::default(); if width > 0 && height > 0 { let (num_alpha_bits, other_channel_bits, image_type) = match color_type { ColorType::Rgba8 => (8, 24, ImageType::RawTrueColor), ColorType::Rgb8 => (0, 24, ImageType::RawTrueColor), ColorType::La8 => (8, 8, ImageType::RawGrayScale), ColorType::L8 => (0, 8, ImageType::RawGrayScale), _ => { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Tga.into(), UnsupportedErrorKind::Color(color_type.into()), ), )) } }; header.image_type = image_type as u8; header.image_width = width; header.image_height = height; header.pixel_depth = num_alpha_bits + other_channel_bits; header.image_desc = num_alpha_bits & ALPHA_BIT_MASK; header.image_desc |= SCREEN_ORIGIN_BIT_MASK; // Upper left origin. } Ok(header) } /// Load the header with values from the reader. pub(crate) fn from_reader(r: &mut dyn Read) -> ImageResult { Ok(Self { id_length: r.read_u8()?, map_type: r.read_u8()?, image_type: r.read_u8()?, map_origin: r.read_u16::()?, map_length: r.read_u16::()?, map_entry_size: r.read_u8()?, x_origin: r.read_u16::()?, y_origin: r.read_u16::()?, image_width: r.read_u16::()?, image_height: r.read_u16::()?, pixel_depth: r.read_u8()?, image_desc: r.read_u8()?, }) } /// Write out the header values. pub(crate) fn write_to(&self, w: &mut dyn Write) -> ImageResult<()> { w.write_u8(self.id_length)?; w.write_u8(self.map_type)?; w.write_u8(self.image_type)?; w.write_u16::(self.map_origin)?; w.write_u16::(self.map_length)?; w.write_u8(self.map_entry_size)?; w.write_u16::(self.x_origin)?; w.write_u16::(self.y_origin)?; w.write_u16::(self.image_width)?; w.write_u16::(self.image_height)?; w.write_u8(self.pixel_depth)?; w.write_u8(self.image_desc)?; Ok(()) } } image-0.24.7/src/codecs/tga/mod.rs000064400000000000000000000005531046102023000147320ustar 00000000000000//! Decoding of TGA Images //! //! # Related Links //! /// A decoder for TGA images /// /// Currently this decoder does not support 8, 15 and 16 bit color images. pub use self::decoder::TgaDecoder; //TODO add 8, 15, 16 bit color support pub use self::encoder::TgaEncoder; mod decoder; mod encoder; mod header; image-0.24.7/src/codecs/tiff.rs000064400000000000000000000315211046102023000143270ustar 00000000000000//! Decoding and Encoding of TIFF Images //! //! TIFF (Tagged Image File Format) is a versatile image format that supports //! lossless and lossy compression. //! //! # Related Links //! * - The TIFF specification extern crate tiff; use std::convert::TryFrom; use std::io::{self, Cursor, Read, Seek, Write}; use std::marker::PhantomData; use std::mem; use crate::color::{ColorType, ExtendedColorType}; use crate::error::{ DecodingError, EncodingError, ImageError, ImageResult, LimitError, LimitErrorKind, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{ImageDecoder, ImageEncoder, ImageFormat}; use crate::utils; /// Decoder for TIFF images. pub struct TiffDecoder where R: Read + Seek, { dimensions: (u32, u32), color_type: ColorType, // We only use an Option here so we can call with_limits on the decoder without moving. inner: Option>, } impl TiffDecoder where R: Read + Seek, { /// Create a new TiffDecoder. pub fn new(r: R) -> Result, ImageError> { let mut inner = tiff::decoder::Decoder::new(r).map_err(ImageError::from_tiff_decode)?; let dimensions = inner.dimensions().map_err(ImageError::from_tiff_decode)?; let color_type = inner.colortype().map_err(ImageError::from_tiff_decode)?; match inner.find_tag_unsigned_vec::(tiff::tags::Tag::SampleFormat) { Ok(Some(sample_formats)) => { for format in sample_formats { check_sample_format(format)?; } } Ok(None) => { /* assume UInt format */ } Err(other) => return Err(ImageError::from_tiff_decode(other)), }; let color_type = match color_type { tiff::ColorType::Gray(8) => ColorType::L8, tiff::ColorType::Gray(16) => ColorType::L16, tiff::ColorType::GrayA(8) => ColorType::La8, tiff::ColorType::GrayA(16) => ColorType::La16, tiff::ColorType::RGB(8) => ColorType::Rgb8, tiff::ColorType::RGB(16) => ColorType::Rgb16, tiff::ColorType::RGBA(8) => ColorType::Rgba8, tiff::ColorType::RGBA(16) => ColorType::Rgba16, tiff::ColorType::Palette(n) | tiff::ColorType::Gray(n) => { return Err(err_unknown_color_type(n)) } tiff::ColorType::GrayA(n) => return Err(err_unknown_color_type(n.saturating_mul(2))), tiff::ColorType::RGB(n) => return Err(err_unknown_color_type(n.saturating_mul(3))), tiff::ColorType::YCbCr(n) => return Err(err_unknown_color_type(n.saturating_mul(3))), tiff::ColorType::RGBA(n) | tiff::ColorType::CMYK(n) => { return Err(err_unknown_color_type(n.saturating_mul(4))) } }; Ok(TiffDecoder { dimensions, color_type, inner: Some(inner), }) } } fn check_sample_format(sample_format: u16) -> Result<(), ImageError> { match tiff::tags::SampleFormat::from_u16(sample_format) { Some(tiff::tags::SampleFormat::Uint) => Ok(()), Some(other) => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Tiff.into(), UnsupportedErrorKind::GenericFeature(format!( "Unhandled TIFF sample format {:?}", other )), ), )), None => Err(ImageError::Decoding(DecodingError::from_format_hint( ImageFormat::Tiff.into(), ))), } } fn err_unknown_color_type(value: u8) -> ImageError { ImageError::Unsupported(UnsupportedError::from_format_and_kind( ImageFormat::Tiff.into(), UnsupportedErrorKind::Color(ExtendedColorType::Unknown(value)), )) } impl ImageError { fn from_tiff_decode(err: tiff::TiffError) -> ImageError { match err { tiff::TiffError::IoError(err) => ImageError::IoError(err), err @ tiff::TiffError::FormatError(_) | err @ tiff::TiffError::IntSizeError | err @ tiff::TiffError::UsageError(_) => { ImageError::Decoding(DecodingError::new(ImageFormat::Tiff.into(), err)) } tiff::TiffError::UnsupportedError(desc) => { ImageError::Unsupported(UnsupportedError::from_format_and_kind( ImageFormat::Tiff.into(), UnsupportedErrorKind::GenericFeature(desc.to_string()), )) } tiff::TiffError::LimitsExceeded => { ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) } } } fn from_tiff_encode(err: tiff::TiffError) -> ImageError { match err { tiff::TiffError::IoError(err) => ImageError::IoError(err), err @ tiff::TiffError::FormatError(_) | err @ tiff::TiffError::IntSizeError | err @ tiff::TiffError::UsageError(_) => { ImageError::Encoding(EncodingError::new(ImageFormat::Tiff.into(), err)) } tiff::TiffError::UnsupportedError(desc) => { ImageError::Unsupported(UnsupportedError::from_format_and_kind( ImageFormat::Tiff.into(), UnsupportedErrorKind::GenericFeature(desc.to_string()), )) } tiff::TiffError::LimitsExceeded => { ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)) } } } } /// Wrapper struct around a `Cursor>` pub struct TiffReader(Cursor>, PhantomData); impl Read for TiffReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { if self.0.position() == 0 && buf.is_empty() { mem::swap(buf, self.0.get_mut()); Ok(buf.len()) } else { self.0.read_to_end(buf) } } } impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TiffDecoder { type Reader = TiffReader; fn dimensions(&self) -> (u32, u32) { self.dimensions } fn color_type(&self) -> ColorType { self.color_type } fn icc_profile(&mut self) -> Option> { if let Some(decoder) = &mut self.inner { decoder.get_tag_u8_vec(tiff::tags::Tag::Unknown(34675)).ok() } else { None } } fn set_limits(&mut self, limits: crate::io::Limits) -> ImageResult<()> { limits.check_support(&crate::io::LimitSupport::default())?; let (width, height) = self.dimensions(); limits.check_dimensions(width, height)?; let max_alloc = limits.max_alloc.unwrap_or(u64::MAX); let max_intermediate_alloc = max_alloc.saturating_sub(self.total_bytes()); let mut tiff_limits: tiff::decoder::Limits = Default::default(); tiff_limits.decoding_buffer_size = usize::try_from(max_alloc - max_intermediate_alloc).unwrap_or(usize::MAX); tiff_limits.intermediate_buffer_size = usize::try_from(max_intermediate_alloc).unwrap_or(usize::MAX); tiff_limits.ifd_value_size = tiff_limits.intermediate_buffer_size; self.inner = Some(self.inner.take().unwrap().with_limits(tiff_limits)); Ok(()) } fn into_reader(self) -> ImageResult { let buf = match self .inner .unwrap() .read_image() .map_err(ImageError::from_tiff_decode)? { tiff::decoder::DecodingResult::U8(v) => v, tiff::decoder::DecodingResult::U16(v) => utils::vec_copy_to_u8(&v), tiff::decoder::DecodingResult::U32(v) => utils::vec_copy_to_u8(&v), tiff::decoder::DecodingResult::U64(v) => utils::vec_copy_to_u8(&v), tiff::decoder::DecodingResult::I8(v) => utils::vec_copy_to_u8(&v), tiff::decoder::DecodingResult::I16(v) => utils::vec_copy_to_u8(&v), tiff::decoder::DecodingResult::I32(v) => utils::vec_copy_to_u8(&v), tiff::decoder::DecodingResult::I64(v) => utils::vec_copy_to_u8(&v), tiff::decoder::DecodingResult::F32(v) => utils::vec_copy_to_u8(&v), tiff::decoder::DecodingResult::F64(v) => utils::vec_copy_to_u8(&v), }; Ok(TiffReader(Cursor::new(buf), PhantomData)) } fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); match self .inner .unwrap() .read_image() .map_err(ImageError::from_tiff_decode)? { tiff::decoder::DecodingResult::U8(v) => { buf.copy_from_slice(&v); } tiff::decoder::DecodingResult::U16(v) => { buf.copy_from_slice(bytemuck::cast_slice(&v)); } tiff::decoder::DecodingResult::U32(v) => { buf.copy_from_slice(bytemuck::cast_slice(&v)); } tiff::decoder::DecodingResult::U64(v) => { buf.copy_from_slice(bytemuck::cast_slice(&v)); } tiff::decoder::DecodingResult::I8(v) => { buf.copy_from_slice(bytemuck::cast_slice(&v)); } tiff::decoder::DecodingResult::I16(v) => { buf.copy_from_slice(bytemuck::cast_slice(&v)); } tiff::decoder::DecodingResult::I32(v) => { buf.copy_from_slice(bytemuck::cast_slice(&v)); } tiff::decoder::DecodingResult::I64(v) => { buf.copy_from_slice(bytemuck::cast_slice(&v)); } tiff::decoder::DecodingResult::F32(v) => { buf.copy_from_slice(bytemuck::cast_slice(&v)); } tiff::decoder::DecodingResult::F64(v) => { buf.copy_from_slice(bytemuck::cast_slice(&v)); } } Ok(()) } } /// Encoder for tiff images pub struct TiffEncoder { w: W, } // Utility to simplify and deduplicate error handling during 16-bit encoding. fn u8_slice_as_u16(buf: &[u8]) -> ImageResult<&[u16]> { bytemuck::try_cast_slice(buf).map_err(|err| { // If the buffer is not aligned or the correct length for a u16 slice, err. // // `bytemuck::PodCastError` of bytemuck-1.2.0 does not implement // `Error` and `Display` trait. // See . ImageError::Parameter(ParameterError::from_kind(ParameterErrorKind::Generic( format!("{:?}", err), ))) }) } impl TiffEncoder { /// Create a new encoder that writes its output to `w` pub fn new(w: W) -> TiffEncoder { TiffEncoder { w } } /// Encodes the image `image` that has dimensions `width` and `height` and `ColorType` `c`. /// /// 16-bit types assume the buffer is native endian. pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> { let mut encoder = tiff::encoder::TiffEncoder::new(self.w).map_err(ImageError::from_tiff_encode)?; match color { ColorType::L8 => { encoder.write_image::(width, height, data) } ColorType::Rgb8 => { encoder.write_image::(width, height, data) } ColorType::Rgba8 => { encoder.write_image::(width, height, data) } ColorType::L16 => encoder.write_image::( width, height, u8_slice_as_u16(data)?, ), ColorType::Rgb16 => encoder.write_image::( width, height, u8_slice_as_u16(data)?, ), ColorType::Rgba16 => encoder.write_image::( width, height, u8_slice_as_u16(data)?, ), _ => { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Tiff.into(), UnsupportedErrorKind::Color(color.into()), ), )) } } .map_err(ImageError::from_tiff_encode)?; Ok(()) } } impl ImageEncoder for TiffEncoder { fn write_image( self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()> { self.encode(buf, width, height, color_type) } } image-0.24.7/src/codecs/webp/decoder.rs000064400000000000000000000312541046102023000157440ustar 00000000000000use byteorder::{LittleEndian, ReadBytesExt}; use std::convert::TryFrom; use std::io::{self, Cursor, Error, Read}; use std::marker::PhantomData; use std::{error, fmt, mem}; use crate::error::{DecodingError, ImageError, ImageResult, ParameterError, ParameterErrorKind}; use crate::image::{ImageDecoder, ImageFormat}; use crate::{color, AnimationDecoder, Frames, Rgba}; use super::lossless::{LosslessDecoder, LosslessFrame}; use super::vp8::{Frame as VP8Frame, Vp8Decoder}; use super::extended::{read_extended_header, ExtendedImage}; /// All errors that can occur when attempting to parse a WEBP container #[derive(Debug, Clone, Copy)] pub(crate) enum DecoderError { /// RIFF's "RIFF" signature not found or invalid RiffSignatureInvalid([u8; 4]), /// WebP's "WEBP" signature not found or invalid WebpSignatureInvalid([u8; 4]), /// Chunk Header was incorrect or invalid in its usage ChunkHeaderInvalid([u8; 4]), } impl fmt::Display for DecoderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { struct SignatureWriter([u8; 4]); impl fmt::Display for SignatureWriter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "[{:#04X?}, {:#04X?}, {:#04X?}, {:#04X?}]", self.0[0], self.0[1], self.0[2], self.0[3] ) } } match self { DecoderError::RiffSignatureInvalid(riff) => f.write_fmt(format_args!( "Invalid RIFF signature: {}", SignatureWriter(*riff) )), DecoderError::WebpSignatureInvalid(webp) => f.write_fmt(format_args!( "Invalid WebP signature: {}", SignatureWriter(*webp) )), DecoderError::ChunkHeaderInvalid(header) => f.write_fmt(format_args!( "Invalid Chunk header: {}", SignatureWriter(*header) )), } } } impl From for ImageError { fn from(e: DecoderError) -> ImageError { ImageError::Decoding(DecodingError::new(ImageFormat::WebP.into(), e)) } } impl error::Error for DecoderError {} /// All possible RIFF chunks in a WebP image file #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) enum WebPRiffChunk { RIFF, WEBP, VP8, VP8L, VP8X, ANIM, ANMF, ALPH, ICCP, EXIF, XMP, } impl WebPRiffChunk { pub(crate) fn from_fourcc(chunk_fourcc: [u8; 4]) -> ImageResult { match &chunk_fourcc { b"RIFF" => Ok(Self::RIFF), b"WEBP" => Ok(Self::WEBP), b"VP8 " => Ok(Self::VP8), b"VP8L" => Ok(Self::VP8L), b"VP8X" => Ok(Self::VP8X), b"ANIM" => Ok(Self::ANIM), b"ANMF" => Ok(Self::ANMF), b"ALPH" => Ok(Self::ALPH), b"ICCP" => Ok(Self::ICCP), b"EXIF" => Ok(Self::EXIF), b"XMP " => Ok(Self::XMP), _ => Err(DecoderError::ChunkHeaderInvalid(chunk_fourcc).into()), } } pub(crate) fn to_fourcc(&self) -> [u8; 4] { match self { Self::RIFF => *b"RIFF", Self::WEBP => *b"WEBP", Self::VP8 => *b"VP8 ", Self::VP8L => *b"VP8L", Self::VP8X => *b"VP8X", Self::ANIM => *b"ANIM", Self::ANMF => *b"ANMF", Self::ALPH => *b"ALPH", Self::ICCP => *b"ICCP", Self::EXIF => *b"EXIF", Self::XMP => *b"XMP ", } } } enum WebPImage { Lossy(VP8Frame), Lossless(LosslessFrame), Extended(ExtendedImage), } /// WebP Image format decoder. Currently only supports lossy RGB images or lossless RGBA images. pub struct WebPDecoder { r: R, image: WebPImage, } impl WebPDecoder { /// Create a new WebPDecoder from the Reader ```r```. /// This function takes ownership of the Reader. pub fn new(r: R) -> ImageResult> { let image = WebPImage::Lossy(Default::default()); let mut decoder = WebPDecoder { r, image }; decoder.read_data()?; Ok(decoder) } //reads the 12 bytes of the WebP file header fn read_riff_header(&mut self) -> ImageResult { let mut riff = [0; 4]; self.r.read_exact(&mut riff)?; if &riff != b"RIFF" { return Err(DecoderError::RiffSignatureInvalid(riff).into()); } let size = self.r.read_u32::()?; let mut webp = [0; 4]; self.r.read_exact(&mut webp)?; if &webp != b"WEBP" { return Err(DecoderError::WebpSignatureInvalid(webp).into()); } Ok(size) } //reads the chunk header, decodes the frame and returns the inner decoder fn read_frame(&mut self) -> ImageResult { let chunk = read_chunk(&mut self.r)?; match chunk { Some((cursor, WebPRiffChunk::VP8)) => { let mut vp8_decoder = Vp8Decoder::new(cursor); let frame = vp8_decoder.decode_frame()?; Ok(WebPImage::Lossy(frame.clone())) } Some((cursor, WebPRiffChunk::VP8L)) => { let mut lossless_decoder = LosslessDecoder::new(cursor); let frame = lossless_decoder.decode_frame()?; Ok(WebPImage::Lossless(frame.clone())) } Some((mut cursor, WebPRiffChunk::VP8X)) => { let info = read_extended_header(&mut cursor)?; let image = ExtendedImage::read_extended_chunks(&mut self.r, info)?; Ok(WebPImage::Extended(image)) } None => Err(ImageError::IoError(Error::from( io::ErrorKind::UnexpectedEof, ))), Some((_, chunk)) => Err(DecoderError::ChunkHeaderInvalid(chunk.to_fourcc()).into()), } } fn read_data(&mut self) -> ImageResult<()> { let _size = self.read_riff_header()?; let image = self.read_frame()?; self.image = image; Ok(()) } /// Returns true if the image as described by the bitstream is animated. pub fn has_animation(&self) -> bool { match &self.image { WebPImage::Lossy(_) => false, WebPImage::Lossless(_) => false, WebPImage::Extended(extended) => extended.has_animation(), } } /// Sets the background color if the image is an extended and animated webp. pub fn set_background_color(&mut self, color: Rgba) -> ImageResult<()> { match &mut self.image { WebPImage::Extended(image) => image.set_background_color(color), _ => Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::Generic( "Background color can only be set on animated webp".to_owned(), ), ))), } } } pub(crate) fn read_len_cursor(r: &mut R) -> ImageResult>> where R: Read, { let unpadded_len = u64::from(r.read_u32::()?); // RIFF chunks containing an uneven number of bytes append // an extra 0x00 at the end of the chunk // // The addition cannot overflow since we have a u64 that was created from a u32 let len = unpadded_len + (unpadded_len % 2); let mut framedata = Vec::new(); r.by_ref().take(len).read_to_end(&mut framedata)?; //remove padding byte if unpadded_len % 2 == 1 { framedata.pop(); } Ok(io::Cursor::new(framedata)) } /// Reads a chunk header FourCC /// Returns None if and only if we hit end of file reading the four character code of the chunk /// The inner error is `Err` if and only if the chunk header FourCC is present but unknown pub(crate) fn read_fourcc(r: &mut R) -> ImageResult>> { let mut chunk_fourcc = [0; 4]; let result = r.read_exact(&mut chunk_fourcc); match result { Ok(()) => {} Err(err) => { if err.kind() == io::ErrorKind::UnexpectedEof { return Ok(None); } else { return Err(err.into()); } } } let chunk = WebPRiffChunk::from_fourcc(chunk_fourcc); Ok(Some(chunk)) } /// Reads a chunk /// Returns an error if the chunk header is not a valid webp header or some other reading error /// Returns None if and only if we hit end of file reading the four character code of the chunk pub(crate) fn read_chunk(r: &mut R) -> ImageResult>, WebPRiffChunk)>> where R: Read, { if let Some(chunk) = read_fourcc(r)? { let chunk = chunk?; let cursor = read_len_cursor(r)?; Ok(Some((cursor, chunk))) } else { Ok(None) } } /// Wrapper struct around a `Cursor>` pub struct WebpReader(Cursor>, PhantomData); impl Read for WebpReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { if self.0.position() == 0 && buf.is_empty() { mem::swap(buf, self.0.get_mut()); Ok(buf.len()) } else { self.0.read_to_end(buf) } } } impl<'a, R: 'a + Read> ImageDecoder<'a> for WebPDecoder { type Reader = WebpReader; fn dimensions(&self) -> (u32, u32) { match &self.image { WebPImage::Lossy(vp8_frame) => { (u32::from(vp8_frame.width), u32::from(vp8_frame.height)) } WebPImage::Lossless(lossless_frame) => ( u32::from(lossless_frame.width), u32::from(lossless_frame.height), ), WebPImage::Extended(extended) => extended.dimensions(), } } fn color_type(&self) -> color::ColorType { match &self.image { WebPImage::Lossy(_) => color::ColorType::Rgb8, WebPImage::Lossless(_) => color::ColorType::Rgba8, WebPImage::Extended(extended) => extended.color_type(), } } fn into_reader(self) -> ImageResult { match &self.image { WebPImage::Lossy(vp8_frame) => { let mut data = vec![0; vp8_frame.get_buf_size()]; vp8_frame.fill_rgb(data.as_mut_slice()); Ok(WebpReader(Cursor::new(data), PhantomData)) } WebPImage::Lossless(lossless_frame) => { let mut data = vec![0; lossless_frame.get_buf_size()]; lossless_frame.fill_rgba(data.as_mut_slice()); Ok(WebpReader(Cursor::new(data), PhantomData)) } WebPImage::Extended(extended) => { let mut data = vec![0; extended.get_buf_size()]; extended.fill_buf(data.as_mut_slice()); Ok(WebpReader(Cursor::new(data), PhantomData)) } } } fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); match &self.image { WebPImage::Lossy(vp8_frame) => { vp8_frame.fill_rgb(buf); } WebPImage::Lossless(lossless_frame) => { lossless_frame.fill_rgba(buf); } WebPImage::Extended(extended) => { extended.fill_buf(buf); } } Ok(()) } fn icc_profile(&mut self) -> Option> { if let WebPImage::Extended(extended) = &self.image { extended.icc_profile() } else { None } } } impl<'a, R: 'a + Read> AnimationDecoder<'a> for WebPDecoder { fn into_frames(self) -> Frames<'a> { match self.image { WebPImage::Lossy(_) | WebPImage::Lossless(_) => { Frames::new(Box::new(std::iter::empty())) } WebPImage::Extended(extended_image) => extended_image.into_frames(), } } } #[cfg(test)] mod tests { use super::*; #[test] fn add_with_overflow_size() { let bytes = vec![ 0x52, 0x49, 0x46, 0x46, 0xaf, 0x37, 0x80, 0x47, 0x57, 0x45, 0x42, 0x50, 0x6c, 0x64, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x7e, 0x73, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x40, 0xfb, 0xff, 0xff, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x49, 0x54, 0x55, 0x50, 0x4c, 0x54, 0x59, 0x50, 0x45, 0x33, 0x37, 0x44, 0x4d, 0x46, ]; let data = std::io::Cursor::new(bytes); let _ = WebPDecoder::new(data); } } image-0.24.7/src/codecs/webp/encoder.rs000064400000000000000000000170371046102023000157610ustar 00000000000000//! Encoding of WebP images. /// /// Uses the simple encoding API from the [libwebp] library. /// /// [libwebp]: https://developers.google.com/speed/webp/docs/api#simple_encoding_api use std::io::Write; use libwebp::{Encoder, PixelLayout, WebPMemory}; use crate::error::{ EncodingError, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, }; use crate::flat::SampleLayout; use crate::{ColorType, ImageEncoder, ImageError, ImageFormat, ImageResult}; /// WebP Encoder. pub struct WebPEncoder { inner: W, quality: WebPQuality, } /// WebP encoder quality. #[derive(Debug, Copy, Clone)] pub struct WebPQuality(Quality); #[derive(Debug, Copy, Clone)] enum Quality { Lossless, Lossy(u8), } impl WebPQuality { /// Minimum lossy quality value (0). pub const MIN: u8 = 0; /// Maximum lossy quality value (100). pub const MAX: u8 = 100; /// Default lossy quality (80), providing a balance of quality and file size. pub const DEFAULT: u8 = 80; /// Lossless encoding. pub fn lossless() -> Self { Self(Quality::Lossless) } /// Lossy encoding. 0 = low quality, small size; 100 = high quality, large size. /// /// Values are clamped from 0 to 100. pub fn lossy(quality: u8) -> Self { Self(Quality::Lossy(quality.clamp(Self::MIN, Self::MAX))) } } impl Default for WebPQuality { fn default() -> Self { Self::lossy(WebPQuality::DEFAULT) } } impl WebPEncoder { /// Create a new encoder that writes its output to `w`. /// /// Defaults to lossy encoding, see [`WebPQuality::DEFAULT`]. pub fn new(w: W) -> Self { WebPEncoder::new_with_quality(w, WebPQuality::default()) } /// Create a new encoder with the specified quality, that writes its output to `w`. pub fn new_with_quality(w: W, quality: WebPQuality) -> Self { Self { inner: w, quality } } /// Encode image data with the indicated color type. /// /// The encoder requires image data be Rgb8 or Rgba8. pub fn encode( mut self, data: &[u8], width: u32, height: u32, color: ColorType, ) -> ImageResult<()> { // TODO: convert color types internally? let layout = match color { ColorType::Rgb8 => PixelLayout::Rgb, ColorType::Rgba8 => PixelLayout::Rgba, _ => { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::WebP.into(), UnsupportedErrorKind::Color(color.into()), ), )) } }; // Validate dimensions upfront to avoid panics. if width == 0 || height == 0 || !SampleLayout::row_major_packed(color.channel_count(), width, height) .fits(data.len()) { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))); } // Call the native libwebp library to encode the image. let encoder = Encoder::new(data, layout, width, height); let encoded: WebPMemory = match self.quality.0 { Quality::Lossless => encoder.encode_lossless(), Quality::Lossy(quality) => encoder.encode(quality as f32), }; // The simple encoding API in libwebp does not return errors. if encoded.is_empty() { return Err(ImageError::Encoding(EncodingError::new( ImageFormat::WebP.into(), "encoding failed, output empty", ))); } self.inner.write_all(&encoded)?; Ok(()) } } impl ImageEncoder for WebPEncoder { fn write_image( self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()> { self.encode(buf, width, height, color_type) } } #[cfg(test)] mod tests { use crate::codecs::webp::{WebPEncoder, WebPQuality}; use crate::{ColorType, ImageEncoder}; #[test] fn webp_lossless_deterministic() { // 1x1 8-bit image buffer containing a single red pixel. let rgb: &[u8] = &[255, 0, 0]; let rgba: &[u8] = &[255, 0, 0, 128]; for (color, img, expected) in [ ( ColorType::Rgb8, rgb, [ 82, 73, 70, 70, 28, 0, 0, 0, 87, 69, 66, 80, 86, 80, 56, 76, 15, 0, 0, 0, 47, 0, 0, 0, 0, 7, 16, 253, 143, 254, 7, 34, 162, 255, 1, 0, ], ), ( ColorType::Rgba8, rgba, [ 82, 73, 70, 70, 28, 0, 0, 0, 87, 69, 66, 80, 86, 80, 56, 76, 15, 0, 0, 0, 47, 0, 0, 0, 16, 7, 16, 253, 143, 2, 6, 34, 162, 255, 1, 0, ], ), ] { // Encode it into a memory buffer. let mut encoded_img = Vec::new(); { let encoder = WebPEncoder::new_with_quality(&mut encoded_img, WebPQuality::lossless()); encoder .write_image(&img, 1, 1, color) .expect("image encoding failed"); } // WebP encoding should be deterministic. assert_eq!(encoded_img, expected); } } #[derive(Debug, Clone)] struct MockImage { width: u32, height: u32, color: ColorType, data: Vec, } impl quickcheck::Arbitrary for MockImage { fn arbitrary(g: &mut quickcheck::Gen) -> Self { // Limit to small, non-empty images <= 512x512. let width = u32::arbitrary(g) % 512 + 1; let height = u32::arbitrary(g) % 512 + 1; let (color, stride) = if bool::arbitrary(g) { (ColorType::Rgb8, 3) } else { (ColorType::Rgba8, 4) }; let size = width * height * stride; let data: Vec = (0..size).map(|_| u8::arbitrary(g)).collect(); MockImage { width, height, color, data, } } } quickcheck! { fn fuzz_webp_valid_image(image: MockImage, quality: u8) -> bool { // Check valid images do not panic. let mut buffer = Vec::::new(); for webp_quality in [WebPQuality::lossless(), WebPQuality::lossy(quality)] { buffer.clear(); let encoder = WebPEncoder::new_with_quality(&mut buffer, webp_quality); if !encoder .write_image(&image.data, image.width, image.height, image.color) .is_ok() { return false; } } true } fn fuzz_webp_no_panic(data: Vec, width: u8, height: u8, quality: u8) -> bool { // Check random (usually invalid) parameters do not panic. let mut buffer = Vec::::new(); for color in [ColorType::Rgb8, ColorType::Rgba8] { for webp_quality in [WebPQuality::lossless(), WebPQuality::lossy(quality)] { buffer.clear(); let encoder = WebPEncoder::new_with_quality(&mut buffer, webp_quality); // Ignore errors. let _ = encoder .write_image(&data, width as u32, height as u32, color); } } true } } } image-0.24.7/src/codecs/webp/extended.rs000064400000000000000000000655741046102023000161530ustar 00000000000000use std::convert::TryInto; use std::io::{self, Cursor, Error, Read}; use std::{error, fmt}; use super::decoder::{ read_chunk, read_fourcc, read_len_cursor, DecoderError::ChunkHeaderInvalid, WebPRiffChunk, }; use super::lossless::{LosslessDecoder, LosslessFrame}; use super::vp8::{Frame as VP8Frame, Vp8Decoder}; use crate::error::{DecodingError, ParameterError, ParameterErrorKind}; use crate::image::ImageFormat; use crate::{ ColorType, Delay, Frame, Frames, ImageError, ImageResult, Rgb, RgbImage, Rgba, RgbaImage, }; use byteorder::{LittleEndian, ReadBytesExt}; //all errors that can occur while parsing extended chunks in a WebP file #[derive(Debug, Clone, Copy)] enum DecoderError { // Some bits were invalid InfoBitsInvalid { name: &'static str, value: u32 }, // Alpha chunk doesn't match the frame's size AlphaChunkSizeMismatch, // Image is too large, either for the platform's pointer size or generally ImageTooLarge, // Frame would go out of the canvas FrameOutsideImage, } impl fmt::Display for DecoderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DecoderError::InfoBitsInvalid { name, value } => f.write_fmt(format_args!( "Info bits `{}` invalid, received value: {}", name, value )), DecoderError::AlphaChunkSizeMismatch => { f.write_str("Alpha chunk doesn't match the size of the frame") } DecoderError::ImageTooLarge => f.write_str("Image is too large to be decoded"), DecoderError::FrameOutsideImage => { f.write_str("Frame is too large and would go outside the image") } } } } impl From for ImageError { fn from(e: DecoderError) -> ImageError { ImageError::Decoding(DecodingError::new(ImageFormat::WebP.into(), e)) } } impl error::Error for DecoderError {} #[derive(Debug, Clone)] pub(crate) struct WebPExtendedInfo { _icc_profile: bool, _alpha: bool, _exif_metadata: bool, _xmp_metadata: bool, _animation: bool, canvas_width: u32, canvas_height: u32, icc_profile: Option>, } #[derive(Debug)] enum ExtendedImageData { Animation { frames: Vec, anim_info: WebPAnimatedInfo, }, Static(WebPStatic), } #[derive(Debug)] pub(crate) struct ExtendedImage { info: WebPExtendedInfo, image: ExtendedImageData, } impl ExtendedImage { pub(crate) fn dimensions(&self) -> (u32, u32) { (self.info.canvas_width, self.info.canvas_height) } pub(crate) fn has_animation(&self) -> bool { self.info._animation } pub(crate) fn icc_profile(&self) -> Option> { self.info.icc_profile.clone() } pub(crate) fn color_type(&self) -> ColorType { match &self.image { ExtendedImageData::Animation { frames, .. } => &frames[0].image, ExtendedImageData::Static(image) => image, } .color_type() } pub(crate) fn into_frames<'a>(self) -> Frames<'a> { struct FrameIterator { image: ExtendedImage, index: usize, canvas: RgbaImage, } impl Iterator for FrameIterator { type Item = ImageResult; fn next(&mut self) -> Option { if let ExtendedImageData::Animation { frames, anim_info } = &self.image.image { let frame = frames.get(self.index); match frame { Some(anim_image) => { self.index += 1; ExtendedImage::draw_subimage( &mut self.canvas, anim_image, anim_info.background_color, ) } None => None, } } else { None } } } let width = self.info.canvas_width; let height = self.info.canvas_height; let background_color = if let ExtendedImageData::Animation { ref anim_info, .. } = self.image { anim_info.background_color } else { Rgba([0, 0, 0, 0]) }; let frame_iter = FrameIterator { image: self, index: 0, canvas: RgbaImage::from_pixel(width, height, background_color), }; Frames::new(Box::new(frame_iter)) } pub(crate) fn read_extended_chunks( reader: &mut R, mut info: WebPExtendedInfo, ) -> ImageResult { let mut anim_info: Option = None; let mut anim_frames: Vec = Vec::new(); let mut static_frame: Option = None; //go until end of file and while chunk headers are valid while let Some((mut cursor, chunk)) = read_extended_chunk(reader)? { match chunk { WebPRiffChunk::EXIF | WebPRiffChunk::XMP => { //ignore these chunks } WebPRiffChunk::ANIM => { if anim_info.is_none() { anim_info = Some(Self::read_anim_info(&mut cursor)?); } } WebPRiffChunk::ANMF => { let frame = read_anim_frame(cursor, info.canvas_width, info.canvas_height)?; anim_frames.push(frame); } WebPRiffChunk::ALPH => { if static_frame.is_none() { let alpha_chunk = read_alpha_chunk(&mut cursor, info.canvas_width, info.canvas_height)?; let vp8_frame = read_lossy_with_chunk(reader)?; let img = WebPStatic::from_alpha_lossy(alpha_chunk, vp8_frame)?; static_frame = Some(img); } } WebPRiffChunk::ICCP => { let mut icc_profile = Vec::new(); cursor.read_to_end(&mut icc_profile)?; info.icc_profile = Some(icc_profile); } WebPRiffChunk::VP8 => { if static_frame.is_none() { let vp8_frame = read_lossy(cursor)?; let img = WebPStatic::from_lossy(vp8_frame)?; static_frame = Some(img); } } WebPRiffChunk::VP8L => { if static_frame.is_none() { let mut lossless_decoder = LosslessDecoder::new(cursor); let frame = lossless_decoder.decode_frame()?; let image = WebPStatic::Lossless(frame.clone()); static_frame = Some(image); } } _ => return Err(ChunkHeaderInvalid(chunk.to_fourcc()).into()), } } let image = if let Some(info) = anim_info { if anim_frames.is_empty() { return Err(ImageError::IoError(Error::from( io::ErrorKind::UnexpectedEof, ))); } ExtendedImageData::Animation { frames: anim_frames, anim_info: info, } } else if let Some(frame) = static_frame { ExtendedImageData::Static(frame) } else { //reached end of file too early before image data was reached return Err(ImageError::IoError(Error::from( io::ErrorKind::UnexpectedEof, ))); }; let image = ExtendedImage { image, info }; Ok(image) } fn read_anim_info(reader: &mut R) -> ImageResult { let mut colors: [u8; 4] = [0; 4]; reader.read_exact(&mut colors)?; //background color is [blue, green, red, alpha] let background_color = Rgba([colors[2], colors[1], colors[0], colors[3]]); let loop_count = reader.read_u16::()?; let info = WebPAnimatedInfo { background_color, _loop_count: loop_count, }; Ok(info) } fn draw_subimage( canvas: &mut RgbaImage, anim_image: &AnimatedFrame, background_color: Rgba, ) -> Option> { let mut buffer = vec![0; anim_image.image.get_buf_size()]; anim_image.image.fill_buf(&mut buffer); let has_alpha = anim_image.image.has_alpha(); let pixel_len: u32 = anim_image.image.color_type().bytes_per_pixel().into(); 'x: for x in 0..anim_image.width { for y in 0..anim_image.height { let canvas_index: (u32, u32) = (x + anim_image.offset_x, y + anim_image.offset_y); // Negative offsets are not possible due to unsigned ints // If we go out of bounds by height, still continue by x if canvas_index.1 >= canvas.height() { continue 'x; } // If we go out of bounds by width, it doesn't make sense to continue at all if canvas_index.0 >= canvas.width() { break 'x; } let index: usize = ((y * anim_image.width + x) * pixel_len).try_into().unwrap(); canvas[canvas_index] = if anim_image.use_alpha_blending && has_alpha { let buffer: [u8; 4] = buffer[index..][..4].try_into().unwrap(); ExtendedImage::do_alpha_blending(buffer, canvas[canvas_index]) } else { Rgba([ buffer[index], buffer[index + 1], buffer[index + 2], if has_alpha { buffer[index + 3] } else { 255 }, ]) }; } } let delay = Delay::from_numer_denom_ms(anim_image.duration, 1); let img = canvas.clone(); let frame = Frame::from_parts(img, 0, 0, delay); if anim_image.dispose { for x in 0..anim_image.width { for y in 0..anim_image.height { let canvas_index = (x + anim_image.offset_x, y + anim_image.offset_y); canvas[canvas_index] = background_color; } } } Some(Ok(frame)) } fn do_alpha_blending(buffer: [u8; 4], canvas: Rgba) -> Rgba { let canvas_alpha = f64::from(canvas[3]); let buffer_alpha = f64::from(buffer[3]); let blend_alpha_f64 = buffer_alpha + canvas_alpha * (1.0 - buffer_alpha / 255.0); //value should be between 0 and 255, this truncates the fractional part let blend_alpha: u8 = blend_alpha_f64 as u8; let blend_rgb: [u8; 3] = if blend_alpha == 0 { [0, 0, 0] } else { let mut rgb = [0u8; 3]; for i in 0..3 { let canvas_f64 = f64::from(canvas[i]); let buffer_f64 = f64::from(buffer[i]); let val = (buffer_f64 * buffer_alpha + canvas_f64 * canvas_alpha * (1.0 - buffer_alpha / 255.0)) / blend_alpha_f64; //value should be between 0 and 255, this truncates the fractional part rgb[i] = val as u8; } rgb }; Rgba([blend_rgb[0], blend_rgb[1], blend_rgb[2], blend_alpha]) } pub(crate) fn fill_buf(&self, buf: &mut [u8]) { match &self.image { // will always have at least one frame ExtendedImageData::Animation { frames, anim_info } => { let first_frame = &frames[0]; let (canvas_width, canvas_height) = self.dimensions(); if canvas_width == first_frame.width && canvas_height == first_frame.height { first_frame.image.fill_buf(buf); } else { let bg_color = match &self.info._alpha { true => Rgba::from([0, 0, 0, 0]), false => anim_info.background_color, }; let mut canvas = RgbaImage::from_pixel(canvas_width, canvas_height, bg_color); let _ = ExtendedImage::draw_subimage(&mut canvas, first_frame, bg_color) .unwrap() .unwrap(); buf.copy_from_slice(canvas.into_raw().as_slice()); } } ExtendedImageData::Static(image) => { image.fill_buf(buf); } } } pub(crate) fn get_buf_size(&self) -> usize { match &self.image { // will always have at least one frame ExtendedImageData::Animation { frames, .. } => &frames[0].image, ExtendedImageData::Static(image) => image, } .get_buf_size() } pub(crate) fn set_background_color(&mut self, color: Rgba) -> ImageResult<()> { match &mut self.image { ExtendedImageData::Animation { anim_info, .. } => { anim_info.background_color = color; Ok(()) } _ => Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::Generic( "Background color can only be set on animated webp".to_owned(), ), ))), } } } #[derive(Debug)] enum WebPStatic { LossyWithAlpha(RgbaImage), LossyWithoutAlpha(RgbImage), Lossless(LosslessFrame), } impl WebPStatic { pub(crate) fn from_alpha_lossy( alpha: AlphaChunk, vp8_frame: VP8Frame, ) -> ImageResult { if alpha.data.len() != usize::from(vp8_frame.width) * usize::from(vp8_frame.height) { return Err(DecoderError::AlphaChunkSizeMismatch.into()); } let size = usize::from(vp8_frame.width).checked_mul(usize::from(vp8_frame.height) * 4); let mut image_vec = match size { Some(size) => vec![0u8; size], None => return Err(DecoderError::ImageTooLarge.into()), }; vp8_frame.fill_rgba(&mut image_vec); for y in 0..vp8_frame.height { for x in 0..vp8_frame.width { let predictor: u8 = WebPStatic::get_predictor( x.into(), y.into(), vp8_frame.width.into(), alpha.filtering_method, &image_vec, ); let predictor = u16::from(predictor); let alpha_index = usize::from(y) * usize::from(vp8_frame.width) + usize::from(x); let alpha_val = alpha.data[alpha_index]; let alpha: u8 = ((predictor + u16::from(alpha_val)) % 256) .try_into() .unwrap(); let alpha_index = alpha_index * 4 + 3; image_vec[alpha_index] = alpha; } } let image = RgbaImage::from_vec(vp8_frame.width.into(), vp8_frame.height.into(), image_vec) .unwrap(); Ok(WebPStatic::LossyWithAlpha(image)) } fn get_predictor( x: usize, y: usize, width: usize, filtering_method: FilteringMethod, image_slice: &[u8], ) -> u8 { match filtering_method { FilteringMethod::None => 0, FilteringMethod::Horizontal => { if x == 0 && y == 0 { 0 } else if x == 0 { let index = (y - 1) * width + x; image_slice[index * 4 + 3] } else { let index = y * width + x - 1; image_slice[index * 4 + 3] } } FilteringMethod::Vertical => { if x == 0 && y == 0 { 0 } else if y == 0 { let index = y * width + x - 1; image_slice[index * 4 + 3] } else { let index = (y - 1) * width + x; image_slice[index * 4 + 3] } } FilteringMethod::Gradient => { let (left, top, top_left) = match (x, y) { (0, 0) => (0, 0, 0), (0, y) => { let above_index = (y - 1) * width + x; let val = image_slice[above_index * 4 + 3]; (val, val, val) } (x, 0) => { let before_index = y * width + x - 1; let val = image_slice[before_index * 4 + 3]; (val, val, val) } (x, y) => { let left_index = y * width + x - 1; let left = image_slice[left_index * 4 + 3]; let top_index = (y - 1) * width + x; let top = image_slice[top_index * 4 + 3]; let top_left_index = (y - 1) * width + x - 1; let top_left = image_slice[top_left_index * 4 + 3]; (left, top, top_left) } }; let combination = i16::from(left) + i16::from(top) - i16::from(top_left); i16::clamp(combination, 0, 255).try_into().unwrap() } } } pub(crate) fn from_lossy(vp8_frame: VP8Frame) -> ImageResult { let mut image = RgbImage::from_pixel( vp8_frame.width.into(), vp8_frame.height.into(), Rgb([0, 0, 0]), ); vp8_frame.fill_rgb(&mut image); Ok(WebPStatic::LossyWithoutAlpha(image)) } pub(crate) fn fill_buf(&self, buf: &mut [u8]) { match self { WebPStatic::LossyWithAlpha(image) => { buf.copy_from_slice(image); } WebPStatic::LossyWithoutAlpha(image) => { buf.copy_from_slice(image); } WebPStatic::Lossless(lossless) => { lossless.fill_rgba(buf); } } } pub(crate) fn get_buf_size(&self) -> usize { match self { WebPStatic::LossyWithAlpha(rgb_image) => rgb_image.len(), WebPStatic::LossyWithoutAlpha(rgba_image) => rgba_image.len(), WebPStatic::Lossless(lossless) => lossless.get_buf_size(), } } pub(crate) fn color_type(&self) -> ColorType { if self.has_alpha() { ColorType::Rgba8 } else { ColorType::Rgb8 } } pub(crate) fn has_alpha(&self) -> bool { match self { Self::LossyWithAlpha(..) | Self::Lossless(..) => true, Self::LossyWithoutAlpha(..) => false, } } } #[derive(Debug)] struct WebPAnimatedInfo { background_color: Rgba, _loop_count: u16, } #[derive(Debug)] struct AnimatedFrame { offset_x: u32, offset_y: u32, width: u32, height: u32, duration: u32, use_alpha_blending: bool, dispose: bool, image: WebPStatic, } /// Reads a chunk, but silently ignores unknown chunks at the end of a file fn read_extended_chunk(r: &mut R) -> ImageResult>, WebPRiffChunk)>> where R: Read, { let mut unknown_chunk = Ok(()); while let Some(chunk) = read_fourcc(r)? { let cursor = read_len_cursor(r)?; match chunk { Ok(chunk) => return unknown_chunk.and(Ok(Some((cursor, chunk)))), Err(err) => unknown_chunk = unknown_chunk.and(Err(err)), } } Ok(None) } pub(crate) fn read_extended_header(reader: &mut R) -> ImageResult { let chunk_flags = reader.read_u8()?; let reserved_first = chunk_flags & 0b11000000; let icc_profile = chunk_flags & 0b00100000 != 0; let alpha = chunk_flags & 0b00010000 != 0; let exif_metadata = chunk_flags & 0b00001000 != 0; let xmp_metadata = chunk_flags & 0b00000100 != 0; let animation = chunk_flags & 0b00000010 != 0; let reserved_second = chunk_flags & 0b00000001; let reserved_third = read_3_bytes(reader)?; if reserved_first != 0 || reserved_second != 0 || reserved_third != 0 { let value: u32 = if reserved_first != 0 { reserved_first.into() } else if reserved_second != 0 { reserved_second.into() } else { reserved_third }; return Err(DecoderError::InfoBitsInvalid { name: "reserved", value, } .into()); } let canvas_width = read_3_bytes(reader)? + 1; let canvas_height = read_3_bytes(reader)? + 1; //product of canvas dimensions cannot be larger than u32 max if u32::checked_mul(canvas_width, canvas_height).is_none() { return Err(DecoderError::ImageTooLarge.into()); } let info = WebPExtendedInfo { _icc_profile: icc_profile, _alpha: alpha, _exif_metadata: exif_metadata, _xmp_metadata: xmp_metadata, _animation: animation, canvas_width, canvas_height, icc_profile: None, }; Ok(info) } fn read_anim_frame( mut reader: R, canvas_width: u32, canvas_height: u32, ) -> ImageResult { //offsets for the frames are twice the values let frame_x = read_3_bytes(&mut reader)? * 2; let frame_y = read_3_bytes(&mut reader)? * 2; let frame_width = read_3_bytes(&mut reader)? + 1; let frame_height = read_3_bytes(&mut reader)? + 1; if frame_x + frame_width > canvas_width || frame_y + frame_height > canvas_height { return Err(DecoderError::FrameOutsideImage.into()); } let duration = read_3_bytes(&mut reader)?; let frame_info = reader.read_u8()?; let reserved = frame_info & 0b11111100; if reserved != 0 { return Err(DecoderError::InfoBitsInvalid { name: "reserved", value: reserved.into(), } .into()); } let use_alpha_blending = frame_info & 0b00000010 == 0; let dispose = frame_info & 0b00000001 != 0; //read normal bitstream now let static_image = read_image(&mut reader, frame_width, frame_height)?; let frame = AnimatedFrame { offset_x: frame_x, offset_y: frame_y, width: frame_width, height: frame_height, duration, use_alpha_blending, dispose, image: static_image, }; Ok(frame) } fn read_3_bytes(reader: &mut R) -> ImageResult { let mut buffer: [u8; 3] = [0; 3]; reader.read_exact(&mut buffer)?; let value: u32 = (u32::from(buffer[2]) << 16) | (u32::from(buffer[1]) << 8) | u32::from(buffer[0]); Ok(value) } fn read_lossy_with_chunk(reader: &mut R) -> ImageResult { let (cursor, chunk) = read_chunk(reader)?.ok_or_else(|| Error::from(io::ErrorKind::UnexpectedEof))?; if chunk != WebPRiffChunk::VP8 { return Err(ChunkHeaderInvalid(chunk.to_fourcc()).into()); } read_lossy(cursor) } fn read_lossy(cursor: Cursor>) -> ImageResult { let mut vp8_decoder = Vp8Decoder::new(cursor); let frame = vp8_decoder.decode_frame()?; Ok(frame.clone()) } fn read_image(reader: &mut R, width: u32, height: u32) -> ImageResult { let chunk = read_chunk(reader)?; match chunk { Some((cursor, WebPRiffChunk::VP8)) => { let mut vp8_decoder = Vp8Decoder::new(cursor); let frame = vp8_decoder.decode_frame()?; let img = WebPStatic::from_lossy(frame.clone())?; Ok(img) } Some((cursor, WebPRiffChunk::VP8L)) => { let mut lossless_decoder = LosslessDecoder::new(cursor); let frame = lossless_decoder.decode_frame()?; let img = WebPStatic::Lossless(frame.clone()); Ok(img) } Some((mut cursor, WebPRiffChunk::ALPH)) => { let alpha_chunk = read_alpha_chunk(&mut cursor, width, height)?; let vp8_frame = read_lossy_with_chunk(reader)?; let img = WebPStatic::from_alpha_lossy(alpha_chunk, vp8_frame)?; Ok(img) } None => Err(ImageError::IoError(Error::from( io::ErrorKind::UnexpectedEof, ))), Some((_, chunk)) => Err(ChunkHeaderInvalid(chunk.to_fourcc()).into()), } } #[derive(Debug)] struct AlphaChunk { _preprocessing: bool, filtering_method: FilteringMethod, data: Vec, } #[derive(Debug, Copy, Clone)] enum FilteringMethod { None, Horizontal, Vertical, Gradient, } fn read_alpha_chunk(reader: &mut R, width: u32, height: u32) -> ImageResult { let info_byte = reader.read_u8()?; let reserved = info_byte & 0b11000000; let preprocessing = (info_byte & 0b00110000) >> 4; let filtering = (info_byte & 0b00001100) >> 2; let compression = info_byte & 0b00000011; if reserved != 0 { return Err(DecoderError::InfoBitsInvalid { name: "reserved", value: reserved.into(), } .into()); } let preprocessing = match preprocessing { 0 => false, 1 => true, _ => { return Err(DecoderError::InfoBitsInvalid { name: "reserved", value: preprocessing.into(), } .into()) } }; let filtering_method = match filtering { 0 => FilteringMethod::None, 1 => FilteringMethod::Horizontal, 2 => FilteringMethod::Vertical, 3 => FilteringMethod::Gradient, _ => unreachable!(), }; let lossless_compression = match compression { 0 => false, 1 => true, _ => { return Err(DecoderError::InfoBitsInvalid { name: "lossless compression", value: compression.into(), } .into()) } }; let mut framedata = Vec::new(); reader.read_to_end(&mut framedata)?; let data = if lossless_compression { let cursor = io::Cursor::new(framedata); let mut decoder = LosslessDecoder::new(cursor); //this is a potential problem for large images; would require rewriting lossless decoder to use u32 for width and height let width: u16 = width .try_into() .map_err(|_| ImageError::from(DecoderError::ImageTooLarge))?; let height: u16 = height .try_into() .map_err(|_| ImageError::from(DecoderError::ImageTooLarge))?; let frame = decoder.decode_frame_implicit_dims(width, height)?; let mut data = vec![0u8; usize::from(width) * usize::from(height)]; frame.fill_green(&mut data); data } else { framedata }; let chunk = AlphaChunk { _preprocessing: preprocessing, filtering_method, data, }; Ok(chunk) } image-0.24.7/src/codecs/webp/huffman.rs000064400000000000000000000142331046102023000157610ustar 00000000000000use std::convert::TryInto; use super::lossless::BitReader; use super::lossless::DecoderError; use crate::ImageResult; /// Rudimentary utility for reading Canonical Huffman Codes. /// Based off https://github.com/webmproject/libwebp/blob/7f8472a610b61ec780ef0a8873cd954ac512a505/src/utils/huffman.c /// const MAX_ALLOWED_CODE_LENGTH: usize = 15; #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum HuffmanTreeNode { Branch(usize), //offset in vector to children Leaf(u16), //symbol stored in leaf Empty, } /// Huffman tree #[derive(Clone, Debug, Default)] pub(crate) struct HuffmanTree { tree: Vec, max_nodes: usize, num_nodes: usize, } impl HuffmanTree { fn is_full(&self) -> bool { self.num_nodes == self.max_nodes } /// Turns a node from empty into a branch and assigns its children fn assign_children(&mut self, node_index: usize) -> usize { let offset_index = self.num_nodes - node_index; self.tree[node_index] = HuffmanTreeNode::Branch(offset_index); self.num_nodes += 2; offset_index } /// Init a huffman tree fn init(num_leaves: usize) -> ImageResult { if num_leaves == 0 { return Err(DecoderError::HuffmanError.into()); } let max_nodes = 2 * num_leaves - 1; let tree = vec![HuffmanTreeNode::Empty; max_nodes]; let num_nodes = 1; let tree = HuffmanTree { tree, max_nodes, num_nodes, }; Ok(tree) } /// Converts code lengths to codes fn code_lengths_to_codes(code_lengths: &[u16]) -> ImageResult>> { let max_code_length = *code_lengths .iter() .reduce(|a, b| if a >= b { a } else { b }) .unwrap(); if max_code_length > MAX_ALLOWED_CODE_LENGTH.try_into().unwrap() { return Err(DecoderError::HuffmanError.into()); } let mut code_length_hist = vec![0; MAX_ALLOWED_CODE_LENGTH + 1]; for &length in code_lengths.iter() { code_length_hist[usize::from(length)] += 1; } code_length_hist[0] = 0; let mut curr_code = 0; let mut next_codes = vec![None; MAX_ALLOWED_CODE_LENGTH + 1]; for code_len in 1..=usize::from(max_code_length) { curr_code = (curr_code + code_length_hist[code_len - 1]) << 1; next_codes[code_len] = Some(curr_code); } let mut huff_codes = vec![None; code_lengths.len()]; for (symbol, &length) in code_lengths.iter().enumerate() { let length = usize::from(length); if length > 0 { huff_codes[symbol] = next_codes[length]; if let Some(value) = next_codes[length].as_mut() { *value += 1; } } else { huff_codes[symbol] = None; } } Ok(huff_codes) } /// Adds a symbol to a huffman tree fn add_symbol(&mut self, symbol: u16, code: u16, code_length: u16) -> ImageResult<()> { let mut node_index = 0; let code = usize::from(code); for length in (0..code_length).rev() { if node_index >= self.max_nodes { return Err(DecoderError::HuffmanError.into()); } let node = self.tree[node_index]; let offset = match node { HuffmanTreeNode::Empty => { if self.is_full() { return Err(DecoderError::HuffmanError.into()); } self.assign_children(node_index) } HuffmanTreeNode::Leaf(_) => return Err(DecoderError::HuffmanError.into()), HuffmanTreeNode::Branch(offset) => offset, }; node_index += offset + ((code >> length) & 1); } match self.tree[node_index] { HuffmanTreeNode::Empty => self.tree[node_index] = HuffmanTreeNode::Leaf(symbol), HuffmanTreeNode::Leaf(_) => return Err(DecoderError::HuffmanError.into()), HuffmanTreeNode::Branch(_offset) => return Err(DecoderError::HuffmanError.into()), } Ok(()) } /// Builds a tree implicitly, just from code lengths pub(crate) fn build_implicit(code_lengths: Vec) -> ImageResult { let mut num_symbols = 0; let mut root_symbol = 0; for (symbol, length) in code_lengths.iter().enumerate() { if *length > 0 { num_symbols += 1; root_symbol = symbol.try_into().unwrap(); } } let mut tree = HuffmanTree::init(num_symbols)?; if num_symbols == 1 { tree.add_symbol(root_symbol, 0, 0)?; } else { let codes = HuffmanTree::code_lengths_to_codes(&code_lengths)?; for (symbol, &length) in code_lengths.iter().enumerate() { if length > 0 && codes[symbol].is_some() { tree.add_symbol(symbol.try_into().unwrap(), codes[symbol].unwrap(), length)?; } } } Ok(tree) } /// Builds a tree explicitly from lengths, codes and symbols pub(crate) fn build_explicit( code_lengths: Vec, codes: Vec, symbols: Vec, ) -> ImageResult { let mut tree = HuffmanTree::init(symbols.len())?; for i in 0..symbols.len() { tree.add_symbol(symbols[i], codes[i], code_lengths[i])?; } Ok(tree) } /// Reads a symbol using the bitstream pub(crate) fn read_symbol(&self, bit_reader: &mut BitReader) -> ImageResult { let mut index = 0; let mut node = self.tree[index]; while let HuffmanTreeNode::Branch(children_offset) = node { index += children_offset + bit_reader.read_bits::(1)?; node = self.tree[index]; } let symbol = match node { HuffmanTreeNode::Branch(_) => unreachable!(), HuffmanTreeNode::Empty => return Err(DecoderError::HuffmanError.into()), HuffmanTreeNode::Leaf(symbol) => symbol, }; Ok(symbol) } } image-0.24.7/src/codecs/webp/loop_filter.rs000064400000000000000000000103301046102023000166450ustar 00000000000000//! Does loop filtering on webp lossy images use crate::utils::clamp; #[inline] fn c(val: i32) -> i32 { clamp(val, -128, 127) } //unsigned to signed #[inline] fn u2s(val: u8) -> i32 { i32::from(val) - 128 } //signed to unsigned #[inline] fn s2u(val: i32) -> u8 { (c(val) + 128) as u8 } #[inline] fn diff(val1: u8, val2: u8) -> u8 { if val1 > val2 { val1 - val2 } else { val2 - val1 } } //15.2 fn common_adjust(use_outer_taps: bool, pixels: &mut [u8], point: usize, stride: usize) -> i32 { let p1 = u2s(pixels[point - 2 * stride]); let p0 = u2s(pixels[point - stride]); let q0 = u2s(pixels[point]); let q1 = u2s(pixels[point + stride]); //value for the outer 2 pixels let outer = if use_outer_taps { c(p1 - q1) } else { 0 }; let mut a = c(outer + 3 * (q0 - p0)); let b = (c(a + 3)) >> 3; a = (c(a + 4)) >> 3; pixels[point] = s2u(q0 - a); pixels[point - stride] = s2u(p0 + b); a } fn simple_threshold(filter_limit: i32, pixels: &[u8], point: usize, stride: usize) -> bool { i32::from(diff(pixels[point - stride], pixels[point])) * 2 + i32::from(diff(pixels[point - 2 * stride], pixels[point + stride])) / 2 <= filter_limit } fn should_filter( interior_limit: u8, edge_limit: u8, pixels: &[u8], point: usize, stride: usize, ) -> bool { simple_threshold(i32::from(edge_limit), pixels, point, stride) && diff(pixels[point - 4 * stride], pixels[point - 3 * stride]) <= interior_limit && diff(pixels[point - 3 * stride], pixels[point - 2 * stride]) <= interior_limit && diff(pixels[point - 2 * stride], pixels[point - stride]) <= interior_limit && diff(pixels[point + 3 * stride], pixels[point + 2 * stride]) <= interior_limit && diff(pixels[point + 2 * stride], pixels[point + stride]) <= interior_limit && diff(pixels[point + stride], pixels[point]) <= interior_limit } fn high_edge_variance(threshold: u8, pixels: &[u8], point: usize, stride: usize) -> bool { diff(pixels[point - 2 * stride], pixels[point - stride]) > threshold || diff(pixels[point + stride], pixels[point]) > threshold } //simple filter //effects 4 pixels on an edge(2 each side) pub(crate) fn simple_segment(edge_limit: u8, pixels: &mut [u8], point: usize, stride: usize) { if simple_threshold(i32::from(edge_limit), pixels, point, stride) { common_adjust(true, pixels, point, stride); } } //normal filter //works on the 8 pixels on the edges between subblocks inside a macroblock pub(crate) fn subblock_filter( hev_threshold: u8, interior_limit: u8, edge_limit: u8, pixels: &mut [u8], point: usize, stride: usize, ) { if should_filter(interior_limit, edge_limit, pixels, point, stride) { let hv = high_edge_variance(hev_threshold, pixels, point, stride); let a = (common_adjust(hv, pixels, point, stride) + 1) >> 1; if !hv { pixels[point + stride] = s2u(u2s(pixels[point + stride]) - a); pixels[point - 2 * stride] = s2u(u2s(pixels[point - 2 * stride]) - a); } } } //normal filter //works on the 8 pixels on the edges between macroblocks pub(crate) fn macroblock_filter( hev_threshold: u8, interior_limit: u8, edge_limit: u8, pixels: &mut [u8], point: usize, stride: usize, ) { let mut spixels = [0i32; 8]; for i in 0..8 { spixels[i] = u2s(pixels[point + i * stride - 4 * stride]); } if should_filter(interior_limit, edge_limit, pixels, point, stride) { if !high_edge_variance(hev_threshold, pixels, point, stride) { let w = c(c(spixels[2] - spixels[5]) + 3 * (spixels[4] - spixels[3])); let mut a = c((27 * w + 63) >> 7); pixels[point] = s2u(spixels[4] - a); pixels[point - stride] = s2u(spixels[3] + a); a = c((18 * w + 63) >> 7); pixels[point + stride] = s2u(spixels[5] - a); pixels[point - 2 * stride] = s2u(spixels[2] + a); a = c((9 * w + 63) >> 7); pixels[point + 2 * stride] = s2u(spixels[6] - a); pixels[point - 3 * stride] = s2u(spixels[1] + a); } else { common_adjust(true, pixels, point, stride); } } } image-0.24.7/src/codecs/webp/lossless.rs000064400000000000000000000623411046102023000162070ustar 00000000000000//! Decoding of lossless WebP images //! //! [Lossless spec](https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification) //! use std::{ convert::TryFrom, convert::TryInto, error, fmt, io::Read, ops::{AddAssign, Shl}, }; use byteorder::ReadBytesExt; use crate::{error::DecodingError, ImageError, ImageFormat, ImageResult}; use super::huffman::HuffmanTree; use super::lossless_transform::{add_pixels, TransformType}; const CODE_LENGTH_CODES: usize = 19; const CODE_LENGTH_CODE_ORDER: [usize; CODE_LENGTH_CODES] = [ 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ]; #[rustfmt::skip] const DISTANCE_MAP: [(i8, i8); 120] = [ (0, 1), (1, 0), (1, 1), (-1, 1), (0, 2), (2, 0), (1, 2), (-1, 2), (2, 1), (-2, 1), (2, 2), (-2, 2), (0, 3), (3, 0), (1, 3), (-1, 3), (3, 1), (-3, 1), (2, 3), (-2, 3), (3, 2), (-3, 2), (0, 4), (4, 0), (1, 4), (-1, 4), (4, 1), (-4, 1), (3, 3), (-3, 3), (2, 4), (-2, 4), (4, 2), (-4, 2), (0, 5), (3, 4), (-3, 4), (4, 3), (-4, 3), (5, 0), (1, 5), (-1, 5), (5, 1), (-5, 1), (2, 5), (-2, 5), (5, 2), (-5, 2), (4, 4), (-4, 4), (3, 5), (-3, 5), (5, 3), (-5, 3), (0, 6), (6, 0), (1, 6), (-1, 6), (6, 1), (-6, 1), (2, 6), (-2, 6), (6, 2), (-6, 2), (4, 5), (-4, 5), (5, 4), (-5, 4), (3, 6), (-3, 6), (6, 3), (-6, 3), (0, 7), (7, 0), (1, 7), (-1, 7), (5, 5), (-5, 5), (7, 1), (-7, 1), (4, 6), (-4, 6), (6, 4), (-6, 4), (2, 7), (-2, 7), (7, 2), (-7, 2), (3, 7), (-3, 7), (7, 3), (-7, 3), (5, 6), (-5, 6), (6, 5), (-6, 5), (8, 0), (4, 7), (-4, 7), (7, 4), (-7, 4), (8, 1), (8, 2), (6, 6), (-6, 6), (8, 3), (5, 7), (-5, 7), (7, 5), (-7, 5), (8, 4), (6, 7), (-6, 7), (7, 6), (-7, 6), (8, 5), (7, 7), (-7, 7), (8, 6), (8, 7) ]; const GREEN: usize = 0; const RED: usize = 1; const BLUE: usize = 2; const ALPHA: usize = 3; const DIST: usize = 4; const HUFFMAN_CODES_PER_META_CODE: usize = 5; type HuffmanCodeGroup = [HuffmanTree; HUFFMAN_CODES_PER_META_CODE]; const ALPHABET_SIZE: [u16; HUFFMAN_CODES_PER_META_CODE] = [256 + 24, 256, 256, 256, 40]; #[inline] pub(crate) fn subsample_size(size: u16, bits: u8) -> u16 { ((u32::from(size) + (1u32 << bits) - 1) >> bits) .try_into() .unwrap() } #[derive(Debug, Clone, Copy)] pub(crate) enum DecoderError { /// Signature of 0x2f not found LosslessSignatureInvalid(u8), /// Version Number must be 0 VersionNumberInvalid(u8), /// InvalidColorCacheBits(u8), HuffmanError, BitStreamError, TransformError, } impl fmt::Display for DecoderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DecoderError::LosslessSignatureInvalid(sig) => { f.write_fmt(format_args!("Invalid lossless signature: {}", sig)) } DecoderError::VersionNumberInvalid(num) => { f.write_fmt(format_args!("Invalid version number: {}", num)) } DecoderError::InvalidColorCacheBits(num) => f.write_fmt(format_args!( "Invalid color cache(must be between 1-11): {}", num )), DecoderError::HuffmanError => f.write_fmt(format_args!("Error building Huffman Tree")), DecoderError::BitStreamError => { f.write_fmt(format_args!("Error while reading bitstream")) } DecoderError::TransformError => { f.write_fmt(format_args!("Error while reading or writing transforms")) } } } } impl From for ImageError { fn from(e: DecoderError) -> ImageError { ImageError::Decoding(DecodingError::new(ImageFormat::WebP.into(), e)) } } impl error::Error for DecoderError {} const NUM_TRANSFORM_TYPES: usize = 4; //Decodes lossless WebP images #[derive(Debug)] pub(crate) struct LosslessDecoder { r: R, bit_reader: BitReader, frame: LosslessFrame, transforms: [Option; NUM_TRANSFORM_TYPES], transform_order: Vec, } impl LosslessDecoder { /// Create a new decoder pub(crate) fn new(r: R) -> LosslessDecoder { LosslessDecoder { r, bit_reader: BitReader::new(), frame: Default::default(), transforms: [None, None, None, None], transform_order: Vec::new(), } } /// Reads the frame pub(crate) fn decode_frame(&mut self) -> ImageResult<&LosslessFrame> { let signature = self.r.read_u8()?; if signature != 0x2f { return Err(DecoderError::LosslessSignatureInvalid(signature).into()); } let mut buf = Vec::new(); self.r.read_to_end(&mut buf)?; self.bit_reader.init(buf); self.frame.width = self.bit_reader.read_bits::(14)? + 1; self.frame.height = self.bit_reader.read_bits::(14)? + 1; let _alpha_used = self.bit_reader.read_bits::(1)?; let version_num = self.bit_reader.read_bits::(3)?; if version_num != 0 { return Err(DecoderError::VersionNumberInvalid(version_num).into()); } let mut data = self.decode_image_stream(self.frame.width, self.frame.height, true)?; for &trans_index in self.transform_order.iter().rev() { let trans = self.transforms[usize::from(trans_index)].as_ref().unwrap(); trans.apply_transform(&mut data, self.frame.width, self.frame.height)?; } self.frame.buf = data; Ok(&self.frame) } //used for alpha data in extended decoding pub(crate) fn decode_frame_implicit_dims( &mut self, width: u16, height: u16, ) -> ImageResult<&LosslessFrame> { let mut buf = Vec::new(); self.r.read_to_end(&mut buf)?; self.bit_reader.init(buf); self.frame.width = width; self.frame.height = height; let mut data = self.decode_image_stream(self.frame.width, self.frame.height, true)?; //transform_order is vector of indices(0-3) into transforms in order decoded for &trans_index in self.transform_order.iter().rev() { let trans = self.transforms[usize::from(trans_index)].as_ref().unwrap(); trans.apply_transform(&mut data, self.frame.width, self.frame.height)?; } self.frame.buf = data; Ok(&self.frame) } /// Reads Image data from the bitstream /// Can be in any of the 5 roles described in the Specification /// ARGB Image role has different behaviour to the other 4 /// xsize and ysize describe the size of the blocks where each block has its own entropy code fn decode_image_stream( &mut self, xsize: u16, ysize: u16, is_argb_img: bool, ) -> ImageResult> { let trans_xsize = if is_argb_img { self.read_transforms()? } else { xsize }; let color_cache_bits = self.read_color_cache()?; let color_cache = color_cache_bits.map(|bits| { let size = 1 << bits; let cache = vec![0u32; size]; ColorCache { color_cache_bits: bits, color_cache: cache, } }); let huffman_info = self.read_huffman_codes(is_argb_img, trans_xsize, ysize, color_cache)?; //decode data let data = self.decode_image_data(trans_xsize, ysize, huffman_info)?; Ok(data) } /// Reads transforms and their data from the bitstream fn read_transforms(&mut self) -> ImageResult { let mut xsize = self.frame.width; while self.bit_reader.read_bits::(1)? == 1 { let transform_type_val = self.bit_reader.read_bits::(2)?; if self.transforms[usize::from(transform_type_val)].is_some() { //can only have one of each transform, error return Err(DecoderError::TransformError.into()); } self.transform_order.push(transform_type_val); let transform_type = match transform_type_val { 0 => { //predictor let size_bits = self.bit_reader.read_bits::(3)? + 2; let block_xsize = subsample_size(xsize, size_bits); let block_ysize = subsample_size(self.frame.height, size_bits); let data = self.decode_image_stream(block_xsize, block_ysize, false)?; TransformType::PredictorTransform { size_bits, predictor_data: data, } } 1 => { //color transform let size_bits = self.bit_reader.read_bits::(3)? + 2; let block_xsize = subsample_size(xsize, size_bits); let block_ysize = subsample_size(self.frame.height, size_bits); let data = self.decode_image_stream(block_xsize, block_ysize, false)?; TransformType::ColorTransform { size_bits, transform_data: data, } } 2 => { //subtract green TransformType::SubtractGreen } 3 => { let color_table_size = self.bit_reader.read_bits::(8)? + 1; let mut color_map = self.decode_image_stream(color_table_size, 1, false)?; let bits = if color_table_size <= 2 { 3 } else if color_table_size <= 4 { 2 } else if color_table_size <= 16 { 1 } else { 0 }; xsize = subsample_size(xsize, bits); Self::adjust_color_map(&mut color_map); TransformType::ColorIndexingTransform { table_size: color_table_size, table_data: color_map, } } _ => unreachable!(), }; self.transforms[usize::from(transform_type_val)] = Some(transform_type); } Ok(xsize) } /// Adjusts the color map since it's subtraction coded fn adjust_color_map(color_map: &mut Vec) { for i in 1..color_map.len() { color_map[i] = add_pixels(color_map[i], color_map[i - 1]); } } /// Reads huffman codes associated with an image fn read_huffman_codes( &mut self, read_meta: bool, xsize: u16, ysize: u16, color_cache: Option, ) -> ImageResult { let mut num_huff_groups = 1; let mut huffman_bits = 0; let mut huffman_xsize = 1; let mut huffman_ysize = 1; let mut entropy_image = Vec::new(); if read_meta && self.bit_reader.read_bits::(1)? == 1 { //meta huffman codes huffman_bits = self.bit_reader.read_bits::(3)? + 2; huffman_xsize = subsample_size(xsize, huffman_bits); huffman_ysize = subsample_size(ysize, huffman_bits); entropy_image = self.decode_image_stream(huffman_xsize, huffman_ysize, false)?; for pixel in entropy_image.iter_mut() { let meta_huff_code = (*pixel >> 8) & 0xffff; *pixel = meta_huff_code; if meta_huff_code >= num_huff_groups { num_huff_groups = meta_huff_code + 1; } } } let mut hufftree_groups = Vec::new(); for _i in 0..num_huff_groups { let mut group: HuffmanCodeGroup = Default::default(); for j in 0..HUFFMAN_CODES_PER_META_CODE { let mut alphabet_size = ALPHABET_SIZE[j]; if j == 0 { if let Some(color_cache) = color_cache.as_ref() { alphabet_size += 1 << color_cache.color_cache_bits; } } let tree = self.read_huffman_code(alphabet_size)?; group[j] = tree; } hufftree_groups.push(group); } let huffman_mask = if huffman_bits == 0 { !0 } else { (1 << huffman_bits) - 1 }; let info = HuffmanInfo { xsize: huffman_xsize, _ysize: huffman_ysize, color_cache, image: entropy_image, bits: huffman_bits, mask: huffman_mask, huffman_code_groups: hufftree_groups, }; Ok(info) } /// Decodes and returns a single huffman tree fn read_huffman_code(&mut self, alphabet_size: u16) -> ImageResult { let simple = self.bit_reader.read_bits::(1)? == 1; if simple { let num_symbols = self.bit_reader.read_bits::(1)? + 1; let mut code_lengths = vec![u16::from(num_symbols - 1)]; let mut codes = vec![0]; let mut symbols = Vec::new(); let is_first_8bits = self.bit_reader.read_bits::(1)?; symbols.push(self.bit_reader.read_bits::(1 + 7 * is_first_8bits)?); if num_symbols == 2 { symbols.push(self.bit_reader.read_bits::(8)?); code_lengths.push(1); codes.push(1); } HuffmanTree::build_explicit(code_lengths, codes, symbols) } else { let mut code_length_code_lengths = vec![0; CODE_LENGTH_CODES]; let num_code_lengths = 4 + self.bit_reader.read_bits::(4)?; for i in 0..num_code_lengths { code_length_code_lengths[CODE_LENGTH_CODE_ORDER[i]] = self.bit_reader.read_bits(3)?; } let new_code_lengths = self.read_huffman_code_lengths(code_length_code_lengths, alphabet_size)?; HuffmanTree::build_implicit(new_code_lengths) } } /// Reads huffman code lengths fn read_huffman_code_lengths( &mut self, code_length_code_lengths: Vec, num_symbols: u16, ) -> ImageResult> { let table = HuffmanTree::build_implicit(code_length_code_lengths)?; let mut max_symbol = if self.bit_reader.read_bits::(1)? == 1 { let length_nbits = 2 + 2 * self.bit_reader.read_bits::(3)?; 2 + self.bit_reader.read_bits::(length_nbits)? } else { num_symbols }; let mut code_lengths = vec![0; usize::from(num_symbols)]; let mut prev_code_len = 8; //default code length let mut symbol = 0; while symbol < num_symbols { if max_symbol == 0 { break; } max_symbol -= 1; let code_len = table.read_symbol(&mut self.bit_reader)?; if code_len < 16 { code_lengths[usize::from(symbol)] = code_len; symbol += 1; if code_len != 0 { prev_code_len = code_len; } } else { let use_prev = code_len == 16; let slot = code_len - 16; let extra_bits = match slot { 0 => 2, 1 => 3, 2 => 7, _ => return Err(DecoderError::BitStreamError.into()), }; let repeat_offset = match slot { 0 | 1 => 3, 2 => 11, _ => return Err(DecoderError::BitStreamError.into()), }; let mut repeat = self.bit_reader.read_bits::(extra_bits)? + repeat_offset; if symbol + repeat > num_symbols { return Err(DecoderError::BitStreamError.into()); } else { let length = if use_prev { prev_code_len } else { 0 }; while repeat > 0 { repeat -= 1; code_lengths[usize::from(symbol)] = length; symbol += 1; } } } } Ok(code_lengths) } /// Decodes the image data using the huffman trees and either of the 3 methods of decoding fn decode_image_data( &mut self, width: u16, height: u16, mut huffman_info: HuffmanInfo, ) -> ImageResult> { let num_values = usize::from(width) * usize::from(height); let mut data = vec![0; num_values]; let huff_index = huffman_info.get_huff_index(0, 0); let mut tree = &huffman_info.huffman_code_groups[huff_index]; let mut last_cached = 0; let mut index = 0; let mut x = 0; let mut y = 0; while index < num_values { if (x & huffman_info.mask) == 0 { let index = huffman_info.get_huff_index(x, y); tree = &huffman_info.huffman_code_groups[index]; } let code = tree[GREEN].read_symbol(&mut self.bit_reader)?; //check code if code < 256 { //literal, so just use huffman codes and read as argb let red = tree[RED].read_symbol(&mut self.bit_reader)?; let blue = tree[BLUE].read_symbol(&mut self.bit_reader)?; let alpha = tree[ALPHA].read_symbol(&mut self.bit_reader)?; data[index] = (u32::from(alpha) << 24) + (u32::from(red) << 16) + (u32::from(code) << 8) + u32::from(blue); index += 1; x += 1; if x >= width { x = 0; y += 1; } } else if code < 256 + 24 { //backward reference, so go back and use that to add image data let length_symbol = code - 256; let length = Self::get_copy_distance(&mut self.bit_reader, length_symbol)?; let dist_symbol = tree[DIST].read_symbol(&mut self.bit_reader)?; let dist_code = Self::get_copy_distance(&mut self.bit_reader, dist_symbol)?; let dist = Self::plane_code_to_distance(width, dist_code); if index < dist || num_values - index < length { return Err(DecoderError::BitStreamError.into()); } for i in 0..length { data[index + i] = data[index + i - dist]; } index += length; x += u16::try_from(length).unwrap(); while x >= width { x -= width; y += 1; } if index < num_values { let index = huffman_info.get_huff_index(x, y); tree = &huffman_info.huffman_code_groups[index]; } } else { //color cache, so use previously stored pixels to get this pixel let key = code - 256 - 24; if let Some(color_cache) = huffman_info.color_cache.as_mut() { //cache old colors while last_cached < index { color_cache.insert(data[last_cached]); last_cached += 1; } data[index] = color_cache.lookup(key.into())?; } else { return Err(DecoderError::BitStreamError.into()); } index += 1; x += 1; if x >= width { x = 0; y += 1; } } } Ok(data) } /// Reads color cache data from the bitstream fn read_color_cache(&mut self) -> ImageResult> { if self.bit_reader.read_bits::(1)? == 1 { let code_bits = self.bit_reader.read_bits::(4)?; if !(1..=11).contains(&code_bits) { return Err(DecoderError::InvalidColorCacheBits(code_bits).into()); } Ok(Some(code_bits)) } else { Ok(None) } } /// Gets the copy distance from the prefix code and bitstream fn get_copy_distance(bit_reader: &mut BitReader, prefix_code: u16) -> ImageResult { if prefix_code < 4 { return Ok(usize::from(prefix_code + 1)); } let extra_bits: u8 = ((prefix_code - 2) >> 1).try_into().unwrap(); let offset = (2 + (usize::from(prefix_code) & 1)) << extra_bits; Ok(offset + bit_reader.read_bits::(extra_bits)? + 1) } /// Gets distance to pixel fn plane_code_to_distance(xsize: u16, plane_code: usize) -> usize { if plane_code > 120 { plane_code - 120 } else { let (xoffset, yoffset) = DISTANCE_MAP[plane_code - 1]; let dist = i32::from(xoffset) + i32::from(yoffset) * i32::from(xsize); if dist < 1 { return 1; } dist.try_into().unwrap() } } } #[derive(Debug, Clone)] struct HuffmanInfo { xsize: u16, _ysize: u16, color_cache: Option, image: Vec, bits: u8, mask: u16, huffman_code_groups: Vec, } impl HuffmanInfo { fn get_huff_index(&self, x: u16, y: u16) -> usize { if self.bits == 0 { return 0; } let position = usize::from((y >> self.bits) * self.xsize + (x >> self.bits)); let meta_huff_code: usize = self.image[position].try_into().unwrap(); meta_huff_code } } #[derive(Debug, Clone)] struct ColorCache { color_cache_bits: u8, color_cache: Vec, } impl ColorCache { fn insert(&mut self, color: u32) { let index = (0x1e35a7bdu32.overflowing_mul(color).0) >> (32 - self.color_cache_bits); self.color_cache[index as usize] = color; } fn lookup(&self, index: usize) -> ImageResult { match self.color_cache.get(index) { Some(&value) => Ok(value), None => Err(DecoderError::BitStreamError.into()), } } } #[derive(Debug, Clone)] pub(crate) struct BitReader { buf: Vec, index: usize, bit_count: u8, } impl BitReader { fn new() -> BitReader { BitReader { buf: Vec::new(), index: 0, bit_count: 0, } } fn init(&mut self, buf: Vec) { self.buf = buf; } pub(crate) fn read_bits(&mut self, num: u8) -> ImageResult where T: num_traits::Unsigned + Shl + AddAssign + From, { let mut value: T = T::zero(); for i in 0..num { if self.buf.len() <= self.index { return Err(DecoderError::BitStreamError.into()); } let bit_true = self.buf[self.index] & (1 << self.bit_count) != 0; value += T::from(bit_true) << i; self.bit_count = if self.bit_count == 7 { self.index += 1; 0 } else { self.bit_count + 1 }; } Ok(value) } } #[derive(Debug, Clone, Default)] pub(crate) struct LosslessFrame { pub(crate) width: u16, pub(crate) height: u16, pub(crate) buf: Vec, } impl LosslessFrame { /// Fills a buffer by converting from argb to rgba pub(crate) fn fill_rgba(&self, buf: &mut [u8]) { for (&argb_val, chunk) in self.buf.iter().zip(buf.chunks_exact_mut(4)) { chunk[0] = ((argb_val >> 16) & 0xff).try_into().unwrap(); chunk[1] = ((argb_val >> 8) & 0xff).try_into().unwrap(); chunk[2] = (argb_val & 0xff).try_into().unwrap(); chunk[3] = ((argb_val >> 24) & 0xff).try_into().unwrap(); } } /// Get buffer size from the image pub(crate) fn get_buf_size(&self) -> usize { usize::from(self.width) * usize::from(self.height) * 4 } /// Fills a buffer with just the green values from the lossless decoding /// Used in extended alpha decoding pub(crate) fn fill_green(&self, buf: &mut [u8]) { for (&argb_val, buf_value) in self.buf.iter().zip(buf.iter_mut()) { *buf_value = ((argb_val >> 8) & 0xff).try_into().unwrap(); } } } #[cfg(test)] mod test { use super::BitReader; #[test] fn bit_read_test() { let mut bit_reader = BitReader::new(); //10011100 01000001 11100001 let buf = vec![0x9C, 0x41, 0xE1]; bit_reader.init(buf); assert_eq!(bit_reader.read_bits::(3).unwrap(), 4); //100 assert_eq!(bit_reader.read_bits::(2).unwrap(), 3); //11 assert_eq!(bit_reader.read_bits::(6).unwrap(), 12); //001100 assert_eq!(bit_reader.read_bits::(10).unwrap(), 40); //0000101000 assert_eq!(bit_reader.read_bits::(3).unwrap(), 7); //111 } #[test] fn bit_read_error_test() { let mut bit_reader = BitReader::new(); //01101010 let buf = vec![0x6A]; bit_reader.init(buf); assert_eq!(bit_reader.read_bits::(3).unwrap(), 2); //010 assert_eq!(bit_reader.read_bits::(5).unwrap(), 13); //01101 assert!(bit_reader.read_bits::(4).is_err()); //error } } image-0.24.7/src/codecs/webp/lossless_transform.rs000064400000000000000000000416161046102023000203040ustar 00000000000000use std::convert::TryFrom; use std::convert::TryInto; use super::lossless::subsample_size; use super::lossless::DecoderError; #[derive(Debug, Clone)] pub(crate) enum TransformType { PredictorTransform { size_bits: u8, predictor_data: Vec, }, ColorTransform { size_bits: u8, transform_data: Vec, }, SubtractGreen, ColorIndexingTransform { table_size: u16, table_data: Vec, }, } impl TransformType { /// Applies a transform to the image data pub(crate) fn apply_transform( &self, image_data: &mut Vec, width: u16, height: u16, ) -> Result<(), DecoderError> { match self { TransformType::PredictorTransform { size_bits, predictor_data, } => { let block_xsize = usize::from(subsample_size(width, *size_bits)); let width = usize::from(width); let height = usize::from(height); if image_data.len() < width * height { return Err(DecoderError::TransformError); } //handle top and left borders specially //this involves ignoring mode and just setting prediction values like this image_data[0] = add_pixels(image_data[0], 0xff000000); for x in 1..width { image_data[x] = add_pixels(image_data[x], get_left(image_data, x, 0, width)); } for y in 1..height { image_data[y * width] = add_pixels(image_data[y * width], get_top(image_data, 0, y, width)); } for y in 1..height { for x in 1..width { let block_index = (y >> size_bits) * block_xsize + (x >> size_bits); let index = y * width + x; let green = (predictor_data[block_index] >> 8) & 0xff; match green { 0 => image_data[index] = add_pixels(image_data[index], 0xff000000), 1 => { image_data[index] = add_pixels(image_data[index], get_left(image_data, x, y, width)) } 2 => { image_data[index] = add_pixels(image_data[index], get_top(image_data, x, y, width)) } 3 => { image_data[index] = add_pixels( image_data[index], get_top_right(image_data, x, y, width), ) } 4 => { image_data[index] = add_pixels( image_data[index], get_top_left(image_data, x, y, width), ) } 5 => { image_data[index] = add_pixels(image_data[index], { let first = average2( get_left(image_data, x, y, width), get_top_right(image_data, x, y, width), ); average2(first, get_top(image_data, x, y, width)) }) } 6 => { image_data[index] = add_pixels( image_data[index], average2( get_left(image_data, x, y, width), get_top_left(image_data, x, y, width), ), ) } 7 => { image_data[index] = add_pixels( image_data[index], average2( get_left(image_data, x, y, width), get_top(image_data, x, y, width), ), ) } 8 => { image_data[index] = add_pixels( image_data[index], average2( get_top_left(image_data, x, y, width), get_top(image_data, x, y, width), ), ) } 9 => { image_data[index] = add_pixels( image_data[index], average2( get_top(image_data, x, y, width), get_top_right(image_data, x, y, width), ), ) } 10 => { image_data[index] = add_pixels(image_data[index], { let first = average2( get_left(image_data, x, y, width), get_top_left(image_data, x, y, width), ); let second = average2( get_top(image_data, x, y, width), get_top_right(image_data, x, y, width), ); average2(first, second) }) } 11 => { image_data[index] = add_pixels( image_data[index], select( get_left(image_data, x, y, width), get_top(image_data, x, y, width), get_top_left(image_data, x, y, width), ), ) } 12 => { image_data[index] = add_pixels( image_data[index], clamp_add_subtract_full( get_left(image_data, x, y, width), get_top(image_data, x, y, width), get_top_left(image_data, x, y, width), ), ) } 13 => { image_data[index] = add_pixels(image_data[index], { let first = average2( get_left(image_data, x, y, width), get_top(image_data, x, y, width), ); clamp_add_subtract_half( first, get_top_left(image_data, x, y, width), ) }) } _ => {} } } } } TransformType::ColorTransform { size_bits, transform_data, } => { let block_xsize = usize::from(subsample_size(width, *size_bits)); let width = usize::from(width); let height = usize::from(height); for y in 0..height { for x in 0..width { let block_index = (y >> size_bits) * block_xsize + (x >> size_bits); let index = y * width + x; let multiplier = ColorTransformElement::from_color_code(transform_data[block_index]); image_data[index] = transform_color(&multiplier, image_data[index]); } } } TransformType::SubtractGreen => { let width = usize::from(width); for y in 0..usize::from(height) { for x in 0..width { image_data[y * width + x] = add_green(image_data[y * width + x]); } } } TransformType::ColorIndexingTransform { table_size, table_data, } => { let mut new_image_data = Vec::with_capacity(usize::from(width) * usize::from(height)); let table_size = *table_size; let width_bits: u8 = if table_size <= 2 { 3 } else if table_size <= 4 { 2 } else if table_size <= 16 { 1 } else { 0 }; let bits_per_pixel = 8 >> width_bits; let mask = (1 << bits_per_pixel) - 1; let mut src = 0; let width = usize::from(width); let pixels_per_byte = 1 << width_bits; let count_mask = pixels_per_byte - 1; let mut packed_pixels = 0; for _y in 0..usize::from(height) { for x in 0..width { if (x & count_mask) == 0 { packed_pixels = (image_data[src] >> 8) & 0xff; src += 1; } let pixels: usize = (packed_pixels & mask).try_into().unwrap(); let new_val = if pixels >= table_size.into() { 0x00000000 } else { table_data[pixels] }; new_image_data.push(new_val); packed_pixels >>= bits_per_pixel; } } *image_data = new_image_data; } } Ok(()) } } //predictor functions /// Adds 2 pixels mod 256 for each pixel pub(crate) fn add_pixels(a: u32, b: u32) -> u32 { let new_alpha = ((a >> 24) + (b >> 24)) & 0xff; let new_red = (((a >> 16) & 0xff) + ((b >> 16) & 0xff)) & 0xff; let new_green = (((a >> 8) & 0xff) + ((b >> 8) & 0xff)) & 0xff; let new_blue = ((a & 0xff) + (b & 0xff)) & 0xff; (new_alpha << 24) + (new_red << 16) + (new_green << 8) + new_blue } /// Get left pixel fn get_left(data: &[u32], x: usize, y: usize, width: usize) -> u32 { data[y * width + x - 1] } /// Get top pixel fn get_top(data: &[u32], x: usize, y: usize, width: usize) -> u32 { data[(y - 1) * width + x] } /// Get pixel to top right fn get_top_right(data: &[u32], x: usize, y: usize, width: usize) -> u32 { // if x == width - 1 this gets the left most pixel of the current row // as described in the specification data[(y - 1) * width + x + 1] } /// Get pixel to top left fn get_top_left(data: &[u32], x: usize, y: usize, width: usize) -> u32 { data[(y - 1) * width + x - 1] } /// Get average of 2 pixels fn average2(a: u32, b: u32) -> u32 { let mut avg = 0u32; for i in 0..4 { let sub_a: u8 = ((a >> (i * 8)) & 0xff).try_into().unwrap(); let sub_b: u8 = ((b >> (i * 8)) & 0xff).try_into().unwrap(); avg |= u32::from(sub_average2(sub_a, sub_b)) << (i * 8); } avg } /// Get average of 2 bytes fn sub_average2(a: u8, b: u8) -> u8 { ((u16::from(a) + u16::from(b)) / 2).try_into().unwrap() } /// Get a specific byte from argb pixel fn get_byte(val: u32, byte: u8) -> u8 { ((val >> (byte * 8)) & 0xff).try_into().unwrap() } /// Get byte as i32 for convenience fn get_byte_i32(val: u32, byte: u8) -> i32 { i32::from(get_byte(val, byte)) } /// Select left or top byte fn select(left: u32, top: u32, top_left: u32) -> u32 { let predict_alpha = get_byte_i32(left, 3) + get_byte_i32(top, 3) - get_byte_i32(top_left, 3); let predict_red = get_byte_i32(left, 2) + get_byte_i32(top, 2) - get_byte_i32(top_left, 2); let predict_green = get_byte_i32(left, 1) + get_byte_i32(top, 1) - get_byte_i32(top_left, 1); let predict_blue = get_byte_i32(left, 0) + get_byte_i32(top, 0) - get_byte_i32(top_left, 0); let predict_left = i32::abs(predict_alpha - get_byte_i32(left, 3)) + i32::abs(predict_red - get_byte_i32(left, 2)) + i32::abs(predict_green - get_byte_i32(left, 1)) + i32::abs(predict_blue - get_byte_i32(left, 0)); let predict_top = i32::abs(predict_alpha - get_byte_i32(top, 3)) + i32::abs(predict_red - get_byte_i32(top, 2)) + i32::abs(predict_green - get_byte_i32(top, 1)) + i32::abs(predict_blue - get_byte_i32(top, 0)); if predict_left < predict_top { left } else { top } } /// Clamp a to [0, 255] fn clamp(a: i32) -> i32 { if a < 0 { 0 } else if a > 255 { 255 } else { a } } /// Clamp add subtract full on one part fn clamp_add_subtract_full_sub(a: i32, b: i32, c: i32) -> i32 { clamp(a + b - c) } /// Clamp add subtract half on one part fn clamp_add_subtract_half_sub(a: i32, b: i32) -> i32 { clamp(a + (a - b) / 2) } /// Clamp add subtract full on 3 pixels fn clamp_add_subtract_full(a: u32, b: u32, c: u32) -> u32 { let mut value: u32 = 0; for i in 0..4u8 { let sub_a: i32 = ((a >> (i * 8)) & 0xff).try_into().unwrap(); let sub_b: i32 = ((b >> (i * 8)) & 0xff).try_into().unwrap(); let sub_c: i32 = ((c >> (i * 8)) & 0xff).try_into().unwrap(); value |= u32::try_from(clamp_add_subtract_full_sub(sub_a, sub_b, sub_c)).unwrap() << (i * 8); } value } /// Clamp add subtract half on 2 pixels fn clamp_add_subtract_half(a: u32, b: u32) -> u32 { let mut value = 0; for i in 0..4u8 { let sub_a: i32 = ((a >> (i * 8)) & 0xff).try_into().unwrap(); let sub_b: i32 = ((b >> (i * 8)) & 0xff).try_into().unwrap(); value |= u32::try_from(clamp_add_subtract_half_sub(sub_a, sub_b)).unwrap() << (i * 8); } value } //color transform #[derive(Debug, Clone, Copy)] struct ColorTransformElement { green_to_red: u8, green_to_blue: u8, red_to_blue: u8, } impl ColorTransformElement { fn from_color_code(color_code: u32) -> ColorTransformElement { ColorTransformElement { green_to_red: (color_code & 0xff).try_into().unwrap(), green_to_blue: ((color_code >> 8) & 0xff).try_into().unwrap(), red_to_blue: ((color_code >> 16) & 0xff).try_into().unwrap(), } } } /// Does color transform on red and blue transformed by green fn color_transform(red: u8, blue: u8, green: u8, trans: &ColorTransformElement) -> (u8, u8) { let mut temp_red = u32::from(red); let mut temp_blue = u32::from(blue); //as does the conversion from u8 to signed two's complement i8 required temp_red += color_transform_delta(trans.green_to_red as i8, green as i8); temp_blue += color_transform_delta(trans.green_to_blue as i8, green as i8); temp_blue += color_transform_delta(trans.red_to_blue as i8, temp_red as i8); ( (temp_red & 0xff).try_into().unwrap(), (temp_blue & 0xff).try_into().unwrap(), ) } /// Does color transform on 2 numbers fn color_transform_delta(t: i8, c: i8) -> u32 { ((i16::from(t) * i16::from(c)) as u32) >> 5 } // Does color transform on a pixel with a color transform element fn transform_color(multiplier: &ColorTransformElement, color_value: u32) -> u32 { let alpha = get_byte(color_value, 3); let red = get_byte(color_value, 2); let green = get_byte(color_value, 1); let blue = get_byte(color_value, 0); let (new_red, new_blue) = color_transform(red, blue, green, multiplier); (u32::from(alpha) << 24) + (u32::from(new_red) << 16) + (u32::from(green) << 8) + u32::from(new_blue) } //subtract green function /// Adds green to red and blue of a pixel fn add_green(argb: u32) -> u32 { let red = (argb >> 16) & 0xff; let green = (argb >> 8) & 0xff; let blue = argb & 0xff; let new_red = (red + green) & 0xff; let new_blue = (blue + green) & 0xff; (argb & 0xff00ff00) | (new_red << 16) | (new_blue) } image-0.24.7/src/codecs/webp/mod.rs000064400000000000000000000010601046102023000151060ustar 00000000000000//! Decoding and Encoding of WebP Images #[cfg(feature = "webp-encoder")] pub use self::encoder::{WebPEncoder, WebPQuality}; #[cfg(feature = "webp-encoder")] mod encoder; #[cfg(feature = "webp")] pub use self::decoder::WebPDecoder; #[cfg(feature = "webp")] mod decoder; #[cfg(feature = "webp")] mod extended; #[cfg(feature = "webp")] mod huffman; #[cfg(feature = "webp")] mod loop_filter; #[cfg(feature = "webp")] mod lossless; #[cfg(feature = "webp")] mod lossless_transform; #[cfg(feature = "webp")] mod transform; #[cfg(feature = "webp")] pub mod vp8; image-0.24.7/src/codecs/webp/transform.rs000064400000000000000000000047471046102023000163610ustar 00000000000000static CONST1: i64 = 20091; static CONST2: i64 = 35468; pub(crate) fn idct4x4(block: &mut [i32]) { // The intermediate results may overflow the types, so we stretch the type. fn fetch(block: &mut [i32], idx: usize) -> i64 { i64::from(block[idx]) } for i in 0usize..4 { let a1 = fetch(block, i) + fetch(block, 8 + i); let b1 = fetch(block, i) - fetch(block, 8 + i); let t1 = (fetch(block, 4 + i) * CONST2) >> 16; let t2 = fetch(block, 12 + i) + ((fetch(block, 12 + i) * CONST1) >> 16); let c1 = t1 - t2; let t1 = fetch(block, 4 + i) + ((fetch(block, 4 + i) * CONST1) >> 16); let t2 = (fetch(block, 12 + i) * CONST2) >> 16; let d1 = t1 + t2; block[i] = (a1 + d1) as i32; block[4 + i] = (b1 + c1) as i32; block[4 * 3 + i] = (a1 - d1) as i32; block[4 * 2 + i] = (b1 - c1) as i32; } for i in 0usize..4 { let a1 = fetch(block, 4 * i) + fetch(block, 4 * i + 2); let b1 = fetch(block, 4 * i) - fetch(block, 4 * i + 2); let t1 = (fetch(block, 4 * i + 1) * CONST2) >> 16; let t2 = fetch(block, 4 * i + 3) + ((fetch(block, 4 * i + 3) * CONST1) >> 16); let c1 = t1 - t2; let t1 = fetch(block, 4 * i + 1) + ((fetch(block, 4 * i + 1) * CONST1) >> 16); let t2 = (fetch(block, 4 * i + 3) * CONST2) >> 16; let d1 = t1 + t2; block[4 * i] = ((a1 + d1 + 4) >> 3) as i32; block[4 * i + 3] = ((a1 - d1 + 4) >> 3) as i32; block[4 * i + 1] = ((b1 + c1 + 4) >> 3) as i32; block[4 * i + 2] = ((b1 - c1 + 4) >> 3) as i32; } } // 14.3 pub(crate) fn iwht4x4(block: &mut [i32]) { for i in 0usize..4 { let a1 = block[i] + block[12 + i]; let b1 = block[4 + i] + block[8 + i]; let c1 = block[4 + i] - block[8 + i]; let d1 = block[i] - block[12 + i]; block[i] = a1 + b1; block[4 + i] = c1 + d1; block[8 + i] = a1 - b1; block[12 + i] = d1 - c1; } for i in 0usize..4 { let a1 = block[4 * i] + block[4 * i + 3]; let b1 = block[4 * i + 1] + block[4 * i + 2]; let c1 = block[4 * i + 1] - block[4 * i + 2]; let d1 = block[4 * i] - block[4 * i + 3]; let a2 = a1 + b1; let b2 = c1 + d1; let c2 = a1 - b1; let d2 = d1 - c1; block[4 * i] = (a2 + 3) >> 3; block[4 * i + 1] = (b2 + 3) >> 3; block[4 * i + 2] = (c2 + 3) >> 3; block[4 * i + 3] = (d2 + 3) >> 3; } } image-0.24.7/src/codecs/webp/vp8.rs000064400000000000000000002757571046102023000150760ustar 00000000000000//! An implementation of the VP8 Video Codec //! //! This module contains a partial implementation of the //! VP8 video format as defined in RFC-6386. //! //! It decodes Keyframes only. //! VP8 is the underpinning of the WebP image format //! //! # Related Links //! * [rfc-6386](http://tools.ietf.org/html/rfc6386) - The VP8 Data Format and Decoding Guide //! * [VP8.pdf](http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/37073.pdf) - An overview of //! of the VP8 format //! use byteorder::{LittleEndian, ReadBytesExt}; use std::convert::TryInto; use std::default::Default; use std::io::Read; use std::{cmp, error, fmt}; use super::loop_filter; use super::transform; use crate::error::{ DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, }; use crate::image::ImageFormat; use crate::utils::clamp; const MAX_SEGMENTS: usize = 4; const NUM_DCT_TOKENS: usize = 12; // Prediction modes const DC_PRED: i8 = 0; const V_PRED: i8 = 1; const H_PRED: i8 = 2; const TM_PRED: i8 = 3; const B_PRED: i8 = 4; const B_DC_PRED: i8 = 0; const B_TM_PRED: i8 = 1; const B_VE_PRED: i8 = 2; const B_HE_PRED: i8 = 3; const B_LD_PRED: i8 = 4; const B_RD_PRED: i8 = 5; const B_VR_PRED: i8 = 6; const B_VL_PRED: i8 = 7; const B_HD_PRED: i8 = 8; const B_HU_PRED: i8 = 9; // Prediction mode enum #[repr(i8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum LumaMode { /// Predict DC using row above and column to the left. DC = DC_PRED, /// Predict rows using row above. V = V_PRED, /// Predict columns using column to the left. H = H_PRED, /// Propagate second differences. TM = TM_PRED, /// Each Y subblock is independently predicted. B = B_PRED, } #[repr(i8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum ChromaMode { /// Predict DC using row above and column to the left. DC = DC_PRED, /// Predict rows using row above. V = V_PRED, /// Predict columns using column to the left. H = H_PRED, /// Propagate second differences. TM = TM_PRED, } #[repr(i8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum IntraMode { DC = B_DC_PRED, TM = B_TM_PRED, VE = B_VE_PRED, HE = B_HE_PRED, LD = B_LD_PRED, RD = B_RD_PRED, VR = B_VR_PRED, VL = B_VL_PRED, HD = B_HD_PRED, HU = B_HU_PRED, } type Prob = u8; static SEGMENT_ID_TREE: [i8; 6] = [2, 4, -0, -1, -2, -3]; // Section 11.2 // Tree for determining the keyframe luma intra prediction modes: static KEYFRAME_YMODE_TREE: [i8; 8] = [-B_PRED, 2, 4, 6, -DC_PRED, -V_PRED, -H_PRED, -TM_PRED]; // Default probabilities for decoding the keyframe luma modes static KEYFRAME_YMODE_PROBS: [Prob; 4] = [145, 156, 163, 128]; // Tree for determining the keyframe B_PRED mode: static KEYFRAME_BPRED_MODE_TREE: [i8; 18] = [ -B_DC_PRED, 2, -B_TM_PRED, 4, -B_VE_PRED, 6, 8, 12, -B_HE_PRED, 10, -B_RD_PRED, -B_VR_PRED, -B_LD_PRED, 14, -B_VL_PRED, 16, -B_HD_PRED, -B_HU_PRED, ]; // Probabilities for the BPRED_MODE_TREE static KEYFRAME_BPRED_MODE_PROBS: [[[u8; 9]; 10]; 10] = [ [ [231, 120, 48, 89, 115, 113, 120, 152, 112], [152, 179, 64, 126, 170, 118, 46, 70, 95], [175, 69, 143, 80, 85, 82, 72, 155, 103], [56, 58, 10, 171, 218, 189, 17, 13, 152], [144, 71, 10, 38, 171, 213, 144, 34, 26], [114, 26, 17, 163, 44, 195, 21, 10, 173], [121, 24, 80, 195, 26, 62, 44, 64, 85], [170, 46, 55, 19, 136, 160, 33, 206, 71], [63, 20, 8, 114, 114, 208, 12, 9, 226], [81, 40, 11, 96, 182, 84, 29, 16, 36], ], [ [134, 183, 89, 137, 98, 101, 106, 165, 148], [72, 187, 100, 130, 157, 111, 32, 75, 80], [66, 102, 167, 99, 74, 62, 40, 234, 128], [41, 53, 9, 178, 241, 141, 26, 8, 107], [104, 79, 12, 27, 217, 255, 87, 17, 7], [74, 43, 26, 146, 73, 166, 49, 23, 157], [65, 38, 105, 160, 51, 52, 31, 115, 128], [87, 68, 71, 44, 114, 51, 15, 186, 23], [47, 41, 14, 110, 182, 183, 21, 17, 194], [66, 45, 25, 102, 197, 189, 23, 18, 22], ], [ [88, 88, 147, 150, 42, 46, 45, 196, 205], [43, 97, 183, 117, 85, 38, 35, 179, 61], [39, 53, 200, 87, 26, 21, 43, 232, 171], [56, 34, 51, 104, 114, 102, 29, 93, 77], [107, 54, 32, 26, 51, 1, 81, 43, 31], [39, 28, 85, 171, 58, 165, 90, 98, 64], [34, 22, 116, 206, 23, 34, 43, 166, 73], [68, 25, 106, 22, 64, 171, 36, 225, 114], [34, 19, 21, 102, 132, 188, 16, 76, 124], [62, 18, 78, 95, 85, 57, 50, 48, 51], ], [ [193, 101, 35, 159, 215, 111, 89, 46, 111], [60, 148, 31, 172, 219, 228, 21, 18, 111], [112, 113, 77, 85, 179, 255, 38, 120, 114], [40, 42, 1, 196, 245, 209, 10, 25, 109], [100, 80, 8, 43, 154, 1, 51, 26, 71], [88, 43, 29, 140, 166, 213, 37, 43, 154], [61, 63, 30, 155, 67, 45, 68, 1, 209], [142, 78, 78, 16, 255, 128, 34, 197, 171], [41, 40, 5, 102, 211, 183, 4, 1, 221], [51, 50, 17, 168, 209, 192, 23, 25, 82], ], [ [125, 98, 42, 88, 104, 85, 117, 175, 82], [95, 84, 53, 89, 128, 100, 113, 101, 45], [75, 79, 123, 47, 51, 128, 81, 171, 1], [57, 17, 5, 71, 102, 57, 53, 41, 49], [115, 21, 2, 10, 102, 255, 166, 23, 6], [38, 33, 13, 121, 57, 73, 26, 1, 85], [41, 10, 67, 138, 77, 110, 90, 47, 114], [101, 29, 16, 10, 85, 128, 101, 196, 26], [57, 18, 10, 102, 102, 213, 34, 20, 43], [117, 20, 15, 36, 163, 128, 68, 1, 26], ], [ [138, 31, 36, 171, 27, 166, 38, 44, 229], [67, 87, 58, 169, 82, 115, 26, 59, 179], [63, 59, 90, 180, 59, 166, 93, 73, 154], [40, 40, 21, 116, 143, 209, 34, 39, 175], [57, 46, 22, 24, 128, 1, 54, 17, 37], [47, 15, 16, 183, 34, 223, 49, 45, 183], [46, 17, 33, 183, 6, 98, 15, 32, 183], [65, 32, 73, 115, 28, 128, 23, 128, 205], [40, 3, 9, 115, 51, 192, 18, 6, 223], [87, 37, 9, 115, 59, 77, 64, 21, 47], ], [ [104, 55, 44, 218, 9, 54, 53, 130, 226], [64, 90, 70, 205, 40, 41, 23, 26, 57], [54, 57, 112, 184, 5, 41, 38, 166, 213], [30, 34, 26, 133, 152, 116, 10, 32, 134], [75, 32, 12, 51, 192, 255, 160, 43, 51], [39, 19, 53, 221, 26, 114, 32, 73, 255], [31, 9, 65, 234, 2, 15, 1, 118, 73], [88, 31, 35, 67, 102, 85, 55, 186, 85], [56, 21, 23, 111, 59, 205, 45, 37, 192], [55, 38, 70, 124, 73, 102, 1, 34, 98], ], [ [102, 61, 71, 37, 34, 53, 31, 243, 192], [69, 60, 71, 38, 73, 119, 28, 222, 37], [68, 45, 128, 34, 1, 47, 11, 245, 171], [62, 17, 19, 70, 146, 85, 55, 62, 70], [75, 15, 9, 9, 64, 255, 184, 119, 16], [37, 43, 37, 154, 100, 163, 85, 160, 1], [63, 9, 92, 136, 28, 64, 32, 201, 85], [86, 6, 28, 5, 64, 255, 25, 248, 1], [56, 8, 17, 132, 137, 255, 55, 116, 128], [58, 15, 20, 82, 135, 57, 26, 121, 40], ], [ [164, 50, 31, 137, 154, 133, 25, 35, 218], [51, 103, 44, 131, 131, 123, 31, 6, 158], [86, 40, 64, 135, 148, 224, 45, 183, 128], [22, 26, 17, 131, 240, 154, 14, 1, 209], [83, 12, 13, 54, 192, 255, 68, 47, 28], [45, 16, 21, 91, 64, 222, 7, 1, 197], [56, 21, 39, 155, 60, 138, 23, 102, 213], [85, 26, 85, 85, 128, 128, 32, 146, 171], [18, 11, 7, 63, 144, 171, 4, 4, 246], [35, 27, 10, 146, 174, 171, 12, 26, 128], ], [ [190, 80, 35, 99, 180, 80, 126, 54, 45], [85, 126, 47, 87, 176, 51, 41, 20, 32], [101, 75, 128, 139, 118, 146, 116, 128, 85], [56, 41, 15, 176, 236, 85, 37, 9, 62], [146, 36, 19, 30, 171, 255, 97, 27, 20], [71, 30, 17, 119, 118, 255, 17, 18, 138], [101, 38, 60, 138, 55, 70, 43, 26, 142], [138, 45, 61, 62, 219, 1, 81, 188, 64], [32, 41, 20, 117, 151, 142, 20, 21, 163], [112, 19, 12, 61, 195, 128, 48, 4, 24], ], ]; // Section 11.4 Tree for determining macroblock the chroma mode static KEYFRAME_UV_MODE_TREE: [i8; 6] = [-DC_PRED, 2, -V_PRED, 4, -H_PRED, -TM_PRED]; // Probabilities for determining macroblock mode static KEYFRAME_UV_MODE_PROBS: [Prob; 3] = [142, 114, 183]; // Section 13.4 type TokenProbTables = [[[[Prob; NUM_DCT_TOKENS - 1]; 3]; 8]; 4]; // Probabilities that a token's probability will be updated static COEFF_UPDATE_PROBS: TokenProbTables = [ [ [ [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255], [223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255], [249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255], [234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255], [253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255], [239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255], [254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255], [251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255], [251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255], [254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255], [250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255], [254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], ], [ [ [217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255], [234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255], ], [ [255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255], [223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255], [238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255], [249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255], [247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255], [252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255], [253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255], [250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], ], [ [ [186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255], [234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255], [251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255], ], [ [255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255], [236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255], [251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255], ], [ [255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255], [254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255], [254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255], [254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], ], [ [ [248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255], [248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255], [246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255], [252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255], ], [ [255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255], [248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255], [253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255], [245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255], [253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255], [252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255], [255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255], [249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255], [250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], [ [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], ], ], ]; // Section 13.5 // Default Probabilities for tokens static COEFF_PROBS: TokenProbTables = [ [ [ [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128], [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128], [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128], ], [ [253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128], [189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128], [106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128], ], [ [1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128], [181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128], [78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128], ], [ [1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128], [184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128], [77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128], ], [ [1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128], [170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128], [37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128], ], [ [1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128], [207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128], [102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128], ], [ [1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128], [177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128], [80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128], ], [ [1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128], [246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128], [255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128], ], ], [ [ [198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62], [131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1], [68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128], ], [ [1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128], [184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128], [81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128], ], [ [1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128], [99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128], [23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128], ], [ [1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128], [109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128], [44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128], ], [ [1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128], [94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128], [22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128], ], [ [1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128], [124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128], [35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128], ], [ [1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128], [121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128], [45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128], ], [ [1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128], [203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128], [137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128], ], ], [ [ [253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128], [175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128], [73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128], ], [ [1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128], [239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128], [155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128], ], [ [1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128], [201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128], [69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128], ], [ [1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128], [223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128], [141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128], ], [ [1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128], [190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128], [149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128], ], [ [1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128], [247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128], [240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128], ], [ [1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128], [213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128], [55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128], ], [ [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128], [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128], [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128], ], ], [ [ [202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255], [126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128], [61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128], ], [ [1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128], [166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128], [39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128], ], [ [1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128], [124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128], [24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128], ], [ [1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128], [149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128], [28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128], ], [ [1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128], [123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128], [20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128], ], [ [1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128], [168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128], [47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128], ], [ [1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128], [141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128], [42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128], ], [ [1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128], [244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128], [238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128], ], ], ]; // DCT Tokens const DCT_0: i8 = 0; const DCT_1: i8 = 1; const DCT_2: i8 = 2; const DCT_3: i8 = 3; const DCT_4: i8 = 4; const DCT_CAT1: i8 = 5; const DCT_CAT2: i8 = 6; const DCT_CAT3: i8 = 7; const DCT_CAT4: i8 = 8; const DCT_CAT5: i8 = 9; const DCT_CAT6: i8 = 10; const DCT_EOB: i8 = 11; static DCT_TOKEN_TREE: [i8; 22] = [ -DCT_EOB, 2, -DCT_0, 4, -DCT_1, 6, 8, 12, -DCT_2, 10, -DCT_3, -DCT_4, 14, 16, -DCT_CAT1, -DCT_CAT2, 18, 20, -DCT_CAT3, -DCT_CAT4, -DCT_CAT5, -DCT_CAT6, ]; static PROB_DCT_CAT: [[Prob; 12]; 6] = [ [159, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [165, 145, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [173, 148, 140, 0, 0, 0, 0, 0, 0, 0, 0, 0], [176, 155, 140, 135, 0, 0, 0, 0, 0, 0, 0, 0], [180, 157, 141, 134, 130, 0, 0, 0, 0, 0, 0, 0], [254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129, 0], ]; static DCT_CAT_BASE: [u8; 6] = [5, 7, 11, 19, 35, 67]; static COEFF_BANDS: [u8; 16] = [0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7]; #[rustfmt::skip] static DC_QUANT: [i16; 128] = [ 4, 5, 6, 7, 8, 9, 10, 10, 11, 12, 13, 14, 15, 16, 17, 17, 18, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 91, 93, 95, 96, 98, 100, 101, 102, 104, 106, 108, 110, 112, 114, 116, 118, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 143, 145, 148, 151, 154, 157, ]; #[rustfmt::skip] static AC_QUANT: [i16; 128] = [ 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 119, 122, 125, 128, 131, 134, 137, 140, 143, 146, 149, 152, 155, 158, 161, 164, 167, 170, 173, 177, 181, 185, 189, 193, 197, 201, 205, 209, 213, 217, 221, 225, 229, 234, 239, 245, 249, 254, 259, 264, 269, 274, 279, 284, ]; static ZIGZAG: [u8; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15]; /// All errors that can occur when attempting to parse a VP8 codec inside WebP #[derive(Debug, Clone, Copy)] enum DecoderError { /// VP8's `[0x9D, 0x01, 0x2A]` magic not found or invalid Vp8MagicInvalid([u8; 3]), /// Decoder initialisation wasn't provided with enough data NotEnoughInitData, /// At time of writing, only the YUV colour-space encoded as `0` is specified ColorSpaceInvalid(u8), /// LUMA prediction mode was not recognised LumaPredictionModeInvalid(i8), /// Intra-prediction mode was not recognised IntraPredictionModeInvalid(i8), /// Chroma prediction mode was not recognised ChromaPredictionModeInvalid(i8), } impl fmt::Display for DecoderError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DecoderError::Vp8MagicInvalid(tag) => f.write_fmt(format_args!( "Invalid VP8 magic: [{:#04X?}, {:#04X?}, {:#04X?}]", tag[0], tag[1], tag[2] )), DecoderError::NotEnoughInitData => { f.write_str("Expected at least 2 bytes of VP8 decoder initialization data") } DecoderError::ColorSpaceInvalid(cs) => { f.write_fmt(format_args!("Invalid non-YUV VP8 color space {}", cs)) } DecoderError::LumaPredictionModeInvalid(pm) => { f.write_fmt(format_args!("Invalid VP8 LUMA prediction mode {}", pm)) } DecoderError::IntraPredictionModeInvalid(i) => { f.write_fmt(format_args!("Invalid VP8 intra-prediction mode {}", i)) } DecoderError::ChromaPredictionModeInvalid(c) => { f.write_fmt(format_args!("Invalid VP8 chroma prediction mode {}", c)) } } } } impl From for ImageError { fn from(e: DecoderError) -> ImageError { ImageError::Decoding(DecodingError::new(ImageFormat::WebP.into(), e)) } } impl error::Error for DecoderError {} struct BoolReader { buf: Vec, index: usize, range: u32, value: u32, bit_count: u8, } impl BoolReader { pub(crate) fn new() -> BoolReader { BoolReader { buf: Vec::new(), range: 0, value: 0, bit_count: 0, index: 0, } } pub(crate) fn init(&mut self, buf: Vec) -> ImageResult<()> { if buf.len() < 2 { return Err(DecoderError::NotEnoughInitData.into()); } self.buf = buf; // Direct access safe, since length has just been validated. self.value = (u32::from(self.buf[0]) << 8) | u32::from(self.buf[1]); self.index = 2; self.range = 255; self.bit_count = 0; Ok(()) } pub(crate) fn read_bool(&mut self, probability: u8) -> bool { let split = 1 + (((self.range - 1) * u32::from(probability)) >> 8); let bigsplit = split << 8; let retval = if self.value >= bigsplit { self.range -= split; self.value -= bigsplit; true } else { self.range = split; false }; while self.range < 128 { self.value <<= 1; self.range <<= 1; self.bit_count += 1; if self.bit_count == 8 { self.bit_count = 0; // If no more bits are available, just don't do anything. // This strategy is suggested in the reference implementation of RFC6386 (p.135) if self.index < self.buf.len() { self.value |= u32::from(self.buf[self.index]); self.index += 1; } } } retval } pub(crate) fn read_literal(&mut self, n: u8) -> u8 { let mut v = 0u8; let mut n = n; while n != 0 { v = (v << 1) + self.read_bool(128u8) as u8; n -= 1; } v } pub(crate) fn read_magnitude_and_sign(&mut self, n: u8) -> i32 { let magnitude = self.read_literal(n); let sign = self.read_literal(1); if sign == 1 { -i32::from(magnitude) } else { i32::from(magnitude) } } pub(crate) fn read_with_tree(&mut self, tree: &[i8], probs: &[Prob], start: isize) -> i8 { let mut index = start; loop { let a = self.read_bool(probs[index as usize >> 1]); let b = index + a as isize; index = tree[b as usize] as isize; if index <= 0 { break; } } -index as i8 } pub(crate) fn read_flag(&mut self) -> bool { 0 != self.read_literal(1) } } #[derive(Default, Clone, Copy)] struct MacroBlock { bpred: [IntraMode; 16], complexity: [u8; 9], luma_mode: LumaMode, chroma_mode: ChromaMode, segmentid: u8, coeffs_skipped: bool, } /// A Representation of the last decoded video frame #[derive(Default, Debug, Clone)] pub struct Frame { /// The width of the luma plane pub width: u16, /// The height of the luma plane pub height: u16, /// The luma plane of the frame pub ybuf: Vec, /// The blue plane of the frame pub ubuf: Vec, /// The red plane of the frame pub vbuf: Vec, /// Indicates whether this frame is a keyframe pub keyframe: bool, version: u8, /// Indicates whether this frame is intended for display pub for_display: bool, // Section 9.2 /// The pixel type of the frame as defined by Section 9.2 /// of the VP8 Specification pub pixel_type: u8, // Section 9.4 and 15 filter_type: bool, //if true uses simple filter // if false uses normal filter filter_level: u8, sharpness_level: u8, } impl Frame { /// Chroma plane is half the size of the Luma plane fn chroma_width(&self) -> u16 { (self.width + 1) / 2 } fn chroma_height(&self) -> u16 { (self.height + 1) / 2 } /// Fills an rgb buffer with the image pub(crate) fn fill_rgb(&self, buf: &mut [u8]) { for (index, rgb_chunk) in (0..self.ybuf.len()).zip(buf.chunks_exact_mut(3)) { let y = index / self.width as usize; let x = index % self.width as usize; let chroma_index = self.chroma_width() as usize * (y / 2) + x / 2; Frame::fill_single( self.ybuf[index], self.ubuf[chroma_index], self.vbuf[chroma_index], rgb_chunk, ); } } /// Fills an rgba buffer by skipping the alpha values pub(crate) fn fill_rgba(&self, buf: &mut [u8]) { for (index, rgba_chunk) in (0..self.ybuf.len()).zip(buf.chunks_exact_mut(4)) { let y = index / self.width as usize; let x = index % self.width as usize; let chroma_index = self.chroma_width() as usize * (y / 2) + x / 2; Frame::fill_single( self.ybuf[index], self.ubuf[chroma_index], self.vbuf[chroma_index], rgba_chunk, ); } } /// Conversion values from https://docs.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-yuv-formats-for-video-rendering#converting-8-bit-yuv-to-rgb888 fn fill_single(y: u8, u: u8, v: u8, rgb: &mut [u8]) { let c: i32 = i32::from(y) - 16; let d: i32 = i32::from(u) - 128; let e: i32 = i32::from(v) - 128; let r: u8 = clamp((298 * c + 409 * e + 128) >> 8, 0, 255) .try_into() .unwrap(); let g: u8 = clamp((298 * c - 100 * d - 208 * e + 128) >> 8, 0, 255) .try_into() .unwrap(); let b: u8 = clamp((298 * c + 516 * d + 128) >> 8, 0, 255) .try_into() .unwrap(); rgb[0] = r; rgb[1] = g; rgb[2] = b; } /// Gets the buffer size pub fn get_buf_size(&self) -> usize { self.ybuf.len() * 3 } } #[derive(Clone, Copy, Default)] struct Segment { ydc: i16, yac: i16, y2dc: i16, y2ac: i16, uvdc: i16, uvac: i16, delta_values: bool, quantizer_level: i8, loopfilter_level: i8, } /// VP8 Decoder /// /// Only decodes keyframes pub struct Vp8Decoder { r: R, b: BoolReader, mbwidth: u16, mbheight: u16, macroblocks: Vec, frame: Frame, segments_enabled: bool, segments_update_map: bool, segment: [Segment; MAX_SEGMENTS], ref_delta: [i32; 4], mode_delta: [i32; 4], partitions: [BoolReader; 8], num_partitions: u8, segment_tree_probs: [Prob; 3], token_probs: Box, // Section 9.10 prob_intra: Prob, // Section 9.11 prob_skip_false: Option, top: Vec, left: MacroBlock, top_border: Vec, left_border: Vec, } impl Vp8Decoder { /// Create a new decoder. /// The reader must present a raw vp8 bitstream to the decoder pub fn new(r: R) -> Vp8Decoder { let f = Frame::default(); let s = Segment::default(); let m = MacroBlock::default(); Vp8Decoder { r, b: BoolReader::new(), mbwidth: 0, mbheight: 0, macroblocks: Vec::new(), frame: f, segments_enabled: false, segments_update_map: false, segment: [s; MAX_SEGMENTS], ref_delta: [0; 4], mode_delta: [0; 4], partitions: [ BoolReader::new(), BoolReader::new(), BoolReader::new(), BoolReader::new(), BoolReader::new(), BoolReader::new(), BoolReader::new(), BoolReader::new(), ], num_partitions: 1, segment_tree_probs: [255u8; 3], token_probs: Box::new(COEFF_PROBS), // Section 9.10 prob_intra: 0u8, // Section 9.11 prob_skip_false: None, top: Vec::new(), left: m, top_border: Vec::new(), left_border: Vec::new(), } } fn update_token_probabilities(&mut self) { for (i, is) in COEFF_UPDATE_PROBS.iter().enumerate() { for (j, js) in is.iter().enumerate() { for (k, ks) in js.iter().enumerate() { for (t, prob) in ks.iter().enumerate().take(NUM_DCT_TOKENS - 1) { if self.b.read_bool(*prob) { let v = self.b.read_literal(8); self.token_probs[i][j][k][t] = v; } } } } } } fn init_partitions(&mut self, n: usize) -> ImageResult<()> { if n > 1 { let mut sizes = vec![0; 3 * n - 3]; self.r.read_exact(sizes.as_mut_slice())?; for (i, s) in sizes.chunks(3).enumerate() { let size = { s } .read_u24::() .expect("Reading from &[u8] can't fail and the chunk is complete"); let mut buf = vec![0; size as usize]; self.r.read_exact(buf.as_mut_slice())?; self.partitions[i].init(buf)?; } } let mut buf = Vec::new(); self.r.read_to_end(&mut buf)?; self.partitions[n - 1].init(buf)?; Ok(()) } fn read_quantization_indices(&mut self) { fn dc_quant(index: i32) -> i16 { DC_QUANT[clamp(index, 0, 127) as usize] } fn ac_quant(index: i32) -> i16 { AC_QUANT[clamp(index, 0, 127) as usize] } let yac_abs = self.b.read_literal(7); let ydc_delta = if self.b.read_flag() { self.b.read_magnitude_and_sign(4) } else { 0 }; let y2dc_delta = if self.b.read_flag() { self.b.read_magnitude_and_sign(4) } else { 0 }; let y2ac_delta = if self.b.read_flag() { self.b.read_magnitude_and_sign(4) } else { 0 }; let uvdc_delta = if self.b.read_flag() { self.b.read_magnitude_and_sign(4) } else { 0 }; let uvac_delta = if self.b.read_flag() { self.b.read_magnitude_and_sign(4) } else { 0 }; let n = if self.segments_enabled { MAX_SEGMENTS } else { 1 }; for i in 0usize..n { let base = i32::from(if !self.segment[i].delta_values { i16::from(self.segment[i].quantizer_level) } else { i16::from(self.segment[i].quantizer_level) + i16::from(yac_abs) }); self.segment[i].ydc = dc_quant(base + ydc_delta); self.segment[i].yac = ac_quant(base); self.segment[i].y2dc = dc_quant(base + y2dc_delta) * 2; // The intermediate result (max`284*155`) can be larger than the `i16` range. self.segment[i].y2ac = (i32::from(ac_quant(base + y2ac_delta)) * 155 / 100) as i16; self.segment[i].uvdc = dc_quant(base + uvdc_delta); self.segment[i].uvac = ac_quant(base + uvac_delta); if self.segment[i].y2ac < 8 { self.segment[i].y2ac = 8; } if self.segment[i].uvdc > 132 { self.segment[i].uvdc = 132; } } } fn read_loop_filter_adjustments(&mut self) { if self.b.read_flag() { for i in 0usize..4 { let ref_frame_delta_update_flag = self.b.read_flag(); self.ref_delta[i] = if ref_frame_delta_update_flag { self.b.read_magnitude_and_sign(6) } else { 0i32 }; } for i in 0usize..4 { let mb_mode_delta_update_flag = self.b.read_flag(); self.mode_delta[i] = if mb_mode_delta_update_flag { self.b.read_magnitude_and_sign(6) } else { 0i32 }; } } } fn read_segment_updates(&mut self) { // Section 9.3 self.segments_update_map = self.b.read_flag(); let update_segment_feature_data = self.b.read_flag(); if update_segment_feature_data { let segment_feature_mode = self.b.read_flag(); for i in 0usize..MAX_SEGMENTS { self.segment[i].delta_values = !segment_feature_mode; } for i in 0usize..MAX_SEGMENTS { let update = self.b.read_flag(); self.segment[i].quantizer_level = if update { self.b.read_magnitude_and_sign(7) } else { 0i32 } as i8; } for i in 0usize..MAX_SEGMENTS { let update = self.b.read_flag(); self.segment[i].loopfilter_level = if update { self.b.read_magnitude_and_sign(6) } else { 0i32 } as i8; } } if self.segments_update_map { for i in 0usize..3 { let update = self.b.read_flag(); self.segment_tree_probs[i] = if update { self.b.read_literal(8) } else { 255 }; } } } fn read_frame_header(&mut self) -> ImageResult<()> { let tag = self.r.read_u24::()?; self.frame.keyframe = tag & 1 == 0; self.frame.version = ((tag >> 1) & 7) as u8; self.frame.for_display = (tag >> 4) & 1 != 0; let first_partition_size = tag >> 5; if self.frame.keyframe { let mut tag = [0u8; 3]; self.r.read_exact(&mut tag)?; if tag != [0x9d, 0x01, 0x2a] { return Err(DecoderError::Vp8MagicInvalid(tag).into()); } let w = self.r.read_u16::()?; let h = self.r.read_u16::()?; self.frame.width = w & 0x3FFF; self.frame.height = h & 0x3FFF; self.top = init_top_macroblocks(self.frame.width as usize); // Almost always the first macro block, except when non exists (i.e. `width == 0`) self.left = self.top.get(0).cloned().unwrap_or_default(); self.mbwidth = (self.frame.width + 15) / 16; self.mbheight = (self.frame.height + 15) / 16; self.frame.ybuf = vec![0u8; self.frame.width as usize * self.frame.height as usize]; self.frame.ubuf = vec![0u8; self.frame.chroma_width() as usize * self.frame.chroma_height() as usize]; self.frame.vbuf = vec![0u8; self.frame.chroma_width() as usize * self.frame.chroma_height() as usize]; self.top_border = vec![127u8; self.frame.width as usize + 4 + 16]; self.left_border = vec![129u8; 1 + 16]; } let mut buf = vec![0; first_partition_size as usize]; self.r.read_exact(&mut buf)?; // initialise binary decoder self.b.init(buf)?; if self.frame.keyframe { let color_space = self.b.read_literal(1); self.frame.pixel_type = self.b.read_literal(1); if color_space != 0 { return Err(DecoderError::ColorSpaceInvalid(color_space).into()); } } self.segments_enabled = self.b.read_flag(); if self.segments_enabled { self.read_segment_updates(); } self.frame.filter_type = self.b.read_flag(); self.frame.filter_level = self.b.read_literal(6); self.frame.sharpness_level = self.b.read_literal(3); let lf_adjust_enable = self.b.read_flag(); if lf_adjust_enable { self.read_loop_filter_adjustments(); } self.num_partitions = (1usize << self.b.read_literal(2) as usize) as u8; let num_partitions = self.num_partitions as usize; self.init_partitions(num_partitions)?; self.read_quantization_indices(); if !self.frame.keyframe { // 9.7 refresh golden frame and altref frame // FIXME: support this? return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::WebP.into(), UnsupportedErrorKind::GenericFeature("Non-keyframe frames".to_owned()), ), )); } else { // Refresh entropy probs ????? let _ = self.b.read_literal(1); } self.update_token_probabilities(); let mb_no_skip_coeff = self.b.read_literal(1); self.prob_skip_false = if mb_no_skip_coeff == 1 { Some(self.b.read_literal(8)) } else { None }; if !self.frame.keyframe { // 9.10 remaining frame data self.prob_intra = 0; // FIXME: support this? return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::WebP.into(), UnsupportedErrorKind::GenericFeature("Non-keyframe frames".to_owned()), ), )); } else { // Reset motion vectors } Ok(()) } fn read_macroblock_header(&mut self, mbx: usize) -> ImageResult { let mut mb = MacroBlock::default(); if self.segments_enabled && self.segments_update_map { mb.segmentid = self .b .read_with_tree(&SEGMENT_ID_TREE, &self.segment_tree_probs, 0) as u8; }; mb.coeffs_skipped = if self.prob_skip_false.is_some() { self.b.read_bool(*self.prob_skip_false.as_ref().unwrap()) } else { false }; let inter_predicted = if !self.frame.keyframe { self.b.read_bool(self.prob_intra) } else { false }; if inter_predicted { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::WebP.into(), UnsupportedErrorKind::GenericFeature("VP8 inter-prediction".to_owned()), ), )); } if self.frame.keyframe { // intra prediction let luma = self .b .read_with_tree(&KEYFRAME_YMODE_TREE, &KEYFRAME_YMODE_PROBS, 0); mb.luma_mode = LumaMode::from_i8(luma).ok_or(DecoderError::LumaPredictionModeInvalid(luma))?; match mb.luma_mode.into_intra() { // `LumaMode::B` - This is predicted individually None => { for y in 0usize..4 { for x in 0usize..4 { let top = self.top[mbx].bpred[12 + x]; let left = self.left.bpred[y]; let intra = self.b.read_with_tree( &KEYFRAME_BPRED_MODE_TREE, &KEYFRAME_BPRED_MODE_PROBS[top as usize][left as usize], 0, ); let bmode = IntraMode::from_i8(intra) .ok_or(DecoderError::IntraPredictionModeInvalid(intra))?; mb.bpred[x + y * 4] = bmode; self.top[mbx].bpred[12 + x] = bmode; self.left.bpred[y] = bmode; } } } Some(mode) => { for i in 0usize..4 { mb.bpred[12 + i] = mode; self.left.bpred[i] = mode; } } } let chroma = self .b .read_with_tree(&KEYFRAME_UV_MODE_TREE, &KEYFRAME_UV_MODE_PROBS, 0); mb.chroma_mode = ChromaMode::from_i8(chroma) .ok_or(DecoderError::ChromaPredictionModeInvalid(chroma))?; } self.top[mbx].chroma_mode = mb.chroma_mode; self.top[mbx].luma_mode = mb.luma_mode; self.top[mbx].bpred = mb.bpred; Ok(mb) } fn intra_predict_luma(&mut self, mbx: usize, mby: usize, mb: &MacroBlock, resdata: &[i32]) { let stride = 1usize + 16 + 4; let w = self.frame.width as usize; let mw = self.mbwidth as usize; let mut ws = create_border_luma(mbx, mby, mw, &self.top_border, &self.left_border); match mb.luma_mode { LumaMode::V => predict_vpred(&mut ws, 16, 1, 1, stride), LumaMode::H => predict_hpred(&mut ws, 16, 1, 1, stride), LumaMode::TM => predict_tmpred(&mut ws, 16, 1, 1, stride), LumaMode::DC => predict_dcpred(&mut ws, 16, stride, mby != 0, mbx != 0), LumaMode::B => predict_4x4(&mut ws, stride, &mb.bpred, resdata), } if mb.luma_mode != LumaMode::B { for y in 0usize..4 { for x in 0usize..4 { let i = x + y * 4; // Create a reference to a [i32; 16] array for add_residue (slices of size 16 do not work). let rb: &[i32; 16] = resdata[i * 16..][..16].try_into().unwrap(); let y0 = 1 + y * 4; let x0 = 1 + x * 4; add_residue(&mut ws, rb, y0, x0, stride); } } } self.left_border[0] = ws[16]; for i in 0usize..16 { self.top_border[mbx * 16 + i] = ws[16 * stride + 1 + i]; self.left_border[i + 1] = ws[(i + 1) * stride + 16]; } // Length is the remainder to the border, but maximally the current chunk. let ylength = cmp::min(self.frame.height as usize - mby * 16, 16); let xlength = cmp::min(self.frame.width as usize - mbx * 16, 16); for y in 0usize..ylength { for x in 0usize..xlength { self.frame.ybuf[(mby * 16 + y) * w + mbx * 16 + x] = ws[(1 + y) * stride + 1 + x]; } } } fn intra_predict_chroma(&mut self, mbx: usize, mby: usize, mb: &MacroBlock, resdata: &[i32]) { let stride = 1usize + 8; let w = self.frame.chroma_width() as usize; //8x8 with left top border of 1 let mut uws = [0u8; (8 + 1) * (8 + 1)]; let mut vws = [0u8; (8 + 1) * (8 + 1)]; let ylength = cmp::min(self.frame.chroma_height() as usize - mby * 8, 8); let xlength = cmp::min(self.frame.chroma_width() as usize - mbx * 8, 8); //left border for y in 0usize..8 { let (uy, vy) = if mbx == 0 || y >= ylength { (129, 129) } else { let index = (mby * 8 + y) * w + ((mbx - 1) * 8 + 7); (self.frame.ubuf[index], self.frame.vbuf[index]) }; uws[(y + 1) * stride] = uy; vws[(y + 1) * stride] = vy; } //top border for x in 0usize..8 { let (ux, vx) = if mby == 0 || x >= xlength { (127, 127) } else { let index = ((mby - 1) * 8 + 7) * w + (mbx * 8 + x); (self.frame.ubuf[index], self.frame.vbuf[index]) }; uws[x + 1] = ux; vws[x + 1] = vx; } //top left point let (u1, v1) = if mby == 0 { (127, 127) } else if mbx == 0 { (129, 129) } else { let index = ((mby - 1) * 8 + 7) * w + (mbx - 1) * 8 + 7; if index >= self.frame.ubuf.len() { (127, 127) } else { (self.frame.ubuf[index], self.frame.vbuf[index]) } }; uws[0] = u1; vws[0] = v1; match mb.chroma_mode { ChromaMode::DC => { predict_dcpred(&mut uws, 8, stride, mby != 0, mbx != 0); predict_dcpred(&mut vws, 8, stride, mby != 0, mbx != 0); } ChromaMode::V => { predict_vpred(&mut uws, 8, 1, 1, stride); predict_vpred(&mut vws, 8, 1, 1, stride); } ChromaMode::H => { predict_hpred(&mut uws, 8, 1, 1, stride); predict_hpred(&mut vws, 8, 1, 1, stride); } ChromaMode::TM => { predict_tmpred(&mut uws, 8, 1, 1, stride); predict_tmpred(&mut vws, 8, 1, 1, stride); } } for y in 0usize..2 { for x in 0usize..2 { let i = x + y * 2; let urb: &[i32; 16] = resdata[16 * 16 + i * 16..][..16].try_into().unwrap(); let y0 = 1 + y * 4; let x0 = 1 + x * 4; add_residue(&mut uws, urb, y0, x0, stride); let vrb: &[i32; 16] = resdata[20 * 16 + i * 16..][..16].try_into().unwrap(); add_residue(&mut vws, vrb, y0, x0, stride); } } for y in 0usize..ylength { for x in 0usize..xlength { self.frame.ubuf[(mby * 8 + y) * w + mbx * 8 + x] = uws[(1 + y) * stride + 1 + x]; self.frame.vbuf[(mby * 8 + y) * w + mbx * 8 + x] = vws[(1 + y) * stride + 1 + x]; } } } fn read_coefficients( &mut self, block: &mut [i32], p: usize, plane: usize, complexity: usize, dcq: i16, acq: i16, ) -> bool { let first = if plane == 0 { 1usize } else { 0usize }; let probs = &self.token_probs[plane]; let tree = &DCT_TOKEN_TREE; let mut complexity = complexity; let mut has_coefficients = false; let mut skip = false; for i in first..16usize { let table = &probs[COEFF_BANDS[i] as usize][complexity]; let token = if !skip { self.partitions[p].read_with_tree(tree, table, 0) } else { self.partitions[p].read_with_tree(tree, table, 2) }; let mut abs_value = i32::from(match token { DCT_EOB => break, DCT_0 => { skip = true; has_coefficients = true; complexity = 0; continue; } literal @ DCT_1..=DCT_4 => i16::from(literal), category @ DCT_CAT1..=DCT_CAT6 => { let t = PROB_DCT_CAT[(category - DCT_CAT1) as usize]; let mut extra = 0i16; let mut j = 0; while t[j] > 0 { extra = extra + extra + self.partitions[p].read_bool(t[j]) as i16; j += 1; } i16::from(DCT_CAT_BASE[(category - DCT_CAT1) as usize]) + extra } c => panic!("unknown token: {}", c), }); skip = false; complexity = if abs_value == 0 { 0 } else if abs_value == 1 { 1 } else { 2 }; if self.partitions[p].read_bool(128) { abs_value = -abs_value; } block[ZIGZAG[i] as usize] = abs_value * i32::from(if ZIGZAG[i] > 0 { acq } else { dcq }); has_coefficients = true; } has_coefficients } fn read_residual_data(&mut self, mb: &MacroBlock, mbx: usize, p: usize) -> [i32; 384] { let sindex = mb.segmentid as usize; let mut blocks = [0i32; 384]; let mut plane = if mb.luma_mode == LumaMode::B { 3 } else { 1 }; if plane == 1 { let complexity = self.top[mbx].complexity[0] + self.left.complexity[0]; let mut block = [0i32; 16]; let dcq = self.segment[sindex].y2dc; let acq = self.segment[sindex].y2ac; let n = self.read_coefficients(&mut block, p, plane, complexity as usize, dcq, acq); self.left.complexity[0] = if n { 1 } else { 0 }; self.top[mbx].complexity[0] = if n { 1 } else { 0 }; transform::iwht4x4(&mut block); for k in 0usize..16 { blocks[16 * k] = block[k]; } plane = 0; } for y in 0usize..4 { let mut left = self.left.complexity[y + 1]; for x in 0usize..4 { let i = x + y * 4; let block = &mut blocks[i * 16..i * 16 + 16]; let complexity = self.top[mbx].complexity[x + 1] + left; let dcq = self.segment[sindex].ydc; let acq = self.segment[sindex].yac; let n = self.read_coefficients(block, p, plane, complexity as usize, dcq, acq); if block[0] != 0 || n { transform::idct4x4(block); } left = if n { 1 } else { 0 }; self.top[mbx].complexity[x + 1] = if n { 1 } else { 0 }; } self.left.complexity[y + 1] = left; } plane = 2; for &j in &[5usize, 7usize] { for y in 0usize..2 { let mut left = self.left.complexity[y + j]; for x in 0usize..2 { let i = x + y * 2 + if j == 5 { 16 } else { 20 }; let block = &mut blocks[i * 16..i * 16 + 16]; let complexity = self.top[mbx].complexity[x + j] + left; let dcq = self.segment[sindex].uvdc; let acq = self.segment[sindex].uvac; let n = self.read_coefficients(block, p, plane, complexity as usize, dcq, acq); if block[0] != 0 || n { transform::idct4x4(block); } left = if n { 1 } else { 0 }; self.top[mbx].complexity[x + j] = if n { 1 } else { 0 }; } self.left.complexity[y + j] = left; } } blocks } /// Does loop filtering on the macroblock fn loop_filter(&mut self, mbx: usize, mby: usize, mb: &MacroBlock) { let luma_w = self.frame.width as usize; let luma_h = self.frame.height as usize; let chroma_w = self.frame.chroma_width() as usize; let chroma_h = self.frame.chroma_height() as usize; let (filter_level, interior_limit, hev_threshold) = self.calculate_filter_parameters(mb); if filter_level > 0 { let mbedge_limit = (filter_level + 2) * 2 + interior_limit; let sub_bedge_limit = (filter_level * 2) + interior_limit; let luma_ylength = cmp::min(luma_h - 16 * mby, 16); let luma_xlength = cmp::min(luma_w - 16 * mbx, 16); let chroma_ylength = cmp::min(chroma_h - 8 * mby, 8); let chroma_xlength = cmp::min(chroma_w - 8 * mbx, 8); //filter across left of macroblock if mbx > 0 { //simple loop filtering if self.frame.filter_type { if luma_xlength >= 2 { for y in 0usize..luma_ylength { let y0 = mby * 16 + y; let x0 = mbx * 16; loop_filter::simple_segment( mbedge_limit, &mut self.frame.ybuf[..], y0 * luma_w + x0, 1, ); } } } else { if luma_xlength >= 4 { for y in 0usize..luma_ylength { let y0 = mby * 16 + y; let x0 = mbx * 16; loop_filter::macroblock_filter( hev_threshold, interior_limit, mbedge_limit, &mut self.frame.ybuf[..], y0 * luma_w + x0, 1, ); } } if chroma_xlength >= 4 { for y in 0usize..chroma_ylength { let y0 = mby * 8 + y; let x0 = mbx * 8; loop_filter::macroblock_filter( hev_threshold, interior_limit, mbedge_limit, &mut self.frame.ubuf[..], y0 * chroma_w + x0, 1, ); loop_filter::macroblock_filter( hev_threshold, interior_limit, mbedge_limit, &mut self.frame.vbuf[..], y0 * chroma_w + x0, 1, ); } } } } //filter across vertical subblocks in macroblock if mb.luma_mode == LumaMode::B || !mb.coeffs_skipped { if self.frame.filter_type { for x in (4usize..luma_xlength - 1).step_by(4) { for y in 0..luma_ylength { let y0 = mby * 16 + y; let x0 = mbx * 16 + x; loop_filter::simple_segment( sub_bedge_limit, &mut self.frame.ybuf[..], y0 * luma_w + x0, 1, ); } } } else { if luma_xlength > 3 { for x in (4usize..luma_xlength - 3).step_by(4) { for y in 0..luma_ylength { let y0 = mby * 16 + y; let x0 = mbx * 16 + x; loop_filter::subblock_filter( hev_threshold, interior_limit, sub_bedge_limit, &mut self.frame.ybuf[..], y0 * luma_w + x0, 1, ); } } } if chroma_xlength == 8 { for y in 0usize..chroma_ylength { let y0 = mby * 8 + y; let x0 = mbx * 8 + 4; loop_filter::subblock_filter( hev_threshold, interior_limit, sub_bedge_limit, &mut self.frame.ubuf[..], y0 * chroma_w + x0, 1, ); loop_filter::subblock_filter( hev_threshold, interior_limit, sub_bedge_limit, &mut self.frame.vbuf[..], y0 * chroma_w + x0, 1, ); } } } } //filter across top of macroblock if mby > 0 { if self.frame.filter_type { if luma_ylength >= 2 { for x in 0usize..luma_xlength { let y0 = mby * 16; let x0 = mbx * 16 + x; loop_filter::simple_segment( mbedge_limit, &mut self.frame.ybuf[..], y0 * luma_w + x0, luma_w, ); } } } else { //if bottom macroblock, can only filter if there is 3 pixels below if luma_ylength >= 4 { for x in 0usize..luma_xlength { let y0 = mby * 16; let x0 = mbx * 16 + x; loop_filter::macroblock_filter( hev_threshold, interior_limit, mbedge_limit, &mut self.frame.ybuf[..], y0 * luma_w + x0, luma_w, ); } } if chroma_ylength >= 4 { for x in 0usize..chroma_xlength { let y0 = mby * 8; let x0 = mbx * 8 + x; loop_filter::macroblock_filter( hev_threshold, interior_limit, mbedge_limit, &mut self.frame.ubuf[..], y0 * chroma_w + x0, chroma_w, ); loop_filter::macroblock_filter( hev_threshold, interior_limit, mbedge_limit, &mut self.frame.vbuf[..], y0 * chroma_w + x0, chroma_w, ); } } } } //filter across horizontal subblock edges within the macroblock if mb.luma_mode == LumaMode::B || !mb.coeffs_skipped { if self.frame.filter_type { for y in (4usize..luma_ylength - 1).step_by(4) { for x in 0..luma_xlength { let y0 = mby * 16 + y; let x0 = mbx * 16 + x; loop_filter::simple_segment( sub_bedge_limit, &mut self.frame.ybuf[..], y0 * luma_w + x0, luma_w, ); } } } else { if luma_ylength > 3 { for y in (4usize..luma_ylength - 3).step_by(4) { for x in 0..luma_xlength { let y0 = mby * 16 + y; let x0 = mbx * 16 + x; loop_filter::subblock_filter( hev_threshold, interior_limit, sub_bedge_limit, &mut self.frame.ybuf[..], y0 * luma_w + x0, luma_w, ); } } } if chroma_ylength == 8 { for x in 0..chroma_xlength { let y0 = mby * 8 + 4; let x0 = mbx * 8 + x; loop_filter::subblock_filter( hev_threshold, interior_limit, sub_bedge_limit, &mut self.frame.ubuf[..], y0 * chroma_w + x0, chroma_w, ); loop_filter::subblock_filter( hev_threshold, interior_limit, sub_bedge_limit, &mut self.frame.vbuf[..], y0 * chroma_w + x0, chroma_w, ); } } } } } } //return values are the filter level, interior limit and hev threshold fn calculate_filter_parameters(&self, macroblock: &MacroBlock) -> (u8, u8, u8) { let segment = self.segment[macroblock.segmentid as usize]; let mut filter_level = self.frame.filter_level as i32; if self.segments_enabled { if segment.delta_values { filter_level += i32::from(segment.loopfilter_level); } else { filter_level = i32::from(segment.loopfilter_level); } } filter_level = clamp(filter_level, 0, 63); if macroblock.luma_mode == LumaMode::B { filter_level += self.mode_delta[0]; } let filter_level = clamp(filter_level, 0, 63) as u8; //interior limit let mut interior_limit = filter_level; if self.frame.sharpness_level > 0 { interior_limit >>= if self.frame.sharpness_level > 4 { 2 } else { 1 }; if interior_limit > 9 - self.frame.sharpness_level { interior_limit = 9 - self.frame.sharpness_level; } } if interior_limit == 0 { interior_limit = 1; } //high edge variance threshold let mut hev_threshold = 0; #[allow(clippy::collapsible_else_if)] if self.frame.keyframe { if filter_level >= 40 { hev_threshold = 2; } else { hev_threshold = 1; } } else { if filter_level >= 40 { hev_threshold = 3; } else if filter_level >= 20 { hev_threshold = 2; } else if filter_level >= 15 { hev_threshold = 1; } } (filter_level, interior_limit, hev_threshold) } /// Decodes the current frame pub fn decode_frame(&mut self) -> ImageResult<&Frame> { self.read_frame_header()?; for mby in 0..self.mbheight as usize { let p = mby % self.num_partitions as usize; self.left = MacroBlock::default(); for mbx in 0..self.mbwidth as usize { let mb = self.read_macroblock_header(mbx)?; let blocks = if !mb.coeffs_skipped { self.read_residual_data(&mb, mbx, p) } else { if mb.luma_mode != LumaMode::B { self.left.complexity[0] = 0; self.top[mbx].complexity[0] = 0; } for i in 1usize..9 { self.left.complexity[i] = 0; self.top[mbx].complexity[i] = 0; } [0i32; 384] }; self.intra_predict_luma(mbx, mby, &mb, &blocks); self.intra_predict_chroma(mbx, mby, &mb, &blocks); self.macroblocks.push(mb); } self.left_border = vec![129u8; 1 + 16]; } //do loop filtering for mby in 0..self.mbheight as usize { for mbx in 0..self.mbwidth as usize { let mb = self.macroblocks[mby * self.mbwidth as usize + mbx]; self.loop_filter(mbx, mby, &mb); } } Ok(&self.frame) } } impl LumaMode { fn from_i8(val: i8) -> Option { Some(match val { DC_PRED => LumaMode::DC, V_PRED => LumaMode::V, H_PRED => LumaMode::H, TM_PRED => LumaMode::TM, B_PRED => LumaMode::B, _ => return None, }) } fn into_intra(self) -> Option { Some(match self { LumaMode::DC => IntraMode::DC, LumaMode::V => IntraMode::VE, LumaMode::H => IntraMode::HE, LumaMode::TM => IntraMode::TM, LumaMode::B => return None, }) } } impl Default for LumaMode { fn default() -> Self { LumaMode::DC } } impl ChromaMode { fn from_i8(val: i8) -> Option { Some(match val { DC_PRED => ChromaMode::DC, V_PRED => ChromaMode::V, H_PRED => ChromaMode::H, TM_PRED => ChromaMode::TM, _ => return None, }) } } impl Default for ChromaMode { fn default() -> Self { ChromaMode::DC } } impl IntraMode { fn from_i8(val: i8) -> Option { Some(match val { B_DC_PRED => IntraMode::DC, B_TM_PRED => IntraMode::TM, B_VE_PRED => IntraMode::VE, B_HE_PRED => IntraMode::HE, B_LD_PRED => IntraMode::LD, B_RD_PRED => IntraMode::RD, B_VR_PRED => IntraMode::VR, B_VL_PRED => IntraMode::VL, B_HD_PRED => IntraMode::HD, B_HU_PRED => IntraMode::HU, _ => return None, }) } } impl Default for IntraMode { fn default() -> Self { IntraMode::DC } } fn init_top_macroblocks(width: usize) -> Vec { let mb_width = (width + 15) / 16; let mb = MacroBlock { // Section 11.3 #3 bpred: [IntraMode::DC; 16], luma_mode: LumaMode::DC, ..MacroBlock::default() }; vec![mb; mb_width] } fn create_border_luma(mbx: usize, mby: usize, mbw: usize, top: &[u8], left: &[u8]) -> [u8; 357] { let stride = 1usize + 16 + 4; let mut ws = [0u8; (1 + 16) * (1 + 16 + 4)]; // A { let above = &mut ws[1..stride]; if mby == 0 { for above in above.iter_mut() { *above = 127; } } else { for i in 0usize..16 { above[i] = top[mbx * 16 + i]; } if mbx == mbw - 1 { for above in above.iter_mut().skip(16) { *above = top[mbx * 16 + 15]; } } else { for i in 16usize..above.len() { above[i] = top[mbx * 16 + i]; } } } } for i in 17usize..stride { ws[4 * stride + i] = ws[i]; ws[8 * stride + i] = ws[i]; ws[12 * stride + i] = ws[i]; } // L if mbx == 0 { for i in 0usize..16 { ws[(i + 1) * stride] = 129; } } else { for i in 0usize..16 { ws[(i + 1) * stride] = left[i + 1]; } } // P ws[0] = if mby == 0 { 127 } else if mbx == 0 { 129 } else { left[0] }; ws } fn avg3(left: u8, this: u8, right: u8) -> u8 { let avg = (u16::from(left) + 2 * u16::from(this) + u16::from(right) + 2) >> 2; avg as u8 } fn avg2(this: u8, right: u8) -> u8 { let avg = (u16::from(this) + u16::from(right) + 1) >> 1; avg as u8 } // Only 16 elements from rblock are used to add residue, so it is restricted to 16 elements // to enable SIMD and other optimizations. fn add_residue(pblock: &mut [u8], rblock: &[i32; 16], y0: usize, x0: usize, stride: usize) { let mut pos = y0 * stride + x0; for row in rblock.chunks(4) { for (p, &a) in pblock[pos..pos + 4].iter_mut().zip(row.iter()) { *p = clamp(a + i32::from(*p), 0, 255) as u8; } pos += stride; } } fn predict_4x4(ws: &mut [u8], stride: usize, modes: &[IntraMode], resdata: &[i32]) { for sby in 0usize..4 { for sbx in 0usize..4 { let i = sbx + sby * 4; let y0 = sby * 4 + 1; let x0 = sbx * 4 + 1; match modes[i] { IntraMode::TM => predict_tmpred(ws, 4, x0, y0, stride), IntraMode::VE => predict_bvepred(ws, x0, y0, stride), IntraMode::HE => predict_bhepred(ws, x0, y0, stride), IntraMode::DC => predict_bdcpred(ws, x0, y0, stride), IntraMode::LD => predict_bldpred(ws, x0, y0, stride), IntraMode::RD => predict_brdpred(ws, x0, y0, stride), IntraMode::VR => predict_bvrpred(ws, x0, y0, stride), IntraMode::VL => predict_bvlpred(ws, x0, y0, stride), IntraMode::HD => predict_bhdpred(ws, x0, y0, stride), IntraMode::HU => predict_bhupred(ws, x0, y0, stride), } let rb: &[i32; 16] = resdata[i * 16..][..16].try_into().unwrap(); add_residue(ws, rb, y0, x0, stride); } } } fn predict_vpred(a: &mut [u8], size: usize, x0: usize, y0: usize, stride: usize) { for y in 0usize..size { for x in 0usize..size { a[(x + x0) + stride * (y + y0)] = a[(x + x0) + stride * (y0 + y - 1)]; } } } fn predict_hpred(a: &mut [u8], size: usize, x0: usize, y0: usize, stride: usize) { for y in 0usize..size { for x in 0usize..size { a[(x + x0) + stride * (y + y0)] = a[(x + x0 - 1) + stride * (y0 + y)]; } } } fn predict_dcpred(a: &mut [u8], size: usize, stride: usize, above: bool, left: bool) { let mut sum = 0; let mut shf = if size == 8 { 2 } else { 3 }; if left { for y in 0usize..size { sum += u32::from(a[(y + 1) * stride]); } shf += 1; } if above { for x in 0usize..size { sum += u32::from(a[x + 1]); } shf += 1; } let dcval = if !left && !above { 128 } else { (sum + (1 << (shf - 1))) >> shf }; for y in 0usize..size { for x in 0usize..size { a[(x + 1) + stride * (y + 1)] = dcval as u8; } } } fn predict_tmpred(a: &mut [u8], size: usize, x0: usize, y0: usize, stride: usize) { for y in 0usize..size { for x in 0usize..size { let pred = i32::from(a[(y0 + y) * stride + x0 - 1]) + i32::from(a[(y0 - 1) * stride + x0 + x]) - i32::from(a[(y0 - 1) * stride + x0 - 1]); a[(x + x0) + stride * (y + y0)] = clamp(pred, 0, 255) as u8; } } } fn predict_bdcpred(a: &mut [u8], x0: usize, y0: usize, stride: usize) { let mut v = 4; for i in 0usize..4 { v += u32::from(a[(y0 + i) * stride + x0 - 1]) + u32::from(a[(y0 - 1) * stride + x0 + i]); } v >>= 3; for y in 0usize..4 { for x in 0usize..4 { a[x + x0 + stride * (y + y0)] = v as u8; } } } fn topleft_pixel(a: &[u8], x0: usize, y0: usize, stride: usize) -> u8 { a[(y0 - 1) * stride + x0 - 1] } fn top_pixels(a: &[u8], x0: usize, y0: usize, stride: usize) -> (u8, u8, u8, u8, u8, u8, u8, u8) { let pos = (y0 - 1) * stride + x0; let a_slice = &a[pos..pos + 8]; let a0 = a_slice[0]; let a1 = a_slice[1]; let a2 = a_slice[2]; let a3 = a_slice[3]; let a4 = a_slice[4]; let a5 = a_slice[5]; let a6 = a_slice[6]; let a7 = a_slice[7]; (a0, a1, a2, a3, a4, a5, a6, a7) } fn left_pixels(a: &[u8], x0: usize, y0: usize, stride: usize) -> (u8, u8, u8, u8) { let l0 = a[y0 * stride + x0 - 1]; let l1 = a[(y0 + 1) * stride + x0 - 1]; let l2 = a[(y0 + 2) * stride + x0 - 1]; let l3 = a[(y0 + 3) * stride + x0 - 1]; (l0, l1, l2, l3) } fn edge_pixels( a: &[u8], x0: usize, y0: usize, stride: usize, ) -> (u8, u8, u8, u8, u8, u8, u8, u8, u8) { let pos = (y0 - 1) * stride + x0 - 1; let a_slice = &a[pos..=pos + 4]; let e0 = a[pos + 4 * stride]; let e1 = a[pos + 3 * stride]; let e2 = a[pos + 2 * stride]; let e3 = a[pos + stride]; let e4 = a_slice[0]; let e5 = a_slice[1]; let e6 = a_slice[2]; let e7 = a_slice[3]; let e8 = a_slice[4]; (e0, e1, e2, e3, e4, e5, e6, e7, e8) } fn predict_bvepred(a: &mut [u8], x0: usize, y0: usize, stride: usize) { let p = topleft_pixel(a, x0, y0, stride); let (a0, a1, a2, a3, a4, _, _, _) = top_pixels(a, x0, y0, stride); let avg_1 = avg3(p, a0, a1); let avg_2 = avg3(a0, a1, a2); let avg_3 = avg3(a1, a2, a3); let avg_4 = avg3(a2, a3, a4); let avg = [avg_1, avg_2, avg_3, avg_4]; let mut pos = y0 * stride + x0; for _ in 0..4 { a[pos..=pos + 3].copy_from_slice(&avg); pos += stride; } } fn predict_bhepred(a: &mut [u8], x0: usize, y0: usize, stride: usize) { let p = topleft_pixel(a, x0, y0, stride); let (l0, l1, l2, l3) = left_pixels(a, x0, y0, stride); let avgs = [ avg3(p, l0, l1), avg3(l0, l1, l2), avg3(l1, l2, l3), avg3(l2, l3, l3), ]; let mut pos = y0 * stride + x0; for &avg in avgs.iter() { for a_p in a[pos..=pos + 3].iter_mut() { *a_p = avg; } pos += stride; } } fn predict_bldpred(a: &mut [u8], x0: usize, y0: usize, stride: usize) { let (a0, a1, a2, a3, a4, a5, a6, a7) = top_pixels(a, x0, y0, stride); let avgs = [ avg3(a0, a1, a2), avg3(a1, a2, a3), avg3(a2, a3, a4), avg3(a3, a4, a5), avg3(a4, a5, a6), avg3(a5, a6, a7), avg3(a6, a7, a7), ]; let mut pos = y0 * stride + x0; for i in 0..4 { a[pos..=pos + 3].copy_from_slice(&avgs[i..=i + 3]); pos += stride; } } fn predict_brdpred(a: &mut [u8], x0: usize, y0: usize, stride: usize) { let (e0, e1, e2, e3, e4, e5, e6, e7, e8) = edge_pixels(a, x0, y0, stride); let avgs = [ avg3(e0, e1, e2), avg3(e1, e2, e3), avg3(e2, e3, e4), avg3(e3, e4, e5), avg3(e4, e5, e6), avg3(e5, e6, e7), avg3(e6, e7, e8), ]; let mut pos = y0 * stride + x0; for i in 0..4 { a[pos..=pos + 3].copy_from_slice(&avgs[3 - i..7 - i]); pos += stride; } } fn predict_bvrpred(a: &mut [u8], x0: usize, y0: usize, stride: usize) { let (_, e1, e2, e3, e4, e5, e6, e7, e8) = edge_pixels(a, x0, y0, stride); a[(y0 + 3) * stride + x0] = avg3(e1, e2, e3); a[(y0 + 2) * stride + x0] = avg3(e2, e3, e4); a[(y0 + 3) * stride + x0 + 1] = avg3(e3, e4, e5); a[(y0 + 1) * stride + x0] = avg3(e3, e4, e5); a[(y0 + 2) * stride + x0 + 1] = avg2(e4, e5); a[y0 * stride + x0] = avg2(e4, e5); a[(y0 + 3) * stride + x0 + 2] = avg3(e4, e5, e6); a[(y0 + 1) * stride + x0 + 1] = avg3(e4, e5, e6); a[(y0 + 2) * stride + x0 + 2] = avg2(e5, e6); a[y0 * stride + x0 + 1] = avg2(e5, e6); a[(y0 + 3) * stride + x0 + 3] = avg3(e5, e6, e7); a[(y0 + 1) * stride + x0 + 2] = avg3(e5, e6, e7); a[(y0 + 2) * stride + x0 + 3] = avg2(e6, e7); a[y0 * stride + x0 + 2] = avg2(e6, e7); a[(y0 + 1) * stride + x0 + 3] = avg3(e6, e7, e8); a[y0 * stride + x0 + 3] = avg2(e7, e8); } fn predict_bvlpred(a: &mut [u8], x0: usize, y0: usize, stride: usize) { let (a0, a1, a2, a3, a4, a5, a6, a7) = top_pixels(a, x0, y0, stride); a[y0 * stride + x0] = avg2(a0, a1); a[(y0 + 1) * stride + x0] = avg3(a0, a1, a2); a[(y0 + 2) * stride + x0] = avg2(a1, a2); a[y0 * stride + x0 + 1] = avg2(a1, a2); a[(y0 + 1) * stride + x0 + 1] = avg3(a1, a2, a3); a[(y0 + 3) * stride + x0] = avg3(a1, a2, a3); a[(y0 + 2) * stride + x0 + 1] = avg2(a2, a3); a[y0 * stride + x0 + 2] = avg2(a2, a3); a[(y0 + 3) * stride + x0 + 1] = avg3(a2, a3, a4); a[(y0 + 1) * stride + x0 + 2] = avg3(a2, a3, a4); a[(y0 + 2) * stride + x0 + 2] = avg2(a3, a4); a[y0 * stride + x0 + 3] = avg2(a3, a4); a[(y0 + 3) * stride + x0 + 2] = avg3(a3, a4, a5); a[(y0 + 1) * stride + x0 + 3] = avg3(a3, a4, a5); a[(y0 + 2) * stride + x0 + 3] = avg3(a4, a5, a6); a[(y0 + 3) * stride + x0 + 3] = avg3(a5, a6, a7); } fn predict_bhdpred(a: &mut [u8], x0: usize, y0: usize, stride: usize) { let (e0, e1, e2, e3, e4, e5, e6, e7, _) = edge_pixels(a, x0, y0, stride); a[(y0 + 3) * stride + x0] = avg2(e0, e1); a[(y0 + 3) * stride + x0 + 1] = avg3(e0, e1, e2); a[(y0 + 2) * stride + x0] = avg2(e1, e2); a[(y0 + 3) * stride + x0 + 2] = avg2(e1, e2); a[(y0 + 2) * stride + x0 + 1] = avg3(e1, e2, e3); a[(y0 + 3) * stride + x0 + 3] = avg3(e1, e2, e3); a[(y0 + 2) * stride + x0 + 2] = avg2(e2, e3); a[(y0 + 1) * stride + x0] = avg2(e2, e3); a[(y0 + 2) * stride + x0 + 3] = avg3(e2, e3, e4); a[(y0 + 1) * stride + x0 + 1] = avg3(e2, e3, e4); a[(y0 + 1) * stride + x0 + 2] = avg2(e3, e4); a[y0 * stride + x0] = avg2(e3, e4); a[(y0 + 1) * stride + x0 + 3] = avg3(e3, e4, e5); a[y0 * stride + x0 + 1] = avg3(e3, e4, e5); a[y0 * stride + x0 + 2] = avg3(e4, e5, e6); a[y0 * stride + x0 + 3] = avg3(e5, e6, e7); } fn predict_bhupred(a: &mut [u8], x0: usize, y0: usize, stride: usize) { let (l0, l1, l2, l3) = left_pixels(a, x0, y0, stride); a[y0 * stride + x0] = avg2(l0, l1); a[y0 * stride + x0 + 1] = avg3(l0, l1, l2); a[y0 * stride + x0 + 2] = avg2(l1, l2); a[(y0 + 1) * stride + x0] = avg2(l1, l2); a[y0 * stride + x0 + 3] = avg3(l1, l2, l3); a[(y0 + 1) * stride + x0 + 1] = avg3(l1, l2, l3); a[(y0 + 1) * stride + x0 + 2] = avg2(l2, l3); a[(y0 + 2) * stride + x0] = avg2(l2, l3); a[(y0 + 1) * stride + x0 + 3] = avg3(l2, l3, l3); a[(y0 + 2) * stride + x0 + 1] = avg3(l2, l3, l3); a[(y0 + 2) * stride + x0 + 2] = l3; a[(y0 + 2) * stride + x0 + 3] = l3; a[(y0 + 3) * stride + x0] = l3; a[(y0 + 3) * stride + x0 + 1] = l3; a[(y0 + 3) * stride + x0 + 2] = l3; a[(y0 + 3) * stride + x0 + 3] = l3; } #[cfg(test)] mod test { #[cfg(feature = "benchmarks")] extern crate test; use super::{ add_residue, avg2, avg3, edge_pixels, predict_bhepred, predict_bldpred, predict_brdpred, predict_bvepred, top_pixels, }; #[cfg(feature = "benchmarks")] use super::{predict_4x4, IntraMode}; #[cfg(feature = "benchmarks")] use test::{black_box, Bencher}; #[cfg(feature = "benchmarks")] const W: usize = 256; #[cfg(feature = "benchmarks")] const H: usize = 256; #[cfg(feature = "benchmarks")] fn make_sample_image() -> Vec { let mut v = Vec::with_capacity((W * H * 4) as usize); for c in 0u8..=255 { for k in 0u8..=255 { v.push(c); v.push(0); v.push(0); v.push(k); } } v } #[cfg(feature = "benchmarks")] #[bench] fn bench_predict_4x4(b: &mut Bencher) { let mut v = black_box(make_sample_image()); let res_data = vec![1i32; W * H * 4]; let modes = [ IntraMode::TM, IntraMode::VE, IntraMode::HE, IntraMode::DC, IntraMode::LD, IntraMode::RD, IntraMode::VR, IntraMode::VL, IntraMode::HD, IntraMode::HU, IntraMode::TM, IntraMode::VE, IntraMode::HE, IntraMode::DC, IntraMode::LD, IntraMode::RD, ]; b.iter(|| { black_box(predict_4x4(&mut v, W * 2, &modes, &res_data)); }); } #[cfg(feature = "benchmarks")] #[bench] fn bench_predict_bvepred(b: &mut Bencher) { let mut v = make_sample_image(); b.iter(|| { predict_bvepred(black_box(&mut v), 5, 5, W * 2); }); } #[cfg(feature = "benchmarks")] #[bench] fn bench_predict_bldpred(b: &mut Bencher) { let mut v = black_box(make_sample_image()); b.iter(|| { black_box(predict_bldpred(black_box(&mut v), 5, 5, W * 2)); }); } #[cfg(feature = "benchmarks")] #[bench] fn bench_predict_brdpred(b: &mut Bencher) { let mut v = black_box(make_sample_image()); b.iter(|| { black_box(predict_brdpred(black_box(&mut v), 5, 5, W * 2)); }); } #[cfg(feature = "benchmarks")] #[bench] fn bench_predict_bhepred(b: &mut Bencher) { let mut v = black_box(make_sample_image()); b.iter(|| { black_box(predict_bhepred(black_box(&mut v), 5, 5, W * 2)); }); } #[cfg(feature = "benchmarks")] #[bench] fn bench_top_pixels(b: &mut Bencher) { let v = black_box(make_sample_image()); b.iter(|| { black_box(top_pixels(black_box(&v), 5, 5, W * 2)); }); } #[cfg(feature = "benchmarks")] #[bench] fn bench_edge_pixels(b: &mut Bencher) { let v = black_box(make_sample_image()); b.iter(|| { black_box(edge_pixels(black_box(&v), 5, 5, W * 2)); }); } #[test] fn test_avg2() { for i in 0u8..=255 { for j in 0u8..=255 { let ceil_avg = ((i as f32) + (j as f32)) / 2.0; let ceil_avg = ceil_avg.ceil() as u8; assert_eq!( ceil_avg, avg2(i, j), "avg2({}, {}), expected {}, got {}.", i, j, ceil_avg, avg2(i, j) ); } } } #[test] fn test_avg2_specific() { assert_eq!( 255, avg2(255, 255), "avg2(255, 255), expected 255, got {}.", avg2(255, 255) ); assert_eq!(1, avg2(1, 1), "avg2(1, 1), expected 1, got {}.", avg2(1, 1)); assert_eq!(2, avg2(2, 1), "avg2(2, 1), expected 2, got {}.", avg2(2, 1)); } #[test] fn test_avg3() { for i in 0u8..=255 { for j in 0u8..=255 { for k in 0u8..=255 { let floor_avg = ((i as f32) + 2.0 * (j as f32) + { k as f32 } + 2.0) / 4.0; let floor_avg = floor_avg.floor() as u8; assert_eq!( floor_avg, avg3(i, j, k), "avg3({}, {}, {}), expected {}, got {}.", i, j, k, floor_avg, avg3(i, j, k) ); } } } } #[test] fn test_edge_pixels() { #[rustfmt::skip] let im = vec![5, 6, 7, 8, 9, 4, 0, 0, 0, 0, 3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0]; let (e0, e1, e2, e3, e4, e5, e6, e7, e8) = edge_pixels(&im, 1, 1, 5); assert_eq!(e0, 1); assert_eq!(e1, 2); assert_eq!(e2, 3); assert_eq!(e3, 4); assert_eq!(e4, 5); assert_eq!(e5, 6); assert_eq!(e6, 7); assert_eq!(e7, 8); assert_eq!(e8, 9); } #[test] fn test_top_pixels() { #[rustfmt::skip] let im = vec![1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; let (e0, e1, e2, e3, e4, e5, e6, e7) = top_pixels(&im, 0, 1, 8); assert_eq!(e0, 1); assert_eq!(e1, 2); assert_eq!(e2, 3); assert_eq!(e3, 4); assert_eq!(e4, 5); assert_eq!(e5, 6); assert_eq!(e6, 7); assert_eq!(e7, 8); } #[test] fn test_add_residue() { let mut pblock = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; let rblock = [ -1, -2, -3, -4, 250, 249, 248, 250, -10, -18, -192, -17, -3, 15, 18, 9, ]; let expected: [u8; 16] = [0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 10, 29, 33, 25]; add_residue(&mut pblock, &rblock, 0, 0, 4); for (&e, &i) in expected.iter().zip(&pblock) { assert_eq!(e, i); } } #[test] fn test_predict_bhepred() { #[rustfmt::skip] let expected: Vec = vec![5, 0, 0, 0, 0, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1]; #[rustfmt::skip] let mut im = vec![5, 0, 0, 0, 0, 4, 0, 0, 0, 0, 3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0]; predict_bhepred(&mut im, 1, 1, 5); for (&e, i) in expected.iter().zip(im) { assert_eq!(e, i); } } #[test] fn test_predict_brdpred() { #[rustfmt::skip] let expected: Vec = vec![5, 6, 7, 8, 9, 4, 5, 6, 7, 8, 3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]; #[rustfmt::skip] let mut im = vec![5, 6, 7, 8, 9, 4, 0, 0, 0, 0, 3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0]; predict_brdpred(&mut im, 1, 1, 5); for (&e, i) in expected.iter().zip(im) { assert_eq!(e, i); } } #[test] fn test_predict_bldpred() { #[rustfmt::skip] let mut im: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; let avg_1 = 2u8; let avg_2 = 3u8; let avg_3 = 4u8; let avg_4 = 5u8; let avg_5 = 6u8; let avg_6 = 7u8; let avg_7 = 8u8; predict_bldpred(&mut im, 0, 1, 8); assert_eq!(im[8], avg_1); assert_eq!(im[9], avg_2); assert_eq!(im[10], avg_3); assert_eq!(im[11], avg_4); assert_eq!(im[16], avg_2); assert_eq!(im[17], avg_3); assert_eq!(im[18], avg_4); assert_eq!(im[19], avg_5); assert_eq!(im[24], avg_3); assert_eq!(im[25], avg_4); assert_eq!(im[26], avg_5); assert_eq!(im[27], avg_6); assert_eq!(im[32], avg_4); assert_eq!(im[33], avg_5); assert_eq!(im[34], avg_6); assert_eq!(im[35], avg_7); } #[test] fn test_predict_bvepred() { #[rustfmt::skip] let mut im: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; let avg_1 = 2u8; let avg_2 = 3u8; let avg_3 = 4u8; let avg_4 = 5u8; predict_bvepred(&mut im, 1, 1, 9); assert_eq!(im[10], avg_1); assert_eq!(im[11], avg_2); assert_eq!(im[12], avg_3); assert_eq!(im[13], avg_4); assert_eq!(im[19], avg_1); assert_eq!(im[20], avg_2); assert_eq!(im[21], avg_3); assert_eq!(im[22], avg_4); assert_eq!(im[28], avg_1); assert_eq!(im[29], avg_2); assert_eq!(im[30], avg_3); assert_eq!(im[31], avg_4); assert_eq!(im[37], avg_1); assert_eq!(im[38], avg_2); assert_eq!(im[39], avg_3); assert_eq!(im[40], avg_4); } } image-0.24.7/src/color.rs000064400000000000000000000701301046102023000132540ustar 00000000000000use std::ops::{Index, IndexMut}; use num_traits::{NumCast, ToPrimitive, Zero}; use crate::traits::{Enlargeable, Pixel, Primitive}; /// An enumeration over supported color types and bit depths #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] #[non_exhaustive] pub enum ColorType { /// Pixel is 8-bit luminance L8, /// Pixel is 8-bit luminance with an alpha channel La8, /// Pixel contains 8-bit R, G and B channels Rgb8, /// Pixel is 8-bit RGB with an alpha channel Rgba8, /// Pixel is 16-bit luminance L16, /// Pixel is 16-bit luminance with an alpha channel La16, /// Pixel is 16-bit RGB Rgb16, /// Pixel is 16-bit RGBA Rgba16, /// Pixel is 32-bit float RGB Rgb32F, /// Pixel is 32-bit float RGBA Rgba32F, } impl ColorType { /// Returns the number of bytes contained in a pixel of `ColorType` ```c``` pub fn bytes_per_pixel(self) -> u8 { match self { ColorType::L8 => 1, ColorType::L16 | ColorType::La8 => 2, ColorType::Rgb8 => 3, ColorType::Rgba8 | ColorType::La16 => 4, ColorType::Rgb16 => 6, ColorType::Rgba16 => 8, ColorType::Rgb32F => 3 * 4, ColorType::Rgba32F => 4 * 4, } } /// Returns if there is an alpha channel. pub fn has_alpha(self) -> bool { use ColorType::*; match self { L8 | L16 | Rgb8 | Rgb16 | Rgb32F => false, La8 | Rgba8 | La16 | Rgba16 | Rgba32F => true, } } /// Returns false if the color scheme is grayscale, true otherwise. pub fn has_color(self) -> bool { use ColorType::*; match self { L8 | L16 | La8 | La16 => false, Rgb8 | Rgb16 | Rgba8 | Rgba16 | Rgb32F | Rgba32F => true, } } /// Returns the number of bits contained in a pixel of `ColorType` ```c``` (which will always be /// a multiple of 8). pub fn bits_per_pixel(self) -> u16 { >::from(self.bytes_per_pixel()) * 8 } /// Returns the number of color channels that make up this pixel pub fn channel_count(self) -> u8 { let e: ExtendedColorType = self.into(); e.channel_count() } } /// An enumeration of color types encountered in image formats. /// /// This is not exhaustive over all existing image formats but should be granular enough to allow /// round tripping of decoding and encoding as much as possible. The variants will be extended as /// necessary to enable this. /// /// Another purpose is to advise users of a rough estimate of the accuracy and effort of the /// decoding from and encoding to such an image format. #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] #[non_exhaustive] pub enum ExtendedColorType { /// Pixel is 8-bit alpha A8, /// Pixel is 1-bit luminance L1, /// Pixel is 1-bit luminance with an alpha channel La1, /// Pixel contains 1-bit R, G and B channels Rgb1, /// Pixel is 1-bit RGB with an alpha channel Rgba1, /// Pixel is 2-bit luminance L2, /// Pixel is 2-bit luminance with an alpha channel La2, /// Pixel contains 2-bit R, G and B channels Rgb2, /// Pixel is 2-bit RGB with an alpha channel Rgba2, /// Pixel is 4-bit luminance L4, /// Pixel is 4-bit luminance with an alpha channel La4, /// Pixel contains 4-bit R, G and B channels Rgb4, /// Pixel is 4-bit RGB with an alpha channel Rgba4, /// Pixel is 8-bit luminance L8, /// Pixel is 8-bit luminance with an alpha channel La8, /// Pixel contains 8-bit R, G and B channels Rgb8, /// Pixel is 8-bit RGB with an alpha channel Rgba8, /// Pixel is 16-bit luminance L16, /// Pixel is 16-bit luminance with an alpha channel La16, /// Pixel contains 16-bit R, G and B channels Rgb16, /// Pixel is 16-bit RGB with an alpha channel Rgba16, /// Pixel contains 8-bit B, G and R channels Bgr8, /// Pixel is 8-bit BGR with an alpha channel Bgra8, // TODO f16 types? /// Pixel is 32-bit float RGB Rgb32F, /// Pixel is 32-bit float RGBA Rgba32F, /// Pixel is of unknown color type with the specified bits per pixel. This can apply to pixels /// which are associated with an external palette. In that case, the pixel value is an index /// into the palette. Unknown(u8), } impl ExtendedColorType { /// Get the number of channels for colors of this type. /// /// Note that the `Unknown` variant returns a value of `1` since pixels can only be treated as /// an opaque datum by the library. pub fn channel_count(self) -> u8 { match self { ExtendedColorType::A8 | ExtendedColorType::L1 | ExtendedColorType::L2 | ExtendedColorType::L4 | ExtendedColorType::L8 | ExtendedColorType::L16 | ExtendedColorType::Unknown(_) => 1, ExtendedColorType::La1 | ExtendedColorType::La2 | ExtendedColorType::La4 | ExtendedColorType::La8 | ExtendedColorType::La16 => 2, ExtendedColorType::Rgb1 | ExtendedColorType::Rgb2 | ExtendedColorType::Rgb4 | ExtendedColorType::Rgb8 | ExtendedColorType::Rgb16 | ExtendedColorType::Rgb32F | ExtendedColorType::Bgr8 => 3, ExtendedColorType::Rgba1 | ExtendedColorType::Rgba2 | ExtendedColorType::Rgba4 | ExtendedColorType::Rgba8 | ExtendedColorType::Rgba16 | ExtendedColorType::Rgba32F | ExtendedColorType::Bgra8 => 4, } } } impl From for ExtendedColorType { fn from(c: ColorType) -> Self { match c { ColorType::L8 => ExtendedColorType::L8, ColorType::La8 => ExtendedColorType::La8, ColorType::Rgb8 => ExtendedColorType::Rgb8, ColorType::Rgba8 => ExtendedColorType::Rgba8, ColorType::L16 => ExtendedColorType::L16, ColorType::La16 => ExtendedColorType::La16, ColorType::Rgb16 => ExtendedColorType::Rgb16, ColorType::Rgba16 => ExtendedColorType::Rgba16, ColorType::Rgb32F => ExtendedColorType::Rgb32F, ColorType::Rgba32F => ExtendedColorType::Rgba32F, } } } macro_rules! define_colors { {$( $(#[$doc:meta])* pub struct $ident:ident([T; $channels:expr, $alphas:expr]) = $interpretation:literal; )*} => { $( // START Structure definitions $(#[$doc])* #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)] #[repr(C)] #[allow(missing_docs)] pub struct $ident (pub [T; $channels]); impl Pixel for $ident { type Subpixel = T; const CHANNEL_COUNT: u8 = $channels; #[inline(always)] fn channels(&self) -> &[T] { &self.0 } #[inline(always)] fn channels_mut(&mut self) -> &mut [T] { &mut self.0 } const COLOR_MODEL: &'static str = $interpretation; fn channels4(&self) -> (T, T, T, T) { const CHANNELS: usize = $channels; let mut channels = [T::DEFAULT_MAX_VALUE; 4]; channels[0..CHANNELS].copy_from_slice(&self.0); (channels[0], channels[1], channels[2], channels[3]) } fn from_channels(a: T, b: T, c: T, d: T,) -> $ident { const CHANNELS: usize = $channels; *<$ident as Pixel>::from_slice(&[a, b, c, d][..CHANNELS]) } fn from_slice(slice: &[T]) -> &$ident { assert_eq!(slice.len(), $channels); unsafe { &*(slice.as_ptr() as *const $ident) } } fn from_slice_mut(slice: &mut [T]) -> &mut $ident { assert_eq!(slice.len(), $channels); unsafe { &mut *(slice.as_mut_ptr() as *mut $ident) } } fn to_rgb(&self) -> Rgb { let mut pix = Rgb([Zero::zero(), Zero::zero(), Zero::zero()]); pix.from_color(self); pix } fn to_rgba(&self) -> Rgba { let mut pix = Rgba([Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero()]); pix.from_color(self); pix } fn to_luma(&self) -> Luma { let mut pix = Luma([Zero::zero()]); pix.from_color(self); pix } fn to_luma_alpha(&self) -> LumaA { let mut pix = LumaA([Zero::zero(), Zero::zero()]); pix.from_color(self); pix } fn map(& self, f: F) -> $ident where F: FnMut(T) -> T { let mut this = (*self).clone(); this.apply(f); this } fn apply(&mut self, mut f: F) where F: FnMut(T) -> T { for v in &mut self.0 { *v = f(*v) } } fn map_with_alpha(&self, f: F, g: G) -> $ident where F: FnMut(T) -> T, G: FnMut(T) -> T { let mut this = (*self).clone(); this.apply_with_alpha(f, g); this } fn apply_with_alpha(&mut self, mut f: F, mut g: G) where F: FnMut(T) -> T, G: FnMut(T) -> T { const ALPHA: usize = $channels - $alphas; for v in self.0[..ALPHA].iter_mut() { *v = f(*v) } // The branch of this match is `const`. This way ensures that no subexpression fails the // `const_err` lint (the expression `self.0[ALPHA]` would). if let Some(v) = self.0.get_mut(ALPHA) { *v = g(*v) } } fn map2(&self, other: &Self, f: F) -> $ident where F: FnMut(T, T) -> T { let mut this = (*self).clone(); this.apply2(other, f); this } fn apply2(&mut self, other: &$ident, mut f: F) where F: FnMut(T, T) -> T { for (a, &b) in self.0.iter_mut().zip(other.0.iter()) { *a = f(*a, b) } } fn invert(&mut self) { Invert::invert(self) } fn blend(&mut self, other: &$ident) { Blend::blend(self, other) } } impl Index for $ident { type Output = T; #[inline(always)] fn index(&self, _index: usize) -> &T { &self.0[_index] } } impl IndexMut for $ident { #[inline(always)] fn index_mut(&mut self, _index: usize) -> &mut T { &mut self.0[_index] } } impl From<[T; $channels]> for $ident { fn from(c: [T; $channels]) -> Self { Self(c) } } )* // END Structure definitions } } define_colors! { /// RGB colors. /// /// For the purpose of color conversion, as well as blending, the implementation of `Pixel` /// assumes an `sRGB` color space of its data. pub struct Rgb([T; 3, 0]) = "RGB"; /// Grayscale colors. pub struct Luma([T; 1, 0]) = "Y"; /// RGB colors + alpha channel pub struct Rgba([T; 4, 1]) = "RGBA"; /// Grayscale colors + alpha channel pub struct LumaA([T; 2, 1]) = "YA"; } /// Convert from one pixel component type to another. For example, convert from `u8` to `f32` pixel values. pub trait FromPrimitive { /// Converts from any pixel component type to this type. fn from_primitive(component: Component) -> Self; } impl FromPrimitive for T { fn from_primitive(sample: T) -> Self { sample } } // from f32: // Note that in to-integer-conversion we are performing rounding but NumCast::from is implemented // as truncate towards zero. We emulate rounding by adding a bias. impl FromPrimitive for u8 { fn from_primitive(float: f32) -> Self { let inner = (float.clamp(0.0, 1.0) * u8::MAX as f32).round(); NumCast::from(inner).unwrap() } } impl FromPrimitive for u16 { fn from_primitive(float: f32) -> Self { let inner = (float.clamp(0.0, 1.0) * u16::MAX as f32).round(); NumCast::from(inner).unwrap() } } // from u16: impl FromPrimitive for u8 { fn from_primitive(c16: u16) -> Self { fn from(c: impl Into) -> u32 { c.into() } // The input c is the numerator of `c / u16::MAX`. // Derive numerator of `num / u8::MAX`, with rounding. // // This method is based on the inverse (see FromPrimitive for u16) and was tested // exhaustively in Python. It's the same as the reference function: // round(c * (2**8 - 1) / (2**16 - 1)) NumCast::from((from(c16) + 128) / 257).unwrap() } } impl FromPrimitive for f32 { fn from_primitive(int: u16) -> Self { (int as f32 / u16::MAX as f32).clamp(0.0, 1.0) } } // from u8: impl FromPrimitive for f32 { fn from_primitive(int: u8) -> Self { (int as f32 / u8::MAX as f32).clamp(0.0, 1.0) } } impl FromPrimitive for u16 { fn from_primitive(c8: u8) -> Self { let x = c8.to_u64().unwrap(); NumCast::from((x << 8) | x).unwrap() } } /// Provides color conversions for the different pixel types. pub trait FromColor { /// Changes `self` to represent `Other` in the color space of `Self` fn from_color(&mut self, _: &Other); } /// Copy-based conversions to target pixel types using `FromColor`. // FIXME: this trait should be removed and replaced with real color space models // rather than assuming sRGB. pub(crate) trait IntoColor { /// Constructs a pixel of the target type and converts this pixel into it. fn into_color(&self) -> Other; } impl IntoColor for S where O: Pixel + FromColor, { fn into_color(&self) -> O { // Note we cannot use Pixel::CHANNELS_COUNT here to directly construct // the pixel due to a current bug/limitation of consts. #[allow(deprecated)] let mut pix = O::from_channels(Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero()); pix.from_color(self); pix } } /// Coefficients to transform from sRGB to a CIE Y (luminance) value. const SRGB_LUMA: [u32; 3] = [2126, 7152, 722]; const SRGB_LUMA_DIV: u32 = 10000; #[inline] fn rgb_to_luma(rgb: &[T]) -> T { let l = ::from(SRGB_LUMA[0]).unwrap() * rgb[0].to_larger() + ::from(SRGB_LUMA[1]).unwrap() * rgb[1].to_larger() + ::from(SRGB_LUMA[2]).unwrap() * rgb[2].to_larger(); T::clamp_from(l / ::from(SRGB_LUMA_DIV).unwrap()) } // `FromColor` for Luma impl FromColor> for Luma where T: FromPrimitive, { fn from_color(&mut self, other: &Luma) { let own = self.channels_mut(); let other = other.channels(); own[0] = T::from_primitive(other[0]); } } impl FromColor> for Luma where T: FromPrimitive, { fn from_color(&mut self, other: &LumaA) { self.channels_mut()[0] = T::from_primitive(other.channels()[0]) } } impl FromColor> for Luma where T: FromPrimitive, { fn from_color(&mut self, other: &Rgb) { let gray = self.channels_mut(); let rgb = other.channels(); gray[0] = T::from_primitive(rgb_to_luma(rgb)); } } impl FromColor> for Luma where T: FromPrimitive, { fn from_color(&mut self, other: &Rgba) { let gray = self.channels_mut(); let rgb = other.channels(); let l = rgb_to_luma(rgb); gray[0] = T::from_primitive(l); } } // `FromColor` for LumaA impl FromColor> for LumaA where T: FromPrimitive, { fn from_color(&mut self, other: &LumaA) { let own = self.channels_mut(); let other = other.channels(); own[0] = T::from_primitive(other[0]); own[1] = T::from_primitive(other[1]); } } impl FromColor> for LumaA where T: FromPrimitive, { fn from_color(&mut self, other: &Rgb) { let gray_a = self.channels_mut(); let rgb = other.channels(); gray_a[0] = T::from_primitive(rgb_to_luma(rgb)); gray_a[1] = T::DEFAULT_MAX_VALUE; } } impl FromColor> for LumaA where T: FromPrimitive, { fn from_color(&mut self, other: &Rgba) { let gray_a = self.channels_mut(); let rgba = other.channels(); gray_a[0] = T::from_primitive(rgb_to_luma(rgba)); gray_a[1] = T::from_primitive(rgba[3]); } } impl FromColor> for LumaA where T: FromPrimitive, { fn from_color(&mut self, other: &Luma) { let gray_a = self.channels_mut(); gray_a[0] = T::from_primitive(other.channels()[0]); gray_a[1] = T::DEFAULT_MAX_VALUE; } } // `FromColor` for RGBA impl FromColor> for Rgba where T: FromPrimitive, { fn from_color(&mut self, other: &Rgba) { let own = &mut self.0; let other = &other.0; own[0] = T::from_primitive(other[0]); own[1] = T::from_primitive(other[1]); own[2] = T::from_primitive(other[2]); own[3] = T::from_primitive(other[3]); } } impl FromColor> for Rgba where T: FromPrimitive, { fn from_color(&mut self, other: &Rgb) { let rgba = &mut self.0; let rgb = &other.0; rgba[0] = T::from_primitive(rgb[0]); rgba[1] = T::from_primitive(rgb[1]); rgba[2] = T::from_primitive(rgb[2]); rgba[3] = T::DEFAULT_MAX_VALUE; } } impl FromColor> for Rgba where T: FromPrimitive, { fn from_color(&mut self, gray: &LumaA) { let rgba = &mut self.0; let gray = &gray.0; rgba[0] = T::from_primitive(gray[0]); rgba[1] = T::from_primitive(gray[0]); rgba[2] = T::from_primitive(gray[0]); rgba[3] = T::from_primitive(gray[1]); } } impl FromColor> for Rgba where T: FromPrimitive, { fn from_color(&mut self, gray: &Luma) { let rgba = &mut self.0; let gray = gray.0[0]; rgba[0] = T::from_primitive(gray); rgba[1] = T::from_primitive(gray); rgba[2] = T::from_primitive(gray); rgba[3] = T::DEFAULT_MAX_VALUE; } } // `FromColor` for RGB impl FromColor> for Rgb where T: FromPrimitive, { fn from_color(&mut self, other: &Rgb) { let own = &mut self.0; let other = &other.0; own[0] = T::from_primitive(other[0]); own[1] = T::from_primitive(other[1]); own[2] = T::from_primitive(other[2]); } } impl FromColor> for Rgb where T: FromPrimitive, { fn from_color(&mut self, other: &Rgba) { let rgb = &mut self.0; let rgba = &other.0; rgb[0] = T::from_primitive(rgba[0]); rgb[1] = T::from_primitive(rgba[1]); rgb[2] = T::from_primitive(rgba[2]); } } impl FromColor> for Rgb where T: FromPrimitive, { fn from_color(&mut self, other: &LumaA) { let rgb = &mut self.0; let gray = other.0[0]; rgb[0] = T::from_primitive(gray); rgb[1] = T::from_primitive(gray); rgb[2] = T::from_primitive(gray); } } impl FromColor> for Rgb where T: FromPrimitive, { fn from_color(&mut self, other: &Luma) { let rgb = &mut self.0; let gray = other.0[0]; rgb[0] = T::from_primitive(gray); rgb[1] = T::from_primitive(gray); rgb[2] = T::from_primitive(gray); } } /// Blends a color inter another one pub(crate) trait Blend { /// Blends a color in-place. fn blend(&mut self, other: &Self); } impl Blend for LumaA { fn blend(&mut self, other: &LumaA) { let max_t = T::DEFAULT_MAX_VALUE; let max_t = max_t.to_f32().unwrap(); let (bg_luma, bg_a) = (self.0[0], self.0[1]); let (fg_luma, fg_a) = (other.0[0], other.0[1]); let (bg_luma, bg_a) = ( bg_luma.to_f32().unwrap() / max_t, bg_a.to_f32().unwrap() / max_t, ); let (fg_luma, fg_a) = ( fg_luma.to_f32().unwrap() / max_t, fg_a.to_f32().unwrap() / max_t, ); let alpha_final = bg_a + fg_a - bg_a * fg_a; if alpha_final == 0.0 { return; }; let bg_luma_a = bg_luma * bg_a; let fg_luma_a = fg_luma * fg_a; let out_luma_a = fg_luma_a + bg_luma_a * (1.0 - fg_a); let out_luma = out_luma_a / alpha_final; *self = LumaA([ NumCast::from(max_t * out_luma).unwrap(), NumCast::from(max_t * alpha_final).unwrap(), ]) } } impl Blend for Luma { fn blend(&mut self, other: &Luma) { *self = *other } } impl Blend for Rgba { fn blend(&mut self, other: &Rgba) { // http://stackoverflow.com/questions/7438263/alpha-compositing-algorithm-blend-modes#answer-11163848 if other.0[3].is_zero() { return; } if other.0[3] == T::DEFAULT_MAX_VALUE { *self = *other; return; } // First, as we don't know what type our pixel is, we have to convert to floats between 0.0 and 1.0 let max_t = T::DEFAULT_MAX_VALUE; let max_t = max_t.to_f32().unwrap(); let (bg_r, bg_g, bg_b, bg_a) = (self.0[0], self.0[1], self.0[2], self.0[3]); let (fg_r, fg_g, fg_b, fg_a) = (other.0[0], other.0[1], other.0[2], other.0[3]); let (bg_r, bg_g, bg_b, bg_a) = ( bg_r.to_f32().unwrap() / max_t, bg_g.to_f32().unwrap() / max_t, bg_b.to_f32().unwrap() / max_t, bg_a.to_f32().unwrap() / max_t, ); let (fg_r, fg_g, fg_b, fg_a) = ( fg_r.to_f32().unwrap() / max_t, fg_g.to_f32().unwrap() / max_t, fg_b.to_f32().unwrap() / max_t, fg_a.to_f32().unwrap() / max_t, ); // Work out what the final alpha level will be let alpha_final = bg_a + fg_a - bg_a * fg_a; if alpha_final == 0.0 { return; }; // We premultiply our channels by their alpha, as this makes it easier to calculate let (bg_r_a, bg_g_a, bg_b_a) = (bg_r * bg_a, bg_g * bg_a, bg_b * bg_a); let (fg_r_a, fg_g_a, fg_b_a) = (fg_r * fg_a, fg_g * fg_a, fg_b * fg_a); // Standard formula for src-over alpha compositing let (out_r_a, out_g_a, out_b_a) = ( fg_r_a + bg_r_a * (1.0 - fg_a), fg_g_a + bg_g_a * (1.0 - fg_a), fg_b_a + bg_b_a * (1.0 - fg_a), ); // Unmultiply the channels by our resultant alpha channel let (out_r, out_g, out_b) = ( out_r_a / alpha_final, out_g_a / alpha_final, out_b_a / alpha_final, ); // Cast back to our initial type on return *self = Rgba([ NumCast::from(max_t * out_r).unwrap(), NumCast::from(max_t * out_g).unwrap(), NumCast::from(max_t * out_b).unwrap(), NumCast::from(max_t * alpha_final).unwrap(), ]) } } impl Blend for Rgb { fn blend(&mut self, other: &Rgb) { *self = *other } } /// Invert a color pub(crate) trait Invert { /// Inverts a color in-place. fn invert(&mut self); } impl Invert for LumaA { fn invert(&mut self) { let l = self.0; let max = T::DEFAULT_MAX_VALUE; *self = LumaA([max - l[0], l[1]]) } } impl Invert for Luma { fn invert(&mut self) { let l = self.0; let max = T::DEFAULT_MAX_VALUE; let l1 = max - l[0]; *self = Luma([l1]) } } impl Invert for Rgba { fn invert(&mut self) { let rgba = self.0; let max = T::DEFAULT_MAX_VALUE; *self = Rgba([max - rgba[0], max - rgba[1], max - rgba[2], rgba[3]]) } } impl Invert for Rgb { fn invert(&mut self) { let rgb = self.0; let max = T::DEFAULT_MAX_VALUE; let r1 = max - rgb[0]; let g1 = max - rgb[1]; let b1 = max - rgb[2]; *self = Rgb([r1, g1, b1]) } } #[cfg(test)] mod tests { use super::{Luma, LumaA, Pixel, Rgb, Rgba}; #[test] fn test_apply_with_alpha_rgba() { let mut rgba = Rgba([0, 0, 0, 0]); rgba.apply_with_alpha(|s| s, |_| 0xFF); assert_eq!(rgba, Rgba([0, 0, 0, 0xFF])); } #[test] fn test_apply_with_alpha_rgb() { let mut rgb = Rgb([0, 0, 0]); rgb.apply_with_alpha(|s| s, |_| panic!("bug")); assert_eq!(rgb, Rgb([0, 0, 0])); } #[test] fn test_map_with_alpha_rgba() { let rgba = Rgba([0, 0, 0, 0]).map_with_alpha(|s| s, |_| 0xFF); assert_eq!(rgba, Rgba([0, 0, 0, 0xFF])); } #[test] fn test_map_with_alpha_rgb() { let rgb = Rgb([0, 0, 0]).map_with_alpha(|s| s, |_| panic!("bug")); assert_eq!(rgb, Rgb([0, 0, 0])); } #[test] fn test_blend_luma_alpha() { let ref mut a = LumaA([255 as u8, 255]); let b = LumaA([255 as u8, 255]); a.blend(&b); assert_eq!(a.0[0], 255); assert_eq!(a.0[1], 255); let ref mut a = LumaA([255 as u8, 0]); let b = LumaA([255 as u8, 255]); a.blend(&b); assert_eq!(a.0[0], 255); assert_eq!(a.0[1], 255); let ref mut a = LumaA([255 as u8, 255]); let b = LumaA([255 as u8, 0]); a.blend(&b); assert_eq!(a.0[0], 255); assert_eq!(a.0[1], 255); let ref mut a = LumaA([255 as u8, 0]); let b = LumaA([255 as u8, 0]); a.blend(&b); assert_eq!(a.0[0], 255); assert_eq!(a.0[1], 0); } #[test] fn test_blend_rgba() { let ref mut a = Rgba([255 as u8, 255, 255, 255]); let b = Rgba([255 as u8, 255, 255, 255]); a.blend(&b); assert_eq!(a.0, [255, 255, 255, 255]); let ref mut a = Rgba([255 as u8, 255, 255, 0]); let b = Rgba([255 as u8, 255, 255, 255]); a.blend(&b); assert_eq!(a.0, [255, 255, 255, 255]); let ref mut a = Rgba([255 as u8, 255, 255, 255]); let b = Rgba([255 as u8, 255, 255, 0]); a.blend(&b); assert_eq!(a.0, [255, 255, 255, 255]); let ref mut a = Rgba([255 as u8, 255, 255, 0]); let b = Rgba([255 as u8, 255, 255, 0]); a.blend(&b); assert_eq!(a.0, [255, 255, 255, 0]); } #[test] fn test_apply_without_alpha_rgba() { let mut rgba = Rgba([0, 0, 0, 0]); rgba.apply_without_alpha(|s| s + 1); assert_eq!(rgba, Rgba([1, 1, 1, 0])); } #[test] fn test_apply_without_alpha_rgb() { let mut rgb = Rgb([0, 0, 0]); rgb.apply_without_alpha(|s| s + 1); assert_eq!(rgb, Rgb([1, 1, 1])); } #[test] fn test_map_without_alpha_rgba() { let rgba = Rgba([0, 0, 0, 0]).map_without_alpha(|s| s + 1); assert_eq!(rgba, Rgba([1, 1, 1, 0])); } #[test] fn test_map_without_alpha_rgb() { let rgb = Rgb([0, 0, 0]).map_without_alpha(|s| s + 1); assert_eq!(rgb, Rgb([1, 1, 1])); } macro_rules! test_lossless_conversion { ($a:ty, $b:ty, $c:ty) => { let a: $a = [<$a as Pixel>::Subpixel::DEFAULT_MAX_VALUE >> 2; <$a as Pixel>::CHANNEL_COUNT as usize] .into(); let b: $b = a.into_color(); let c: $c = b.into_color(); assert_eq!(a.channels(), c.channels()); }; } #[test] fn test_lossless_conversions() { use super::IntoColor; use crate::traits::Primitive; test_lossless_conversion!(Luma, Luma, Luma); test_lossless_conversion!(LumaA, LumaA, LumaA); test_lossless_conversion!(Rgb, Rgb, Rgb); test_lossless_conversion!(Rgba, Rgba, Rgba); } #[test] fn accuracy_conversion() { use super::{Luma, Pixel, Rgb}; let pixel = Rgb::from([13, 13, 13]); let Luma([luma]) = pixel.to_luma(); assert_eq!(luma, 13); } } image-0.24.7/src/dynimage.rs000064400000000000000000001337511046102023000137440ustar 00000000000000use std::io; use std::io::{Seek, Write}; use std::path::Path; use std::u32; #[cfg(feature = "gif")] use crate::codecs::gif; #[cfg(feature = "png")] use crate::codecs::png; #[cfg(feature = "pnm")] use crate::codecs::pnm; use crate::buffer_::{ ConvertBuffer, Gray16Image, GrayAlpha16Image, GrayAlphaImage, GrayImage, ImageBuffer, Rgb16Image, RgbImage, Rgba16Image, RgbaImage, }; use crate::color::{self, IntoColor}; use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; use crate::flat::FlatSamples; use crate::image::{ GenericImage, GenericImageView, ImageDecoder, ImageEncoder, ImageFormat, ImageOutputFormat, }; use crate::imageops; use crate::io::free_functions; use crate::math::resize_dimensions; use crate::traits::Pixel; use crate::{image, Luma, LumaA}; use crate::{Rgb32FImage, Rgba32FImage}; /// A Dynamic Image /// /// This represents a _matrix_ of _pixels_ which are _convertible_ from and to an _RGBA_ /// representation. More variants that adhere to these principles may get added in the future, in /// particular to cover other combinations typically used. /// /// # Usage /// /// This type can act as a converter between specific `ImageBuffer` instances. /// /// ``` /// use image::{DynamicImage, GrayImage, RgbImage}; /// /// let rgb: RgbImage = RgbImage::new(10, 10); /// let luma: GrayImage = DynamicImage::ImageRgb8(rgb).into_luma8(); /// ``` /// /// # Design /// /// There is no goal to provide an all-encompassing type with all possible memory layouts. This /// would hardly be feasible as a simple enum, due to the sheer number of combinations of channel /// kinds, channel order, and bit depth. Rather, this type provides an opinionated selection with /// normalized channel order which can store common pixel values without loss. #[derive(Clone, Debug, PartialEq)] #[non_exhaustive] pub enum DynamicImage { /// Each pixel in this image is 8-bit Luma ImageLuma8(GrayImage), /// Each pixel in this image is 8-bit Luma with alpha ImageLumaA8(GrayAlphaImage), /// Each pixel in this image is 8-bit Rgb ImageRgb8(RgbImage), /// Each pixel in this image is 8-bit Rgb with alpha ImageRgba8(RgbaImage), /// Each pixel in this image is 16-bit Luma ImageLuma16(Gray16Image), /// Each pixel in this image is 16-bit Luma with alpha ImageLumaA16(GrayAlpha16Image), /// Each pixel in this image is 16-bit Rgb ImageRgb16(Rgb16Image), /// Each pixel in this image is 16-bit Rgb with alpha ImageRgba16(Rgba16Image), /// Each pixel in this image is 32-bit float Rgb ImageRgb32F(Rgb32FImage), /// Each pixel in this image is 32-bit float Rgb with alpha ImageRgba32F(Rgba32FImage), } macro_rules! dynamic_map( ($dynimage: expr, $image: pat => $action: expr) => ({ use DynamicImage::*; match $dynimage { ImageLuma8($image) => ImageLuma8($action), ImageLumaA8($image) => ImageLumaA8($action), ImageRgb8($image) => ImageRgb8($action), ImageRgba8($image) => ImageRgba8($action), ImageLuma16($image) => ImageLuma16($action), ImageLumaA16($image) => ImageLumaA16($action), ImageRgb16($image) => ImageRgb16($action), ImageRgba16($image) => ImageRgba16($action), ImageRgb32F($image) => ImageRgb32F($action), ImageRgba32F($image) => ImageRgba32F($action), } }); ($dynimage: expr, |$image: pat| $action: expr) => ( match $dynimage { DynamicImage::ImageLuma8($image) => $action, DynamicImage::ImageLumaA8($image) => $action, DynamicImage::ImageRgb8($image) => $action, DynamicImage::ImageRgba8($image) => $action, DynamicImage::ImageLuma16($image) => $action, DynamicImage::ImageLumaA16($image) => $action, DynamicImage::ImageRgb16($image) => $action, DynamicImage::ImageRgba16($image) => $action, DynamicImage::ImageRgb32F($image) => $action, DynamicImage::ImageRgba32F($image) => $action, } ); ); impl DynamicImage { /// Creates a dynamic image backed by a buffer of gray pixels. pub fn new_luma8(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageLuma8(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of gray /// pixels with transparency. pub fn new_luma_a8(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageLumaA8(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGB pixels. pub fn new_rgb8(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgb8(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGBA pixels. pub fn new_rgba8(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgba8(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of gray pixels. pub fn new_luma16(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageLuma16(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of gray /// pixels with transparency. pub fn new_luma_a16(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageLumaA16(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGB pixels. pub fn new_rgb16(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgb16(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGBA pixels. pub fn new_rgba16(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgba16(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGB pixels. pub fn new_rgb32f(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgb32F(ImageBuffer::new(w, h)) } /// Creates a dynamic image backed by a buffer of RGBA pixels. pub fn new_rgba32f(w: u32, h: u32) -> DynamicImage { DynamicImage::ImageRgba32F(ImageBuffer::new(w, h)) } /// Decodes an encoded image into a dynamic image. pub fn from_decoder<'a>(decoder: impl ImageDecoder<'a>) -> ImageResult { decoder_to_image(decoder) } /// Returns a copy of this image as an RGB image. pub fn to_rgb8(&self) -> RgbImage { dynamic_map!(*self, |ref p| p.convert()) } /// Returns a copy of this image as an RGB image. pub fn to_rgb16(&self) -> Rgb16Image { dynamic_map!(*self, |ref p| p.convert()) } /// Returns a copy of this image as an RGB image. pub fn to_rgb32f(&self) -> Rgb32FImage { dynamic_map!(*self, |ref p| p.convert()) } /// Returns a copy of this image as an RGBA image. pub fn to_rgba8(&self) -> RgbaImage { dynamic_map!(*self, |ref p| p.convert()) } /// Returns a copy of this image as an RGBA image. pub fn to_rgba16(&self) -> Rgba16Image { dynamic_map!(*self, |ref p| p.convert()) } /// Returns a copy of this image as an RGBA image. pub fn to_rgba32f(&self) -> Rgba32FImage { dynamic_map!(*self, |ref p| p.convert()) } /// Returns a copy of this image as a Luma image. pub fn to_luma8(&self) -> GrayImage { dynamic_map!(*self, |ref p| p.convert()) } /// Returns a copy of this image as a Luma image. pub fn to_luma16(&self) -> Gray16Image { dynamic_map!(*self, |ref p| p.convert()) } /// Returns a copy of this image as a Luma image. pub fn to_luma32f(&self) -> ImageBuffer, Vec> { dynamic_map!(*self, |ref p| p.convert()) } /// Returns a copy of this image as a LumaA image. pub fn to_luma_alpha8(&self) -> GrayAlphaImage { dynamic_map!(*self, |ref p| p.convert()) } /// Returns a copy of this image as a LumaA image. pub fn to_luma_alpha16(&self) -> GrayAlpha16Image { dynamic_map!(*self, |ref p| p.convert()) } /// Returns a copy of this image as a LumaA image. pub fn to_luma_alpha32f(&self) -> ImageBuffer, Vec> { dynamic_map!(*self, |ref p| p.convert()) } /// Consume the image and returns a RGB image. /// /// If the image was already the correct format, it is returned as is. /// Otherwise, a copy is created. pub fn into_rgb8(self) -> RgbImage { match self { DynamicImage::ImageRgb8(x) => x, x => x.to_rgb8(), } } /// Consume the image and returns a RGB image. /// /// If the image was already the correct format, it is returned as is. /// Otherwise, a copy is created. pub fn into_rgb16(self) -> Rgb16Image { match self { DynamicImage::ImageRgb16(x) => x, x => x.to_rgb16(), } } /// Consume the image and returns a RGB image. /// /// If the image was already the correct format, it is returned as is. /// Otherwise, a copy is created. pub fn into_rgb32f(self) -> Rgb32FImage { match self { DynamicImage::ImageRgb32F(x) => x, x => x.to_rgb32f(), } } /// Consume the image and returns a RGBA image. /// /// If the image was already the correct format, it is returned as is. /// Otherwise, a copy is created. pub fn into_rgba8(self) -> RgbaImage { match self { DynamicImage::ImageRgba8(x) => x, x => x.to_rgba8(), } } /// Consume the image and returns a RGBA image. /// /// If the image was already the correct format, it is returned as is. /// Otherwise, a copy is created. pub fn into_rgba16(self) -> Rgba16Image { match self { DynamicImage::ImageRgba16(x) => x, x => x.to_rgba16(), } } /// Consume the image and returns a RGBA image. /// /// If the image was already the correct format, it is returned as is. /// Otherwise, a copy is created. pub fn into_rgba32f(self) -> Rgba32FImage { match self { DynamicImage::ImageRgba32F(x) => x, x => x.to_rgba32f(), } } /// Consume the image and returns a Luma image. /// /// If the image was already the correct format, it is returned as is. /// Otherwise, a copy is created. pub fn into_luma8(self) -> GrayImage { match self { DynamicImage::ImageLuma8(x) => x, x => x.to_luma8(), } } /// Consume the image and returns a Luma image. /// /// If the image was already the correct format, it is returned as is. /// Otherwise, a copy is created. pub fn into_luma16(self) -> Gray16Image { match self { DynamicImage::ImageLuma16(x) => x, x => x.to_luma16(), } } /// Consume the image and returns a LumaA image. /// /// If the image was already the correct format, it is returned as is. /// Otherwise, a copy is created. pub fn into_luma_alpha8(self) -> GrayAlphaImage { match self { DynamicImage::ImageLumaA8(x) => x, x => x.to_luma_alpha8(), } } /// Consume the image and returns a LumaA image. /// /// If the image was already the correct format, it is returned as is. /// Otherwise, a copy is created. pub fn into_luma_alpha16(self) -> GrayAlpha16Image { match self { DynamicImage::ImageLumaA16(x) => x, x => x.to_luma_alpha16(), } } /// Return a cut-out of this image delimited by the bounding rectangle. /// /// Note: this method does *not* modify the object, /// and its signature will be replaced with `crop_imm()`'s in the 0.24 release pub fn crop(&mut self, x: u32, y: u32, width: u32, height: u32) -> DynamicImage { dynamic_map!(*self, ref mut p => imageops::crop(p, x, y, width, height).to_image()) } /// Return a cut-out of this image delimited by the bounding rectangle. pub fn crop_imm(&self, x: u32, y: u32, width: u32, height: u32) -> DynamicImage { dynamic_map!(*self, ref p => imageops::crop_imm(p, x, y, width, height).to_image()) } /// Return a reference to an 8bit RGB image pub fn as_rgb8(&self) -> Option<&RgbImage> { match *self { DynamicImage::ImageRgb8(ref p) => Some(p), _ => None, } } /// Return a mutable reference to an 8bit RGB image pub fn as_mut_rgb8(&mut self) -> Option<&mut RgbImage> { match *self { DynamicImage::ImageRgb8(ref mut p) => Some(p), _ => None, } } /// Return a reference to an 8bit RGBA image pub fn as_rgba8(&self) -> Option<&RgbaImage> { match *self { DynamicImage::ImageRgba8(ref p) => Some(p), _ => None, } } /// Return a mutable reference to an 8bit RGBA image pub fn as_mut_rgba8(&mut self) -> Option<&mut RgbaImage> { match *self { DynamicImage::ImageRgba8(ref mut p) => Some(p), _ => None, } } /// Return a reference to an 8bit Grayscale image pub fn as_luma8(&self) -> Option<&GrayImage> { match *self { DynamicImage::ImageLuma8(ref p) => Some(p), _ => None, } } /// Return a mutable reference to an 8bit Grayscale image pub fn as_mut_luma8(&mut self) -> Option<&mut GrayImage> { match *self { DynamicImage::ImageLuma8(ref mut p) => Some(p), _ => None, } } /// Return a reference to an 8bit Grayscale image with an alpha channel pub fn as_luma_alpha8(&self) -> Option<&GrayAlphaImage> { match *self { DynamicImage::ImageLumaA8(ref p) => Some(p), _ => None, } } /// Return a mutable reference to an 8bit Grayscale image with an alpha channel pub fn as_mut_luma_alpha8(&mut self) -> Option<&mut GrayAlphaImage> { match *self { DynamicImage::ImageLumaA8(ref mut p) => Some(p), _ => None, } } /// Return a reference to an 16bit RGB image pub fn as_rgb16(&self) -> Option<&Rgb16Image> { match *self { DynamicImage::ImageRgb16(ref p) => Some(p), _ => None, } } /// Return a mutable reference to an 16bit RGB image pub fn as_mut_rgb16(&mut self) -> Option<&mut Rgb16Image> { match *self { DynamicImage::ImageRgb16(ref mut p) => Some(p), _ => None, } } /// Return a reference to an 16bit RGBA image pub fn as_rgba16(&self) -> Option<&Rgba16Image> { match *self { DynamicImage::ImageRgba16(ref p) => Some(p), _ => None, } } /// Return a mutable reference to an 16bit RGBA image pub fn as_mut_rgba16(&mut self) -> Option<&mut Rgba16Image> { match *self { DynamicImage::ImageRgba16(ref mut p) => Some(p), _ => None, } } /// Return a reference to an 32bit RGB image pub fn as_rgb32f(&self) -> Option<&Rgb32FImage> { match *self { DynamicImage::ImageRgb32F(ref p) => Some(p), _ => None, } } /// Return a mutable reference to an 32bit RGB image pub fn as_mut_rgb32f(&mut self) -> Option<&mut Rgb32FImage> { match *self { DynamicImage::ImageRgb32F(ref mut p) => Some(p), _ => None, } } /// Return a reference to an 32bit RGBA image pub fn as_rgba32f(&self) -> Option<&Rgba32FImage> { match *self { DynamicImage::ImageRgba32F(ref p) => Some(p), _ => None, } } /// Return a mutable reference to an 16bit RGBA image pub fn as_mut_rgba32f(&mut self) -> Option<&mut Rgba32FImage> { match *self { DynamicImage::ImageRgba32F(ref mut p) => Some(p), _ => None, } } /// Return a reference to an 16bit Grayscale image pub fn as_luma16(&self) -> Option<&Gray16Image> { match *self { DynamicImage::ImageLuma16(ref p) => Some(p), _ => None, } } /// Return a mutable reference to an 16bit Grayscale image pub fn as_mut_luma16(&mut self) -> Option<&mut Gray16Image> { match *self { DynamicImage::ImageLuma16(ref mut p) => Some(p), _ => None, } } /// Return a reference to an 16bit Grayscale image with an alpha channel pub fn as_luma_alpha16(&self) -> Option<&GrayAlpha16Image> { match *self { DynamicImage::ImageLumaA16(ref p) => Some(p), _ => None, } } /// Return a mutable reference to an 16bit Grayscale image with an alpha channel pub fn as_mut_luma_alpha16(&mut self) -> Option<&mut GrayAlpha16Image> { match *self { DynamicImage::ImageLumaA16(ref mut p) => Some(p), _ => None, } } /// Return a view on the raw sample buffer for 8 bit per channel images. pub fn as_flat_samples_u8(&self) -> Option> { match *self { DynamicImage::ImageLuma8(ref p) => Some(p.as_flat_samples()), DynamicImage::ImageLumaA8(ref p) => Some(p.as_flat_samples()), DynamicImage::ImageRgb8(ref p) => Some(p.as_flat_samples()), DynamicImage::ImageRgba8(ref p) => Some(p.as_flat_samples()), _ => None, } } /// Return a view on the raw sample buffer for 16 bit per channel images. pub fn as_flat_samples_u16(&self) -> Option> { match *self { DynamicImage::ImageLuma16(ref p) => Some(p.as_flat_samples()), DynamicImage::ImageLumaA16(ref p) => Some(p.as_flat_samples()), DynamicImage::ImageRgb16(ref p) => Some(p.as_flat_samples()), DynamicImage::ImageRgba16(ref p) => Some(p.as_flat_samples()), _ => None, } } /// Return a view on the raw sample buffer for 32bit per channel images. pub fn as_flat_samples_f32(&self) -> Option> { match *self { DynamicImage::ImageRgb32F(ref p) => Some(p.as_flat_samples()), DynamicImage::ImageRgba32F(ref p) => Some(p.as_flat_samples()), _ => None, } } /// Return this image's pixels as a native endian byte slice. pub fn as_bytes(&self) -> &[u8] { // we can do this because every variant contains an `ImageBuffer<_, Vec<_>>` dynamic_map!(*self, |ref image_buffer| bytemuck::cast_slice( image_buffer.as_raw().as_ref() )) } // TODO: choose a name under which to expose? fn inner_bytes(&self) -> &[u8] { // we can do this because every variant contains an `ImageBuffer<_, Vec<_>>` dynamic_map!(*self, |ref image_buffer| bytemuck::cast_slice( image_buffer.inner_pixels() )) } /// Return this image's pixels as a byte vector. If the `ImageBuffer` /// container is `Vec`, this operation is free. Otherwise, a copy /// is returned. pub fn into_bytes(self) -> Vec { // we can do this because every variant contains an `ImageBuffer<_, Vec<_>>` dynamic_map!(self, |image_buffer| { match bytemuck::allocation::try_cast_vec(image_buffer.into_raw()) { Ok(vec) => vec, Err((_, vec)) => { // Fallback: vector requires an exact alignment and size match // Reuse of the allocation as done in the Ok branch only works if the // underlying container is exactly Vec (or compatible but that's the only // alternative at the time of writing). // In all other cases we must allocate a new vector with the 'same' contents. bytemuck::cast_slice(&vec).to_owned() } } }) } /// Return a copy of this image's pixels as a byte vector. /// Deprecated, because it does nothing but hide an expensive clone operation. #[deprecated( since = "0.24.0", note = "use `image.into_bytes()` or `image.as_bytes().to_vec()` instead" )] pub fn to_bytes(&self) -> Vec { self.as_bytes().to_vec() } /// Return this image's color type. pub fn color(&self) -> color::ColorType { match *self { DynamicImage::ImageLuma8(_) => color::ColorType::L8, DynamicImage::ImageLumaA8(_) => color::ColorType::La8, DynamicImage::ImageRgb8(_) => color::ColorType::Rgb8, DynamicImage::ImageRgba8(_) => color::ColorType::Rgba8, DynamicImage::ImageLuma16(_) => color::ColorType::L16, DynamicImage::ImageLumaA16(_) => color::ColorType::La16, DynamicImage::ImageRgb16(_) => color::ColorType::Rgb16, DynamicImage::ImageRgba16(_) => color::ColorType::Rgba16, DynamicImage::ImageRgb32F(_) => color::ColorType::Rgb32F, DynamicImage::ImageRgba32F(_) => color::ColorType::Rgba32F, } } /// Returns the width of the underlying image pub fn width(&self) -> u32 { dynamic_map!(*self, |ref p| { p.width() }) } /// Returns the height of the underlying image pub fn height(&self) -> u32 { dynamic_map!(*self, |ref p| { p.height() }) } /// Return a grayscale version of this image. /// Returns `Luma` images in most cases. However, for `f32` images, /// this will return a grayscale `Rgb/Rgba` image instead. pub fn grayscale(&self) -> DynamicImage { match *self { DynamicImage::ImageLuma8(ref p) => DynamicImage::ImageLuma8(p.clone()), DynamicImage::ImageLumaA8(ref p) => { DynamicImage::ImageLumaA8(imageops::grayscale_alpha(p)) } DynamicImage::ImageRgb8(ref p) => DynamicImage::ImageLuma8(imageops::grayscale(p)), DynamicImage::ImageRgba8(ref p) => { DynamicImage::ImageLumaA8(imageops::grayscale_alpha(p)) } DynamicImage::ImageLuma16(ref p) => DynamicImage::ImageLuma16(p.clone()), DynamicImage::ImageLumaA16(ref p) => { DynamicImage::ImageLumaA16(imageops::grayscale_alpha(p)) } DynamicImage::ImageRgb16(ref p) => DynamicImage::ImageLuma16(imageops::grayscale(p)), DynamicImage::ImageRgba16(ref p) => { DynamicImage::ImageLumaA16(imageops::grayscale_alpha(p)) } DynamicImage::ImageRgb32F(ref p) => { DynamicImage::ImageRgb32F(imageops::grayscale_with_type(p)) } DynamicImage::ImageRgba32F(ref p) => { DynamicImage::ImageRgba32F(imageops::grayscale_with_type_alpha(p)) } } } /// Invert the colors of this image. /// This method operates inplace. pub fn invert(&mut self) { dynamic_map!(*self, |ref mut p| imageops::invert(p)) } /// Resize this image using the specified filter algorithm. /// Returns a new image. The image's aspect ratio is preserved. /// The image is scaled to the maximum possible size that fits /// within the bounds specified by `nwidth` and `nheight`. pub fn resize(&self, nwidth: u32, nheight: u32, filter: imageops::FilterType) -> DynamicImage { if (nwidth, nheight) == self.dimensions() { return self.clone(); } let (width2, height2) = resize_dimensions(self.width(), self.height(), nwidth, nheight, false); self.resize_exact(width2, height2, filter) } /// Resize this image using the specified filter algorithm. /// Returns a new image. Does not preserve aspect ratio. /// `nwidth` and `nheight` are the new image's dimensions pub fn resize_exact( &self, nwidth: u32, nheight: u32, filter: imageops::FilterType, ) -> DynamicImage { dynamic_map!(*self, ref p => imageops::resize(p, nwidth, nheight, filter)) } /// Scale this image down to fit within a specific size. /// Returns a new image. The image's aspect ratio is preserved. /// The image is scaled to the maximum possible size that fits /// within the bounds specified by `nwidth` and `nheight`. /// /// This method uses a fast integer algorithm where each source /// pixel contributes to exactly one target pixel. /// May give aliasing artifacts if new size is close to old size. pub fn thumbnail(&self, nwidth: u32, nheight: u32) -> DynamicImage { let (width2, height2) = resize_dimensions(self.width(), self.height(), nwidth, nheight, false); self.thumbnail_exact(width2, height2) } /// Scale this image down to a specific size. /// Returns a new image. Does not preserve aspect ratio. /// `nwidth` and `nheight` are the new image's dimensions. /// This method uses a fast integer algorithm where each source /// pixel contributes to exactly one target pixel. /// May give aliasing artifacts if new size is close to old size. pub fn thumbnail_exact(&self, nwidth: u32, nheight: u32) -> DynamicImage { dynamic_map!(*self, ref p => imageops::thumbnail(p, nwidth, nheight)) } /// Resize this image using the specified filter algorithm. /// Returns a new image. The image's aspect ratio is preserved. /// The image is scaled to the maximum possible size that fits /// within the larger (relative to aspect ratio) of the bounds /// specified by `nwidth` and `nheight`, then cropped to /// fit within the other bound. pub fn resize_to_fill( &self, nwidth: u32, nheight: u32, filter: imageops::FilterType, ) -> DynamicImage { let (width2, height2) = resize_dimensions(self.width(), self.height(), nwidth, nheight, true); let mut intermediate = self.resize_exact(width2, height2, filter); let (iwidth, iheight) = intermediate.dimensions(); let ratio = u64::from(iwidth) * u64::from(nheight); let nratio = u64::from(nwidth) * u64::from(iheight); if nratio > ratio { intermediate.crop(0, (iheight - nheight) / 2, nwidth, nheight) } else { intermediate.crop((iwidth - nwidth) / 2, 0, nwidth, nheight) } } /// Performs a Gaussian blur on this image. /// `sigma` is a measure of how much to blur by. pub fn blur(&self, sigma: f32) -> DynamicImage { dynamic_map!(*self, ref p => imageops::blur(p, sigma)) } /// Performs an unsharpen mask on this image. /// `sigma` is the amount to blur the image by. /// `threshold` is a control of how much to sharpen. /// /// See pub fn unsharpen(&self, sigma: f32, threshold: i32) -> DynamicImage { dynamic_map!(*self, ref p => imageops::unsharpen(p, sigma, threshold)) } /// Filters this image with the specified 3x3 kernel. pub fn filter3x3(&self, kernel: &[f32]) -> DynamicImage { if kernel.len() != 9 { panic!("filter must be 3 x 3") } dynamic_map!(*self, ref p => imageops::filter3x3(p, kernel)) } /// Adjust the contrast of this image. /// `contrast` is the amount to adjust the contrast by. /// Negative values decrease the contrast and positive values increase the contrast. pub fn adjust_contrast(&self, c: f32) -> DynamicImage { dynamic_map!(*self, ref p => imageops::contrast(p, c)) } /// Brighten the pixels of this image. /// `value` is the amount to brighten each pixel by. /// Negative values decrease the brightness and positive values increase it. pub fn brighten(&self, value: i32) -> DynamicImage { dynamic_map!(*self, ref p => imageops::brighten(p, value)) } /// Hue rotate the supplied image. /// `value` is the degrees to rotate each pixel by. /// 0 and 360 do nothing, the rest rotates by the given degree value. /// just like the css webkit filter hue-rotate(180) pub fn huerotate(&self, value: i32) -> DynamicImage { dynamic_map!(*self, ref p => imageops::huerotate(p, value)) } /// Flip this image vertically pub fn flipv(&self) -> DynamicImage { dynamic_map!(*self, ref p => imageops::flip_vertical(p)) } /// Flip this image horizontally pub fn fliph(&self) -> DynamicImage { dynamic_map!(*self, ref p => imageops::flip_horizontal(p)) } /// Rotate this image 90 degrees clockwise. pub fn rotate90(&self) -> DynamicImage { dynamic_map!(*self, ref p => imageops::rotate90(p)) } /// Rotate this image 180 degrees clockwise. pub fn rotate180(&self) -> DynamicImage { dynamic_map!(*self, ref p => imageops::rotate180(p)) } /// Rotate this image 270 degrees clockwise. pub fn rotate270(&self) -> DynamicImage { dynamic_map!(*self, ref p => imageops::rotate270(p)) } /// Encode this image and write it to ```w```. /// /// Assumes the writer is buffered. In most cases, /// you should wrap your writer in a `BufWriter` for best performance. pub fn write_to>( &self, w: &mut W, format: F, ) -> ImageResult<()> { #[allow(unused_variables)] // When no features are supported let w = w; #[allow(unused_variables, unused_mut)] let mut bytes = self.inner_bytes(); #[allow(unused_variables)] let (width, height) = self.dimensions(); #[allow(unused_variables, unused_mut)] let mut color = self.color(); let format = format.into(); // TODO do not repeat this match statement across the crate #[allow(deprecated)] match format { #[cfg(feature = "png")] image::ImageOutputFormat::Png => { let p = png::PngEncoder::new(w); p.write_image(bytes, width, height, color)?; Ok(()) } #[cfg(feature = "pnm")] image::ImageOutputFormat::Pnm(subtype) => { let p = pnm::PnmEncoder::new(w).with_subtype(subtype); p.write_image(bytes, width, height, color)?; Ok(()) } #[cfg(feature = "gif")] image::ImageOutputFormat::Gif => { let mut g = gif::GifEncoder::new(w); g.encode_frame(crate::animation::Frame::new(self.to_rgba8()))?; Ok(()) } format => write_buffer_with_format(w, bytes, width, height, color, format), } } /// Encode this image with the provided encoder. pub fn write_with_encoder(&self, encoder: impl ImageEncoder) -> ImageResult<()> { dynamic_map!(self, |ref p| p.write_with_encoder(encoder)) } /// Saves the buffer to a file at the path specified. /// /// The image format is derived from the file extension. pub fn save(&self, path: Q) -> ImageResult<()> where Q: AsRef, { dynamic_map!(*self, |ref p| p.save(path)) } /// Saves the buffer to a file at the specified path in /// the specified format. /// /// See [`save_buffer_with_format`](fn.save_buffer_with_format.html) for /// supported types. pub fn save_with_format(&self, path: Q, format: ImageFormat) -> ImageResult<()> where Q: AsRef, { dynamic_map!(*self, |ref p| p.save_with_format(path, format)) } } impl From for DynamicImage { fn from(image: GrayImage) -> Self { DynamicImage::ImageLuma8(image) } } impl From for DynamicImage { fn from(image: GrayAlphaImage) -> Self { DynamicImage::ImageLumaA8(image) } } impl From for DynamicImage { fn from(image: RgbImage) -> Self { DynamicImage::ImageRgb8(image) } } impl From for DynamicImage { fn from(image: RgbaImage) -> Self { DynamicImage::ImageRgba8(image) } } impl From for DynamicImage { fn from(image: Gray16Image) -> Self { DynamicImage::ImageLuma16(image) } } impl From for DynamicImage { fn from(image: GrayAlpha16Image) -> Self { DynamicImage::ImageLumaA16(image) } } impl From for DynamicImage { fn from(image: Rgb16Image) -> Self { DynamicImage::ImageRgb16(image) } } impl From for DynamicImage { fn from(image: Rgba16Image) -> Self { DynamicImage::ImageRgba16(image) } } impl From for DynamicImage { fn from(image: Rgb32FImage) -> Self { DynamicImage::ImageRgb32F(image) } } impl From for DynamicImage { fn from(image: Rgba32FImage) -> Self { DynamicImage::ImageRgba32F(image) } } impl From, Vec>> for DynamicImage { fn from(image: ImageBuffer, Vec>) -> Self { DynamicImage::ImageRgb32F(image.convert()) } } impl From, Vec>> for DynamicImage { fn from(image: ImageBuffer, Vec>) -> Self { DynamicImage::ImageRgba32F(image.convert()) } } #[allow(deprecated)] impl GenericImageView for DynamicImage { type Pixel = color::Rgba; // TODO use f32 as default for best precision and unbounded color? fn dimensions(&self) -> (u32, u32) { dynamic_map!(*self, |ref p| p.dimensions()) } fn bounds(&self) -> (u32, u32, u32, u32) { dynamic_map!(*self, |ref p| p.bounds()) } fn get_pixel(&self, x: u32, y: u32) -> color::Rgba { dynamic_map!(*self, |ref p| p.get_pixel(x, y).to_rgba().into_color()) } } #[allow(deprecated)] impl GenericImage for DynamicImage { fn put_pixel(&mut self, x: u32, y: u32, pixel: color::Rgba) { match *self { DynamicImage::ImageLuma8(ref mut p) => p.put_pixel(x, y, pixel.to_luma()), DynamicImage::ImageLumaA8(ref mut p) => p.put_pixel(x, y, pixel.to_luma_alpha()), DynamicImage::ImageRgb8(ref mut p) => p.put_pixel(x, y, pixel.to_rgb()), DynamicImage::ImageRgba8(ref mut p) => p.put_pixel(x, y, pixel), DynamicImage::ImageLuma16(ref mut p) => p.put_pixel(x, y, pixel.to_luma().into_color()), DynamicImage::ImageLumaA16(ref mut p) => { p.put_pixel(x, y, pixel.to_luma_alpha().into_color()) } DynamicImage::ImageRgb16(ref mut p) => p.put_pixel(x, y, pixel.to_rgb().into_color()), DynamicImage::ImageRgba16(ref mut p) => p.put_pixel(x, y, pixel.into_color()), DynamicImage::ImageRgb32F(ref mut p) => p.put_pixel(x, y, pixel.to_rgb().into_color()), DynamicImage::ImageRgba32F(ref mut p) => p.put_pixel(x, y, pixel.into_color()), } } fn blend_pixel(&mut self, x: u32, y: u32, pixel: color::Rgba) { match *self { DynamicImage::ImageLuma8(ref mut p) => p.blend_pixel(x, y, pixel.to_luma()), DynamicImage::ImageLumaA8(ref mut p) => p.blend_pixel(x, y, pixel.to_luma_alpha()), DynamicImage::ImageRgb8(ref mut p) => p.blend_pixel(x, y, pixel.to_rgb()), DynamicImage::ImageRgba8(ref mut p) => p.blend_pixel(x, y, pixel), DynamicImage::ImageLuma16(ref mut p) => { p.blend_pixel(x, y, pixel.to_luma().into_color()) } DynamicImage::ImageLumaA16(ref mut p) => { p.blend_pixel(x, y, pixel.to_luma_alpha().into_color()) } DynamicImage::ImageRgb16(ref mut p) => p.blend_pixel(x, y, pixel.to_rgb().into_color()), DynamicImage::ImageRgba16(ref mut p) => p.blend_pixel(x, y, pixel.into_color()), DynamicImage::ImageRgb32F(ref mut p) => { p.blend_pixel(x, y, pixel.to_rgb().into_color()) } DynamicImage::ImageRgba32F(ref mut p) => p.blend_pixel(x, y, pixel.into_color()), } } /// Do not use is function: It is unimplemented! fn get_pixel_mut(&mut self, _: u32, _: u32) -> &mut color::Rgba { unimplemented!() } } impl Default for DynamicImage { fn default() -> Self { Self::ImageRgba8(Default::default()) } } /// Decodes an image and stores it into a dynamic image fn decoder_to_image<'a, I: ImageDecoder<'a>>(decoder: I) -> ImageResult { let (w, h) = decoder.dimensions(); let color_type = decoder.color_type(); let image = match color_type { color::ColorType::Rgb8 => { let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb8) } color::ColorType::Rgba8 => { let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba8) } color::ColorType::L8 => { let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLuma8) } color::ColorType::La8 => { let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLumaA8) } color::ColorType::Rgb16 => { let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb16) } color::ColorType::Rgba16 => { let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba16) } color::ColorType::Rgb32F => { let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb32F) } color::ColorType::Rgba32F => { let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba32F) } color::ColorType::L16 => { let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLuma16) } color::ColorType::La16 => { let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLumaA16) } }; match image { Some(image) => Ok(image), None => Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))), } } /// Open the image located at the path specified. /// The image's format is determined from the path's file extension. /// /// Try [`io::Reader`] for more advanced uses, including guessing the format based on the file's /// content before its path. /// /// [`io::Reader`]: io/struct.Reader.html pub fn open

(path: P) -> ImageResult where P: AsRef, { // thin wrapper function to strip generics before calling open_impl free_functions::open_impl(path.as_ref()) } /// Read a tuple containing the (width, height) of the image located at the specified path. /// This is faster than fully loading the image and then getting its dimensions. /// /// Try [`io::Reader`] for more advanced uses, including guessing the format based on the file's /// content before its path or manually supplying the format. /// /// [`io::Reader`]: io/struct.Reader.html pub fn image_dimensions

(path: P) -> ImageResult<(u32, u32)> where P: AsRef, { // thin wrapper function to strip generics before calling open_impl free_functions::image_dimensions_impl(path.as_ref()) } /// Saves the supplied buffer to a file at the path specified. /// /// The image format is derived from the file extension. The buffer is assumed to have /// the correct format according to the specified color type. /// /// This will lead to corrupted files if the buffer contains malformed data. Currently only /// jpeg, png, ico, pnm, bmp, exr and tiff files are supported. pub fn save_buffer

( path: P, buf: &[u8], width: u32, height: u32, color: color::ColorType, ) -> ImageResult<()> where P: AsRef, { // thin wrapper function to strip generics before calling save_buffer_impl free_functions::save_buffer_impl(path.as_ref(), buf, width, height, color) } /// Saves the supplied buffer to a file at the path specified /// in the specified format. /// /// The buffer is assumed to have the correct format according /// to the specified color type. /// This will lead to corrupted files if the buffer contains /// malformed data. Currently only jpeg, png, ico, bmp, exr and /// tiff files are supported. pub fn save_buffer_with_format

( path: P, buf: &[u8], width: u32, height: u32, color: color::ColorType, format: ImageFormat, ) -> ImageResult<()> where P: AsRef, { // thin wrapper function to strip generics free_functions::save_buffer_with_format_impl(path.as_ref(), buf, width, height, color, format) } /// Writes the supplied buffer to a writer in the specified format. /// /// The buffer is assumed to have the correct format according /// to the specified color type. /// This will lead to corrupted writers if the buffer contains /// malformed data. /// /// See [`ImageOutputFormat`](enum.ImageOutputFormat.html) for /// supported types. /// /// Assumes the writer is buffered. In most cases, /// you should wrap your writer in a `BufWriter` for best performance. pub fn write_buffer_with_format( buffered_writer: &mut W, buf: &[u8], width: u32, height: u32, color: color::ColorType, format: F, ) -> ImageResult<()> where W: Write + Seek, F: Into, { // thin wrapper function to strip generics free_functions::write_buffer_impl(buffered_writer, buf, width, height, color, format.into()) } /// Create a new image from a byte slice /// /// Makes an educated guess about the image format. /// TGA is not supported by this function. /// /// Try [`io::Reader`] for more advanced uses. /// /// [`io::Reader`]: io/struct.Reader.html pub fn load_from_memory(buffer: &[u8]) -> ImageResult { let format = free_functions::guess_format(buffer)?; load_from_memory_with_format(buffer, format) } /// Create a new image from a byte slice /// /// This is just a simple wrapper that constructs an `std::io::Cursor` around the buffer and then /// calls `load` with that reader. /// /// Try [`io::Reader`] for more advanced uses. /// /// [`load`]: fn.load.html /// [`io::Reader`]: io/struct.Reader.html #[inline(always)] pub fn load_from_memory_with_format(buf: &[u8], format: ImageFormat) -> ImageResult { let b = io::Cursor::new(buf); free_functions::load(b, format) } #[cfg(test)] mod bench { #[cfg(feature = "benchmarks")] use test; #[bench] #[cfg(feature = "benchmarks")] fn bench_conversion(b: &mut test::Bencher) { let a = super::DynamicImage::ImageRgb8(crate::ImageBuffer::new(1000, 1000)); b.iter(|| a.to_luma8()); b.bytes = 1000 * 1000 * 3 } } #[cfg(test)] mod test { #[test] fn test_empty_file() { assert!(super::load_from_memory(b"").is_err()); } #[cfg(feature = "jpeg")] #[test] fn image_dimensions() { let im_path = "./tests/images/jpg/progressive/cat.jpg"; let dims = super::image_dimensions(im_path).unwrap(); assert_eq!(dims, (320, 240)); } #[cfg(feature = "png")] #[test] fn open_16bpc_png() { let im_path = "./tests/images/png/16bpc/basn6a16.png"; let image = super::open(im_path).unwrap(); assert_eq!(image.color(), super::color::ColorType::Rgba16); } fn test_grayscale(mut img: super::DynamicImage, alpha_discarded: bool) { use crate::image::{GenericImage, GenericImageView}; img.put_pixel(0, 0, crate::color::Rgba([255, 0, 0, 100])); let expected_alpha = if alpha_discarded { 255 } else { 100 }; assert_eq!( img.grayscale().get_pixel(0, 0), crate::color::Rgba([54, 54, 54, expected_alpha]) ); } fn test_grayscale_alpha_discarded(img: super::DynamicImage) { test_grayscale(img, true); } fn test_grayscale_alpha_preserved(img: super::DynamicImage) { test_grayscale(img, false); } #[test] fn test_grayscale_luma8() { test_grayscale_alpha_discarded(super::DynamicImage::new_luma8(1, 1)); } #[test] fn test_grayscale_luma_a8() { test_grayscale_alpha_preserved(super::DynamicImage::new_luma_a8(1, 1)); } #[test] fn test_grayscale_rgb8() { test_grayscale_alpha_discarded(super::DynamicImage::new_rgb8(1, 1)); } #[test] fn test_grayscale_rgba8() { test_grayscale_alpha_preserved(super::DynamicImage::new_rgba8(1, 1)); } #[test] fn test_grayscale_luma16() { test_grayscale_alpha_discarded(super::DynamicImage::new_luma16(1, 1)); } #[test] fn test_grayscale_luma_a16() { test_grayscale_alpha_preserved(super::DynamicImage::new_luma_a16(1, 1)); } #[test] fn test_grayscale_rgb16() { test_grayscale_alpha_discarded(super::DynamicImage::new_rgb16(1, 1)); } #[test] fn test_grayscale_rgba16() { test_grayscale_alpha_preserved(super::DynamicImage::new_rgba16(1, 1)); } #[test] fn test_grayscale_rgb32f() { test_grayscale_alpha_discarded(super::DynamicImage::new_rgb32f(1, 1)); } #[test] fn test_grayscale_rgba32f() { test_grayscale_alpha_preserved(super::DynamicImage::new_rgba32f(1, 1)); } #[test] fn test_dynamic_image_default_implementation() { // Test that structs wrapping a DynamicImage are able to auto-derive the Default trait // ensures that DynamicImage implements Default (if it didn't, this would cause a compile error). #[derive(Default)] struct Foo { _image: super::DynamicImage, } } #[test] fn test_to_vecu8() { let _ = super::DynamicImage::new_luma8(1, 1).into_bytes(); let _ = super::DynamicImage::new_luma16(1, 1).into_bytes(); } #[test] fn issue_1705_can_turn_16bit_image_into_bytes() { let pixels = vec![65535u16; 64 * 64]; let img = super::ImageBuffer::from_vec(64, 64, pixels).unwrap(); let img = super::DynamicImage::ImageLuma16(img.into()); assert!(img.as_luma16().is_some()); let bytes: Vec = img.into_bytes(); assert_eq!(bytes, vec![0xFF; 64 * 64 * 2]); } } image-0.24.7/src/error.rs000064400000000000000000000412651046102023000132760ustar 00000000000000//! Contains detailed error representation. //! //! See the main [`ImageError`] which contains a variant for each specialized error type. The //! subtypes used in each variant are opaque by design. They can be roughly inspected through their //! respective `kind` methods which work similar to `std::io::Error::kind`. //! //! The error interface makes it possible to inspect the error of an underlying decoder or encoder, //! through the `Error::source` method. Note that this is not part of the stable interface and you //! may not rely on a particular error value for a particular operation. This means mainly that //! `image` does not promise to remain on a particular version of its underlying decoders but if //! you ensure to use the same version of the dependency (or at least of the error type) through //! external means then you could inspect the error type in slightly more detail. //! //! [`ImageError`]: enum.ImageError.html use std::error::Error; use std::{fmt, io}; use crate::color::ExtendedColorType; use crate::image::ImageFormat; /// The generic error type for image operations. /// /// This high level enum allows, by variant matching, a rough separation of concerns between /// underlying IO, the caller, format specifications, and the `image` implementation. #[derive(Debug)] pub enum ImageError { /// An error was encountered while decoding. /// /// This means that the input data did not conform to the specification of some image format, /// or that no format could be determined, or that it did not match format specific /// requirements set by the caller. Decoding(DecodingError), /// An error was encountered while encoding. /// /// The input image can not be encoded with the chosen format, for example because the /// specification has no representation for its color space or because a necessary conversion /// is ambiguous. In some cases it might also happen that the dimensions can not be used with /// the format. Encoding(EncodingError), /// An error was encountered in input arguments. /// /// This is a catch-all case for strictly internal operations such as scaling, conversions, /// etc. that involve no external format specifications. Parameter(ParameterError), /// Completing the operation would have required more resources than allowed. /// /// Errors of this type are limits set by the user or environment, *not* inherent in a specific /// format or operation that was executed. Limits(LimitError), /// An operation can not be completed by the chosen abstraction. /// /// This means that it might be possible for the operation to succeed in general but /// * it requires a disabled feature, /// * the implementation does not yet exist, or /// * no abstraction for a lower level could be found. Unsupported(UnsupportedError), /// An error occurred while interacting with the environment. IoError(io::Error), } /// The implementation for an operation was not provided. /// /// See the variant [`Unsupported`] for more documentation. /// /// [`Unsupported`]: enum.ImageError.html#variant.Unsupported #[derive(Debug)] pub struct UnsupportedError { format: ImageFormatHint, kind: UnsupportedErrorKind, } /// Details what feature is not supported. #[derive(Clone, Debug, Hash, PartialEq)] #[non_exhaustive] pub enum UnsupportedErrorKind { /// The required color type can not be handled. Color(ExtendedColorType), /// An image format is not supported. Format(ImageFormatHint), /// Some feature specified by string. /// This is discouraged and is likely to get deprecated (but not removed). GenericFeature(String), } /// An error was encountered while encoding an image. /// /// This is used as an opaque representation for the [`ImageError::Encoding`] variant. See its /// documentation for more information. /// /// [`ImageError::Encoding`]: enum.ImageError.html#variant.Encoding #[derive(Debug)] pub struct EncodingError { format: ImageFormatHint, underlying: Option>, } /// An error was encountered in inputs arguments. /// /// This is used as an opaque representation for the [`ImageError::Parameter`] variant. See its /// documentation for more information. /// /// [`ImageError::Parameter`]: enum.ImageError.html#variant.Parameter #[derive(Debug)] pub struct ParameterError { kind: ParameterErrorKind, underlying: Option>, } /// Details how a parameter is malformed. #[derive(Clone, Debug, Hash, PartialEq)] #[non_exhaustive] pub enum ParameterErrorKind { /// The dimensions passed are wrong. DimensionMismatch, /// Repeated an operation for which error that could not be cloned was emitted already. FailedAlready, /// A string describing the parameter. /// This is discouraged and is likely to get deprecated (but not removed). Generic(String), /// The end of the image has been reached. NoMoreData, } /// An error was encountered while decoding an image. /// /// This is used as an opaque representation for the [`ImageError::Decoding`] variant. See its /// documentation for more information. /// /// [`ImageError::Decoding`]: enum.ImageError.html#variant.Decoding #[derive(Debug)] pub struct DecodingError { format: ImageFormatHint, underlying: Option>, } /// Completing the operation would have required more resources than allowed. /// /// This is used as an opaque representation for the [`ImageError::Limits`] variant. See its /// documentation for more information. /// /// [`ImageError::Limits`]: enum.ImageError.html#variant.Limits #[derive(Debug)] pub struct LimitError { kind: LimitErrorKind, // do we need an underlying error? } /// Indicates the limit that prevented an operation from completing. /// /// Note that this enumeration is not exhaustive and may in the future be extended to provide more /// detailed information or to incorporate other resources types. #[derive(Clone, Debug, Hash, PartialEq, Eq)] #[non_exhaustive] #[allow(missing_copy_implementations)] // Might be non-Copy in the future. pub enum LimitErrorKind { /// The resulting image exceed dimension limits in either direction. DimensionError, /// The operation would have performed an allocation larger than allowed. InsufficientMemory, /// The specified strict limits are not supported for this operation Unsupported { /// The given limits limits: crate::io::Limits, /// The supported strict limits supported: crate::io::LimitSupport, }, } /// A best effort representation for image formats. #[derive(Clone, Debug, Hash, PartialEq)] #[non_exhaustive] pub enum ImageFormatHint { /// The format is known exactly. Exact(ImageFormat), /// The format can be identified by a name. Name(String), /// A common path extension for the format is known. PathExtension(std::path::PathBuf), /// The format is not known or could not be determined. Unknown, } impl UnsupportedError { /// Create an `UnsupportedError` for an image with details on the unsupported feature. /// /// If the operation was not connected to a particular image format then the hint may be /// `Unknown`. pub fn from_format_and_kind(format: ImageFormatHint, kind: UnsupportedErrorKind) -> Self { UnsupportedError { format, kind } } /// Returns the corresponding `UnsupportedErrorKind` of the error. pub fn kind(&self) -> UnsupportedErrorKind { self.kind.clone() } /// Returns the image format associated with this error. pub fn format_hint(&self) -> ImageFormatHint { self.format.clone() } } impl DecodingError { /// Create a `DecodingError` that stems from an arbitrary error of an underlying decoder. pub fn new(format: ImageFormatHint, err: impl Into>) -> Self { DecodingError { format, underlying: Some(err.into()), } } /// Create a `DecodingError` for an image format. /// /// The error will not contain any further information but is very easy to create. pub fn from_format_hint(format: ImageFormatHint) -> Self { DecodingError { format, underlying: None, } } /// Returns the image format associated with this error. pub fn format_hint(&self) -> ImageFormatHint { self.format.clone() } } impl EncodingError { /// Create an `EncodingError` that stems from an arbitrary error of an underlying encoder. pub fn new(format: ImageFormatHint, err: impl Into>) -> Self { EncodingError { format, underlying: Some(err.into()), } } /// Create an `EncodingError` for an image format. /// /// The error will not contain any further information but is very easy to create. pub fn from_format_hint(format: ImageFormatHint) -> Self { EncodingError { format, underlying: None, } } /// Return the image format associated with this error. pub fn format_hint(&self) -> ImageFormatHint { self.format.clone() } } impl ParameterError { /// Construct a `ParameterError` directly from a corresponding kind. pub fn from_kind(kind: ParameterErrorKind) -> Self { ParameterError { kind, underlying: None, } } /// Returns the corresponding `ParameterErrorKind` of the error. pub fn kind(&self) -> ParameterErrorKind { self.kind.clone() } } impl LimitError { /// Construct a generic `LimitError` directly from a corresponding kind. pub fn from_kind(kind: LimitErrorKind) -> Self { LimitError { kind } } /// Returns the corresponding `LimitErrorKind` of the error. pub fn kind(&self) -> LimitErrorKind { self.kind.clone() } } impl From for ImageError { fn from(err: io::Error) -> ImageError { ImageError::IoError(err) } } impl From for ImageFormatHint { fn from(format: ImageFormat) -> Self { ImageFormatHint::Exact(format) } } impl From<&'_ std::path::Path> for ImageFormatHint { fn from(path: &'_ std::path::Path) -> Self { match path.extension() { Some(ext) => ImageFormatHint::PathExtension(ext.into()), None => ImageFormatHint::Unknown, } } } impl From for UnsupportedError { fn from(hint: ImageFormatHint) -> Self { UnsupportedError { format: hint.clone(), kind: UnsupportedErrorKind::Format(hint), } } } /// Result of an image decoding/encoding process pub type ImageResult = Result; impl fmt::Display for ImageError { fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { match self { ImageError::IoError(err) => err.fmt(fmt), ImageError::Decoding(err) => err.fmt(fmt), ImageError::Encoding(err) => err.fmt(fmt), ImageError::Parameter(err) => err.fmt(fmt), ImageError::Limits(err) => err.fmt(fmt), ImageError::Unsupported(err) => err.fmt(fmt), } } } impl Error for ImageError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { ImageError::IoError(err) => err.source(), ImageError::Decoding(err) => err.source(), ImageError::Encoding(err) => err.source(), ImageError::Parameter(err) => err.source(), ImageError::Limits(err) => err.source(), ImageError::Unsupported(err) => err.source(), } } } impl fmt::Display for UnsupportedError { fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { match &self.kind { UnsupportedErrorKind::Format(ImageFormatHint::Unknown) => { write!(fmt, "The image format could not be determined",) } UnsupportedErrorKind::Format(format @ ImageFormatHint::PathExtension(_)) => write!( fmt, "The file extension {} was not recognized as an image format", format, ), UnsupportedErrorKind::Format(format) => { write!(fmt, "The image format {} is not supported", format,) } UnsupportedErrorKind::Color(color) => write!( fmt, "The decoder for {} does not support the color type `{:?}`", self.format, color, ), UnsupportedErrorKind::GenericFeature(message) => match &self.format { ImageFormatHint::Unknown => write!( fmt, "The decoder does not support the format feature {}", message, ), other => write!( fmt, "The decoder for {} does not support the format features {}", other, message, ), }, } } } impl Error for UnsupportedError {} impl fmt::Display for ParameterError { fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { match &self.kind { ParameterErrorKind::DimensionMismatch => write!( fmt, "The Image's dimensions are either too \ small or too large" ), ParameterErrorKind::FailedAlready => write!( fmt, "The end the image stream has been reached due to a previous error" ), ParameterErrorKind::Generic(message) => { write!(fmt, "The parameter is malformed: {}", message,) } ParameterErrorKind::NoMoreData => write!(fmt, "The end of the image has been reached",), }?; if let Some(underlying) = &self.underlying { write!(fmt, "\n{}", underlying)?; } Ok(()) } } impl Error for ParameterError { fn source(&self) -> Option<&(dyn Error + 'static)> { match &self.underlying { None => None, Some(source) => Some(&**source), } } } impl fmt::Display for EncodingError { fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { match &self.underlying { Some(underlying) => write!( fmt, "Format error encoding {}:\n{}", self.format, underlying, ), None => write!(fmt, "Format error encoding {}", self.format,), } } } impl Error for EncodingError { fn source(&self) -> Option<&(dyn Error + 'static)> { match &self.underlying { None => None, Some(source) => Some(&**source), } } } impl fmt::Display for DecodingError { fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { match &self.underlying { None => match self.format { ImageFormatHint::Unknown => write!(fmt, "Format error"), _ => write!(fmt, "Format error decoding {}", self.format), }, Some(underlying) => { write!(fmt, "Format error decoding {}: {}", self.format, underlying) } } } } impl Error for DecodingError { fn source(&self) -> Option<&(dyn Error + 'static)> { match &self.underlying { None => None, Some(source) => Some(&**source), } } } impl fmt::Display for LimitError { fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { match self.kind { LimitErrorKind::InsufficientMemory => write!(fmt, "Insufficient memory"), LimitErrorKind::DimensionError => write!(fmt, "Image is too large"), LimitErrorKind::Unsupported { .. } => { write!(fmt, "The following strict limits are specified but not supported by the opertation: ")?; Ok(()) } } } } impl Error for LimitError {} impl fmt::Display for ImageFormatHint { fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { match self { ImageFormatHint::Exact(format) => write!(fmt, "{:?}", format), ImageFormatHint::Name(name) => write!(fmt, "`{}`", name), ImageFormatHint::PathExtension(ext) => write!(fmt, "`.{:?}`", ext), ImageFormatHint::Unknown => write!(fmt, "`Unknown`"), } } } #[cfg(test)] mod tests { use super::*; use std::mem; #[allow(dead_code)] // This will fail to compile if the size of this type is large. const ASSERT_SMALLISH: usize = [0][(mem::size_of::() >= 200) as usize]; #[test] fn test_send_sync_stability() { fn assert_send_sync() {} assert_send_sync::(); } } image-0.24.7/src/flat.rs000064400000000000000000001756531046102023000131040ustar 00000000000000//! Image representations for ffi. //! //! # Usage //! //! Imagine you want to offer a very simple ffi interface: The caller provides an image buffer and //! your program creates a thumbnail from it and dumps that image as `png`. This module is designed //! to help you transition from raw memory data to Rust representation. //! //! ```no_run //! use std::ptr; //! use std::slice; //! use image::Rgb; //! use image::flat::{FlatSamples, SampleLayout}; //! use image::imageops::thumbnail; //! //! #[no_mangle] //! pub extern "C" fn store_rgb8_compressed( //! data: *const u8, len: usize, //! layout: *const SampleLayout //! ) //! -> bool //! { //! let samples = unsafe { slice::from_raw_parts(data, len) }; //! let layout = unsafe { ptr::read(layout) }; //! //! let buffer = FlatSamples { //! samples, //! layout, //! color_hint: None, //! }; //! //! let view = match buffer.as_view::>() { //! Err(_) => return false, // Invalid layout. //! Ok(view) => view, //! }; //! //! thumbnail(&view, 64, 64) //! .save("output.png") //! .map(|_| true) //! .unwrap_or_else(|_| false) //! } //! ``` //! use std::marker::PhantomData; use std::ops::{Deref, Index, IndexMut}; use std::{cmp, error, fmt}; use num_traits::Zero; use crate::color::ColorType; use crate::error::{ DecodingError, ImageError, ImageFormatHint, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{GenericImage, GenericImageView}; use crate::traits::Pixel; use crate::ImageBuffer; /// A flat buffer over a (multi channel) image. /// /// In contrast to `ImageBuffer`, this representation of a sample collection is much more lenient /// in the layout thereof. It also allows grouping by color planes instead of by pixel as long as /// the strides of each extent are constant. This struct itself has no invariants on the strides /// but not every possible configuration can be interpreted as a [`GenericImageView`] or /// [`GenericImage`]. The methods [`as_view`] and [`as_view_mut`] construct the actual implementors /// of these traits and perform necessary checks. To manually perform this and other layout checks /// use [`is_normal`] or [`has_aliased_samples`]. /// /// Instances can be constructed not only by hand. The buffer instances returned by library /// functions such as [`ImageBuffer::as_flat_samples`] guarantee that the conversion to a generic /// image or generic view succeeds. A very different constructor is [`with_monocolor`]. It uses a /// single pixel as the backing storage for an arbitrarily sized read-only raster by mapping each /// pixel to the same samples by setting some strides to `0`. /// /// [`GenericImage`]: ../trait.GenericImage.html /// [`GenericImageView`]: ../trait.GenericImageView.html /// [`ImageBuffer::as_flat_samples`]: ../struct.ImageBuffer.html#method.as_flat_samples /// [`is_normal`]: #method.is_normal /// [`has_aliased_samples`]: #method.has_aliased_samples /// [`as_view`]: #method.as_view /// [`as_view_mut`]: #method.as_view_mut /// [`with_monocolor`]: #method.with_monocolor #[derive(Clone, Debug)] pub struct FlatSamples { /// Underlying linear container holding sample values. pub samples: Buffer, /// A `repr(C)` description of the layout of buffer samples. pub layout: SampleLayout, /// Supplementary color information. /// /// You may keep this as `None` in most cases. This is NOT checked in `View` or other /// converters. It is intended mainly as a way for types that convert to this buffer type to /// attach their otherwise static color information. A dynamic image representation could /// however use this to resolve representational ambiguities such as the order of RGB channels. pub color_hint: Option, } /// A ffi compatible description of a sample buffer. #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct SampleLayout { /// The number of channels in the color representation of the image. pub channels: u8, /// Add this to an index to get to the sample in the next channel. pub channel_stride: usize, /// The width of the represented image. pub width: u32, /// Add this to an index to get to the next sample in x-direction. pub width_stride: usize, /// The height of the represented image. pub height: u32, /// Add this to an index to get to the next sample in y-direction. pub height_stride: usize, } /// Helper struct for an unnamed (stride, length) pair. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] struct Dim(usize, usize); impl SampleLayout { /// Describe a row-major image packed in all directions. /// /// The resulting will surely be `NormalForm::RowMajorPacked`. It can therefore be converted to /// safely to an `ImageBuffer` with a large enough underlying buffer. /// /// ``` /// # use image::flat::{NormalForm, SampleLayout}; /// let layout = SampleLayout::row_major_packed(3, 640, 480); /// assert!(layout.is_normal(NormalForm::RowMajorPacked)); /// ``` /// /// # Panics /// /// On platforms where `usize` has the same size as `u32` this panics when the resulting stride /// in the `height` direction would be larger than `usize::max_value()`. On other platforms /// where it can surely accommodate `u8::max_value() * u32::max_value(), this can never happen. pub fn row_major_packed(channels: u8, width: u32, height: u32) -> Self { let height_stride = (channels as usize).checked_mul(width as usize).expect( "Row major packed image can not be described because it does not fit into memory", ); SampleLayout { channels, channel_stride: 1, width, width_stride: channels as usize, height, height_stride, } } /// Describe a column-major image packed in all directions. /// /// The resulting will surely be `NormalForm::ColumnMajorPacked`. This is not particularly /// useful for conversion but can be used to describe such a buffer without pitfalls. /// /// ``` /// # use image::flat::{NormalForm, SampleLayout}; /// let layout = SampleLayout::column_major_packed(3, 640, 480); /// assert!(layout.is_normal(NormalForm::ColumnMajorPacked)); /// ``` /// /// # Panics /// /// On platforms where `usize` has the same size as `u32` this panics when the resulting stride /// in the `width` direction would be larger than `usize::max_value()`. On other platforms /// where it can surely accommodate `u8::max_value() * u32::max_value(), this can never happen. pub fn column_major_packed(channels: u8, width: u32, height: u32) -> Self { let width_stride = (channels as usize).checked_mul(height as usize).expect( "Column major packed image can not be described because it does not fit into memory", ); SampleLayout { channels, channel_stride: 1, height, height_stride: channels as usize, width, width_stride, } } /// Get the strides for indexing matrix-like `[(c, w, h)]`. /// /// For a row-major layout with grouped samples, this tuple is strictly /// increasing. pub fn strides_cwh(&self) -> (usize, usize, usize) { (self.channel_stride, self.width_stride, self.height_stride) } /// Get the dimensions `(channels, width, height)`. /// /// The interface is optimized for use with `strides_cwh` instead. The channel extent will be /// before width and height. pub fn extents(&self) -> (usize, usize, usize) { ( self.channels as usize, self.width as usize, self.height as usize, ) } /// Tuple of bounds in the order of coordinate inputs. /// /// This function should be used whenever working with image coordinates opposed to buffer /// coordinates. The only difference compared to `extents` is the output type. pub fn bounds(&self) -> (u8, u32, u32) { (self.channels, self.width, self.height) } /// Get the minimum length of a buffer such that all in-bounds samples have valid indices. /// /// This method will allow zero strides, allowing compact representations of monochrome images. /// To check that no aliasing occurs, try `check_alias_invariants`. For compact images (no /// aliasing and no unindexed samples) this is `width*height*channels`. But for both of the /// other cases, the reasoning is slightly more involved. /// /// # Explanation /// /// Note that there is a difference between `min_length` and the index of the sample /// 'one-past-the-end`. This is due to strides that may be larger than the dimension below. /// /// ## Example with holes /// /// Let's look at an example of a grayscale image with /// * `width_stride = 1` /// * `width = 2` /// * `height_stride = 3` /// * `height = 2` /// /// ```text /// | x x | x x m | $ /// min_length m ^ /// ^ one-past-the-end $ /// ``` /// /// The difference is also extreme for empty images with large strides. The one-past-the-end /// sample index is still as large as the largest of these strides while `min_length = 0`. /// /// ## Example with aliasing /// /// The concept gets even more important when you allow samples to alias each other. Here we /// have the buffer of a small grayscale image where this is the case, this time we will first /// show the buffer and then the individual rows below. /// /// * `width_stride = 1` /// * `width = 3` /// * `height_stride = 2` /// * `height = 2` /// /// ```text /// 1 2 3 4 5 m /// |1 2 3| row one /// |3 4 5| row two /// ^ m min_length /// ^ ??? one-past-the-end /// ``` /// /// This time 'one-past-the-end' is not even simply the largest stride times the extent of its /// dimension. That still points inside the image because `height*height_stride = 4` but also /// `index_of(1, 2) = 4`. pub fn min_length(&self) -> Option { if self.width == 0 || self.height == 0 || self.channels == 0 { return Some(0); } self.index(self.channels - 1, self.width - 1, self.height - 1) .and_then(|idx| idx.checked_add(1)) } /// Check if a buffer of length `len` is large enough. pub fn fits(&self, len: usize) -> bool { self.min_length().map(|min| len >= min).unwrap_or(false) } /// The extents of this array, in order of increasing strides. fn increasing_stride_dims(&self) -> [Dim; 3] { // Order extents by strides, then check that each is less equal than the next stride. let mut grouped: [Dim; 3] = [ Dim(self.channel_stride, self.channels as usize), Dim(self.width_stride, self.width as usize), Dim(self.height_stride, self.height as usize), ]; grouped.sort(); let (min_dim, mid_dim, max_dim) = (grouped[0], grouped[1], grouped[2]); assert!(min_dim.stride() <= mid_dim.stride() && mid_dim.stride() <= max_dim.stride()); grouped } /// If there are any samples aliasing each other. /// /// If this is not the case, it would always be safe to allow mutable access to two different /// samples at the same time. Otherwise, this operation would need additional checks. When one /// dimension overflows `usize` with its stride we also consider this aliasing. pub fn has_aliased_samples(&self) -> bool { let grouped = self.increasing_stride_dims(); let (min_dim, mid_dim, max_dim) = (grouped[0], grouped[1], grouped[2]); let min_size = match min_dim.checked_len() { None => return true, Some(size) => size, }; let mid_size = match mid_dim.checked_len() { None => return true, Some(size) => size, }; match max_dim.checked_len() { None => return true, Some(_) => (), // Only want to know this didn't overflow. }; // Each higher dimension must walk over all of one lower dimension. min_size > mid_dim.stride() || mid_size > max_dim.stride() } /// Check if a buffer fulfills the requirements of a normal form. /// /// Certain conversions have preconditions on the structure of the sample buffer that are not /// captured (by design) by the type system. These are then checked before the conversion. Such /// checks can all be done in constant time and will not inspect the buffer content. You can /// perform these checks yourself when the conversion is not required at this moment but maybe /// still performed later. pub fn is_normal(&self, form: NormalForm) -> bool { if self.has_aliased_samples() { return false; } if form >= NormalForm::PixelPacked && self.channel_stride != 1 { return false; } if form >= NormalForm::ImagePacked { // has aliased already checked for overflows. let grouped = self.increasing_stride_dims(); let (min_dim, mid_dim, max_dim) = (grouped[0], grouped[1], grouped[2]); if 1 != min_dim.stride() { return false; } if min_dim.len() != mid_dim.stride() { return false; } if mid_dim.len() != max_dim.stride() { return false; } } if form >= NormalForm::RowMajorPacked { if self.width_stride != self.channels as usize { return false; } if self.width as usize * self.width_stride != self.height_stride { return false; } } if form >= NormalForm::ColumnMajorPacked { if self.height_stride != self.channels as usize { return false; } if self.height as usize * self.height_stride != self.width_stride { return false; } } true } /// Check that the pixel and the channel index are in bounds. /// /// An in-bound coordinate does not yet guarantee that the corresponding calculation of a /// buffer index does not overflow. However, if such a buffer large enough to hold all samples /// actually exists in memory, this property of course follows. pub fn in_bounds(&self, channel: u8, x: u32, y: u32) -> bool { channel < self.channels && x < self.width && y < self.height } /// Resolve the index of a particular sample. /// /// `None` if the index is outside the bounds or does not fit into a `usize`. pub fn index(&self, channel: u8, x: u32, y: u32) -> Option { if !self.in_bounds(channel, x, y) { return None; } self.index_ignoring_bounds(channel as usize, x as usize, y as usize) } /// Get the theoretical position of sample (channel, x, y). /// /// The 'check' is for overflow during index calculation, not that it is contained in the /// image. Two samples may return the same index, even when one of them is out of bounds. This /// happens when all strides are `0`, i.e. the image is an arbitrarily large monochrome image. pub fn index_ignoring_bounds(&self, channel: usize, x: usize, y: usize) -> Option { let idx_c = channel.checked_mul(self.channel_stride); let idx_x = x.checked_mul(self.width_stride); let idx_y = y.checked_mul(self.height_stride); let (idx_c, idx_x, idx_y) = match (idx_c, idx_x, idx_y) { (Some(idx_c), Some(idx_x), Some(idx_y)) => (idx_c, idx_x, idx_y), _ => return None, }; Some(0usize) .and_then(|b| b.checked_add(idx_c)) .and_then(|b| b.checked_add(idx_x)) .and_then(|b| b.checked_add(idx_y)) } /// Get an index provided it is inbouds. /// /// Assumes that the image is backed by some sufficiently large buffer. Then computation can /// not overflow as we could represent the maximum coordinate. Since overflow is defined either /// way, this method can not be unsafe. pub fn in_bounds_index(&self, c: u8, x: u32, y: u32) -> usize { let (c_stride, x_stride, y_stride) = self.strides_cwh(); (y as usize * y_stride) + (x as usize * x_stride) + (c as usize * c_stride) } /// Shrink the image to the minimum of current and given extents. /// /// This does not modify the strides, so that the resulting sample buffer may have holes /// created by the shrinking operation. Shrinking could also lead to an non-aliasing image when /// samples had aliased each other before. pub fn shrink_to(&mut self, channels: u8, width: u32, height: u32) { self.channels = self.channels.min(channels); self.width = self.width.min(width); self.height = self.height.min(height); } } impl Dim { fn stride(self) -> usize { self.0 } /// Length of this dimension in memory. fn checked_len(self) -> Option { self.0.checked_mul(self.1) } fn len(self) -> usize { self.0 * self.1 } } impl FlatSamples { /// Get the strides for indexing matrix-like `[(c, w, h)]`. /// /// For a row-major layout with grouped samples, this tuple is strictly /// increasing. pub fn strides_cwh(&self) -> (usize, usize, usize) { self.layout.strides_cwh() } /// Get the dimensions `(channels, width, height)`. /// /// The interface is optimized for use with `strides_cwh` instead. The channel extent will be /// before width and height. pub fn extents(&self) -> (usize, usize, usize) { self.layout.extents() } /// Tuple of bounds in the order of coordinate inputs. /// /// This function should be used whenever working with image coordinates opposed to buffer /// coordinates. The only difference compared to `extents` is the output type. pub fn bounds(&self) -> (u8, u32, u32) { self.layout.bounds() } /// Get a reference based version. pub fn as_ref(&self) -> FlatSamples<&[T]> where Buffer: AsRef<[T]>, { FlatSamples { samples: self.samples.as_ref(), layout: self.layout, color_hint: self.color_hint, } } /// Get a mutable reference based version. pub fn as_mut(&mut self) -> FlatSamples<&mut [T]> where Buffer: AsMut<[T]>, { FlatSamples { samples: self.samples.as_mut(), layout: self.layout, color_hint: self.color_hint, } } /// Copy the data into an owned vector. pub fn to_vec(&self) -> FlatSamples> where T: Clone, Buffer: AsRef<[T]>, { FlatSamples { samples: self.samples.as_ref().to_vec(), layout: self.layout, color_hint: self.color_hint, } } /// Get a reference to a single sample. /// /// This more restrictive than the method based on `std::ops::Index` but guarantees to properly /// check all bounds and not panic as long as `Buffer::as_ref` does not do so. /// /// ``` /// # use image::{RgbImage}; /// let flat = RgbImage::new(480, 640).into_flat_samples(); /// /// // Get the blue channel at (10, 10). /// assert!(flat.get_sample(1, 10, 10).is_some()); /// /// // There is no alpha channel. /// assert!(flat.get_sample(3, 10, 10).is_none()); /// ``` /// /// For cases where a special buffer does not provide `AsRef<[T]>`, consider encapsulating /// bounds checks with `min_length` in a type similar to `View`. Then you may use /// `in_bounds_index` as a small speedup over the index calculation of this method which relies /// on `index_ignoring_bounds` since it can not have a-priori knowledge that the sample /// coordinate is in fact backed by any memory buffer. pub fn get_sample(&self, channel: u8, x: u32, y: u32) -> Option<&T> where Buffer: AsRef<[T]>, { self.index(channel, x, y) .and_then(|idx| self.samples.as_ref().get(idx)) } /// Get a mutable reference to a single sample. /// /// This more restrictive than the method based on `std::ops::IndexMut` but guarantees to /// properly check all bounds and not panic as long as `Buffer::as_ref` does not do so. /// Contrary to conversion to `ViewMut`, this does not require that samples are packed since it /// does not need to convert samples to a color representation. /// /// **WARNING**: Note that of course samples may alias, so that the mutable reference returned /// here can in fact modify more than the coordinate in the argument. /// /// ``` /// # use image::{RgbImage}; /// let mut flat = RgbImage::new(480, 640).into_flat_samples(); /// /// // Assign some new color to the blue channel at (10, 10). /// *flat.get_mut_sample(1, 10, 10).unwrap() = 255; /// /// // There is no alpha channel. /// assert!(flat.get_mut_sample(3, 10, 10).is_none()); /// ``` /// /// For cases where a special buffer does not provide `AsRef<[T]>`, consider encapsulating /// bounds checks with `min_length` in a type similar to `View`. Then you may use /// `in_bounds_index` as a small speedup over the index calculation of this method which relies /// on `index_ignoring_bounds` since it can not have a-priori knowledge that the sample /// coordinate is in fact backed by any memory buffer. pub fn get_mut_sample(&mut self, channel: u8, x: u32, y: u32) -> Option<&mut T> where Buffer: AsMut<[T]>, { match self.index(channel, x, y) { None => None, Some(idx) => self.samples.as_mut().get_mut(idx), } } /// View this buffer as an image over some type of pixel. /// /// This first ensures that all in-bounds coordinates refer to valid indices in the sample /// buffer. It also checks that the specified pixel format expects the same number of channels /// that are present in this buffer. Neither are larger nor a smaller number will be accepted. /// There is no automatic conversion. pub fn as_view

(&self) -> Result, Error> where P: Pixel, Buffer: AsRef<[P::Subpixel]>, { if self.layout.channels != P::CHANNEL_COUNT { return Err(Error::ChannelCountMismatch( self.layout.channels, P::CHANNEL_COUNT, )); } let as_ref = self.samples.as_ref(); if !self.layout.fits(as_ref.len()) { return Err(Error::TooLarge); } Ok(View { inner: FlatSamples { samples: as_ref, layout: self.layout, color_hint: self.color_hint, }, phantom: PhantomData, }) } /// View this buffer but keep mutability at a sample level. /// /// This is similar to `as_view` but subtly different from `as_view_mut`. The resulting type /// can be used as a `GenericImage` with the same prior invariants needed as for `as_view`. /// It can not be used as a mutable `GenericImage` but does not need channels to be packed in /// their pixel representation. /// /// This first ensures that all in-bounds coordinates refer to valid indices in the sample /// buffer. It also checks that the specified pixel format expects the same number of channels /// that are present in this buffer. Neither are larger nor a smaller number will be accepted. /// There is no automatic conversion. /// /// **WARNING**: Note that of course samples may alias, so that the mutable reference returned /// for one sample can in fact modify other samples as well. Sometimes exactly this is /// intended. pub fn as_view_with_mut_samples

(&mut self) -> Result, Error> where P: Pixel, Buffer: AsMut<[P::Subpixel]>, { if self.layout.channels != P::CHANNEL_COUNT { return Err(Error::ChannelCountMismatch( self.layout.channels, P::CHANNEL_COUNT, )); } let as_mut = self.samples.as_mut(); if !self.layout.fits(as_mut.len()) { return Err(Error::TooLarge); } Ok(View { inner: FlatSamples { samples: as_mut, layout: self.layout, color_hint: self.color_hint, }, phantom: PhantomData, }) } /// Interpret this buffer as a mutable image. /// /// To succeed, the pixels in this buffer may not alias each other and the samples of each /// pixel must be packed (i.e. `channel_stride` is `1`). The number of channels must be /// consistent with the channel count expected by the pixel format. /// /// This is similar to an `ImageBuffer` except it is a temporary view that is not normalized as /// strongly. To get an owning version, consider copying the data into an `ImageBuffer`. This /// provides many more operations, is possibly faster (if not you may want to open an issue) is /// generally polished. You can also try to convert this buffer inline, see /// `ImageBuffer::from_raw`. pub fn as_view_mut

(&mut self) -> Result, Error> where P: Pixel, Buffer: AsMut<[P::Subpixel]>, { if !self.layout.is_normal(NormalForm::PixelPacked) { return Err(Error::NormalFormRequired(NormalForm::PixelPacked)); } if self.layout.channels != P::CHANNEL_COUNT { return Err(Error::ChannelCountMismatch( self.layout.channels, P::CHANNEL_COUNT, )); } let as_mut = self.samples.as_mut(); if !self.layout.fits(as_mut.len()) { return Err(Error::TooLarge); } Ok(ViewMut { inner: FlatSamples { samples: as_mut, layout: self.layout, color_hint: self.color_hint, }, phantom: PhantomData, }) } /// View the samples as a slice. /// /// The slice is not limited to the region of the image and not all sample indices are valid /// indices into this buffer. See `image_mut_slice` as an alternative. pub fn as_slice(&self) -> &[T] where Buffer: AsRef<[T]>, { self.samples.as_ref() } /// View the samples as a slice. /// /// The slice is not limited to the region of the image and not all sample indices are valid /// indices into this buffer. See `image_mut_slice` as an alternative. pub fn as_mut_slice(&mut self) -> &mut [T] where Buffer: AsMut<[T]>, { self.samples.as_mut() } /// Return the portion of the buffer that holds sample values. /// /// This may fail when the coordinates in this image are either out-of-bounds of the underlying /// buffer or can not be represented. Note that the slice may have holes that do not correspond /// to any sample in the image represented by it. pub fn image_slice(&self) -> Option<&[T]> where Buffer: AsRef<[T]>, { let min_length = match self.min_length() { None => return None, Some(index) => index, }; let slice = self.samples.as_ref(); if slice.len() < min_length { return None; } Some(&slice[..min_length]) } /// Mutable portion of the buffer that holds sample values. pub fn image_mut_slice(&mut self) -> Option<&mut [T]> where Buffer: AsMut<[T]>, { let min_length = match self.min_length() { None => return None, Some(index) => index, }; let slice = self.samples.as_mut(); if slice.len() < min_length { return None; } Some(&mut slice[..min_length]) } /// Move the data into an image buffer. /// /// This does **not** convert the sample layout. The buffer needs to be in packed row-major form /// before calling this function. In case of an error, returns the buffer again so that it does /// not release any allocation. pub fn try_into_buffer

(self) -> Result, (Error, Self)> where P: Pixel + 'static, P::Subpixel: 'static, Buffer: Deref, { if !self.is_normal(NormalForm::RowMajorPacked) { return Err((Error::NormalFormRequired(NormalForm::RowMajorPacked), self)); } if self.layout.channels != P::CHANNEL_COUNT { return Err(( Error::ChannelCountMismatch(self.layout.channels, P::CHANNEL_COUNT), self, )); } if !self.fits(self.samples.deref().len()) { return Err((Error::TooLarge, self)); } Ok( ImageBuffer::from_raw(self.layout.width, self.layout.height, self.samples) .unwrap_or_else(|| { panic!("Preconditions should have been ensured before conversion") }), ) } /// Get the minimum length of a buffer such that all in-bounds samples have valid indices. /// /// This method will allow zero strides, allowing compact representations of monochrome images. /// To check that no aliasing occurs, try `check_alias_invariants`. For compact images (no /// aliasing and no unindexed samples) this is `width*height*channels`. But for both of the /// other cases, the reasoning is slightly more involved. /// /// # Explanation /// /// Note that there is a difference between `min_length` and the index of the sample /// 'one-past-the-end`. This is due to strides that may be larger than the dimension below. /// /// ## Example with holes /// /// Let's look at an example of a grayscale image with /// * `width_stride = 1` /// * `width = 2` /// * `height_stride = 3` /// * `height = 2` /// /// ```text /// | x x | x x m | $ /// min_length m ^ /// ^ one-past-the-end $ /// ``` /// /// The difference is also extreme for empty images with large strides. The one-past-the-end /// sample index is still as large as the largest of these strides while `min_length = 0`. /// /// ## Example with aliasing /// /// The concept gets even more important when you allow samples to alias each other. Here we /// have the buffer of a small grayscale image where this is the case, this time we will first /// show the buffer and then the individual rows below. /// /// * `width_stride = 1` /// * `width = 3` /// * `height_stride = 2` /// * `height = 2` /// /// ```text /// 1 2 3 4 5 m /// |1 2 3| row one /// |3 4 5| row two /// ^ m min_length /// ^ ??? one-past-the-end /// ``` /// /// This time 'one-past-the-end' is not even simply the largest stride times the extent of its /// dimension. That still points inside the image because `height*height_stride = 4` but also /// `index_of(1, 2) = 4`. pub fn min_length(&self) -> Option { self.layout.min_length() } /// Check if a buffer of length `len` is large enough. pub fn fits(&self, len: usize) -> bool { self.layout.fits(len) } /// If there are any samples aliasing each other. /// /// If this is not the case, it would always be safe to allow mutable access to two different /// samples at the same time. Otherwise, this operation would need additional checks. When one /// dimension overflows `usize` with its stride we also consider this aliasing. pub fn has_aliased_samples(&self) -> bool { self.layout.has_aliased_samples() } /// Check if a buffer fulfills the requirements of a normal form. /// /// Certain conversions have preconditions on the structure of the sample buffer that are not /// captured (by design) by the type system. These are then checked before the conversion. Such /// checks can all be done in constant time and will not inspect the buffer content. You can /// perform these checks yourself when the conversion is not required at this moment but maybe /// still performed later. pub fn is_normal(&self, form: NormalForm) -> bool { self.layout.is_normal(form) } /// Check that the pixel and the channel index are in bounds. /// /// An in-bound coordinate does not yet guarantee that the corresponding calculation of a /// buffer index does not overflow. However, if such a buffer large enough to hold all samples /// actually exists in memory, this property of course follows. pub fn in_bounds(&self, channel: u8, x: u32, y: u32) -> bool { self.layout.in_bounds(channel, x, y) } /// Resolve the index of a particular sample. /// /// `None` if the index is outside the bounds or does not fit into a `usize`. pub fn index(&self, channel: u8, x: u32, y: u32) -> Option { self.layout.index(channel, x, y) } /// Get the theoretical position of sample (x, y, channel). /// /// The 'check' is for overflow during index calculation, not that it is contained in the /// image. Two samples may return the same index, even when one of them is out of bounds. This /// happens when all strides are `0`, i.e. the image is an arbitrarily large monochrome image. pub fn index_ignoring_bounds(&self, channel: usize, x: usize, y: usize) -> Option { self.layout.index_ignoring_bounds(channel, x, y) } /// Get an index provided it is inbouds. /// /// Assumes that the image is backed by some sufficiently large buffer. Then computation can /// not overflow as we could represent the maximum coordinate. Since overflow is defined either /// way, this method can not be unsafe. pub fn in_bounds_index(&self, channel: u8, x: u32, y: u32) -> usize { self.layout.in_bounds_index(channel, x, y) } /// Shrink the image to the minimum of current and given extents. /// /// This does not modify the strides, so that the resulting sample buffer may have holes /// created by the shrinking operation. Shrinking could also lead to an non-aliasing image when /// samples had aliased each other before. pub fn shrink_to(&mut self, channels: u8, width: u32, height: u32) { self.layout.shrink_to(channels, width, height) } } impl<'buf, Subpixel> FlatSamples<&'buf [Subpixel]> { /// Create a monocolor image from a single pixel. /// /// This can be used as a very cheap source of a `GenericImageView` with an arbitrary number of /// pixels of a single color, without any dynamic allocation. /// /// ## Examples /// /// ``` /// # fn paint_something(_: T) {} /// use image::{flat::FlatSamples, GenericImage, RgbImage, Rgb}; /// /// let background = Rgb([20, 20, 20]); /// let bg = FlatSamples::with_monocolor(&background, 200, 200);; /// /// let mut image = RgbImage::new(200, 200); /// paint_something(&mut image); /// /// // Reset the canvas /// image.copy_from(&bg.as_view().unwrap(), 0, 0); /// ``` pub fn with_monocolor

(pixel: &'buf P, width: u32, height: u32) -> Self where P: Pixel, Subpixel: crate::Primitive, { FlatSamples { samples: pixel.channels(), layout: SampleLayout { channels: P::CHANNEL_COUNT, channel_stride: 1, width, width_stride: 0, height, height_stride: 0, }, // TODO this value is never set. It should be set in all places where the Pixel type implements PixelWithColorType color_hint: None, } } } /// A flat buffer that can be used as an image view. /// /// This is a nearly trivial wrapper around a buffer but at least sanitizes by checking the buffer /// length first and constraining the pixel type. /// /// Note that this does not eliminate panics as the `AsRef<[T]>` implementation of `Buffer` may be /// unreliable, i.e. return different buffers at different times. This of course is a non-issue for /// all common collections where the bounds check once must be enough. /// /// # Inner invariants /// /// * For all indices inside bounds, the corresponding index is valid in the buffer /// * `P::channel_count()` agrees with `self.inner.layout.channels` /// #[derive(Clone, Debug)] pub struct View where Buffer: AsRef<[P::Subpixel]>, { inner: FlatSamples, phantom: PhantomData

, } /// A mutable owning version of a flat buffer. /// /// While this wraps a buffer similar to `ImageBuffer`, this is mostly intended as a utility. The /// library endorsed normalized representation is still `ImageBuffer`. Also, the implementation of /// `AsMut<[P::Subpixel]>` must always yield the same buffer. Therefore there is no public way to /// construct this with an owning buffer. /// /// # Inner invariants /// /// * For all indices inside bounds, the corresponding index is valid in the buffer /// * There is no aliasing of samples /// * The samples are packed, i.e. `self.inner.layout.sample_stride == 1` /// * `P::channel_count()` agrees with `self.inner.layout.channels` /// #[derive(Clone, Debug)] pub struct ViewMut where Buffer: AsMut<[P::Subpixel]>, { inner: FlatSamples, phantom: PhantomData

, } /// Denotes invalid flat sample buffers when trying to convert to stricter types. /// /// The biggest use case being `ImageBuffer` which expects closely packed /// samples in a row major matrix representation. But this error type may be /// resused for other import functions. A more versatile user may also try to /// correct the underlying representation depending on the error variant. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Error { /// The represented image was too large. /// /// The optional value denotes a possibly accepted maximal bound. TooLarge, /// The represented image can not use this representation. /// /// Has an additional value of the normalized form that would be accepted. NormalFormRequired(NormalForm), /// The color format did not match the channel count. /// /// In some cases you might be able to fix this by lowering the reported pixel count of the /// buffer without touching the strides. /// /// In very special circumstances you *may* do the opposite. This is **VERY** dangerous but not /// directly memory unsafe although that will likely alias pixels. One scenario is when you /// want to construct an `Rgba` image but have only 3 bytes per pixel and for some reason don't /// care about the value of the alpha channel even though you need `Rgba`. ChannelCountMismatch(u8, u8), /// Deprecated - ChannelCountMismatch is used instead WrongColor(ColorType), } /// Different normal forms of buffers. /// /// A normal form is an unaliased buffer with some additional constraints. The `ÌmageBuffer` uses /// row major form with packed samples. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum NormalForm { /// No pixel aliases another. /// /// Unaliased also guarantees that all index calculations in the image bounds using /// `dim_index*dim_stride` (such as `x*width_stride + y*height_stride`) do not overflow. Unaliased, /// At least pixels are packed. /// /// Images of these types can wrap `[T]`-slices into the standard color types. This is a /// precondition for `GenericImage` which requires by-reference access to pixels. PixelPacked, /// All samples are packed. /// /// This is orthogonal to `PixelPacked`. It requires that there are no holes in the image but /// it is not necessary that the pixel samples themselves are adjacent. An example of this /// behaviour is a planar image layout. ImagePacked, /// The samples are in row-major form and all samples are packed. /// /// In addition to `PixelPacked` and `ImagePacked` this also asserts that the pixel matrix is /// in row-major form. RowMajorPacked, /// The samples are in column-major form and all samples are packed. /// /// In addition to `PixelPacked` and `ImagePacked` this also asserts that the pixel matrix is /// in column-major form. ColumnMajorPacked, } impl View where Buffer: AsRef<[P::Subpixel]>, { /// Take out the sample buffer. /// /// Gives up the normalization invariants on the buffer format. pub fn into_inner(self) -> FlatSamples { self.inner } /// Get a reference on the inner sample descriptor. /// /// There is no mutable counterpart as modifying the buffer format, including strides and /// lengths, could invalidate the accessibility invariants of the `View`. It is not specified /// if the inner buffer is the same as the buffer of the image from which this view was /// created. It might have been truncated as an optimization. pub fn flat(&self) -> &FlatSamples { &self.inner } /// Get a reference on the inner buffer. /// /// There is no mutable counter part since it is not intended to allow you to reassign the /// buffer or otherwise change its size or properties. pub fn samples(&self) -> &Buffer { &self.inner.samples } /// Get a reference to a selected subpixel if it is in-bounds. /// /// This method will return `None` when the sample is out-of-bounds. All errors that could /// occur due to overflow have been eliminated while construction the `View`. pub fn get_sample(&self, channel: u8, x: u32, y: u32) -> Option<&P::Subpixel> { if !self.inner.in_bounds(channel, x, y) { return None; } let index = self.inner.in_bounds_index(channel, x, y); // Should always be `Some(_)` but checking is more costly. self.samples().as_ref().get(index) } /// Get a mutable reference to a selected subpixel if it is in-bounds. /// /// This is relevant only when constructed with `FlatSamples::as_view_with_mut_samples`. This /// method will return `None` when the sample is out-of-bounds. All errors that could occur due /// to overflow have been eliminated while construction the `View`. /// /// **WARNING**: Note that of course samples may alias, so that the mutable reference returned /// here can in fact modify more than the coordinate in the argument. pub fn get_mut_sample(&mut self, channel: u8, x: u32, y: u32) -> Option<&mut P::Subpixel> where Buffer: AsMut<[P::Subpixel]>, { if !self.inner.in_bounds(channel, x, y) { return None; } let index = self.inner.in_bounds_index(channel, x, y); // Should always be `Some(_)` but checking is more costly. self.inner.samples.as_mut().get_mut(index) } /// Get the minimum length of a buffer such that all in-bounds samples have valid indices. /// /// See `FlatSamples::min_length`. This method will always succeed. pub fn min_length(&self) -> usize { self.inner.min_length().unwrap() } /// Return the portion of the buffer that holds sample values. /// /// While this can not fail–the validity of all coordinates has been validated during the /// conversion from `FlatSamples`–the resulting slice may still contain holes. pub fn image_slice(&self) -> &[P::Subpixel] { &self.samples().as_ref()[..self.min_length()] } /// Return the mutable portion of the buffer that holds sample values. /// /// This is relevant only when constructed with `FlatSamples::as_view_with_mut_samples`. While /// this can not fail–the validity of all coordinates has been validated during the conversion /// from `FlatSamples`–the resulting slice may still contain holes. pub fn image_mut_slice(&mut self) -> &mut [P::Subpixel] where Buffer: AsMut<[P::Subpixel]>, { let min_length = self.min_length(); &mut self.inner.samples.as_mut()[..min_length] } /// Shrink the inner image. /// /// The new dimensions will be the minimum of the previous dimensions. Since the set of /// in-bounds pixels afterwards is a subset of the current ones, this is allowed on a `View`. /// Note that you can not change the number of channels as an intrinsic property of `P`. pub fn shrink_to(&mut self, width: u32, height: u32) { let channels = self.inner.layout.channels; self.inner.shrink_to(channels, width, height) } /// Try to convert this into an image with mutable pixels. /// /// The resulting image implements `GenericImage` in addition to `GenericImageView`. While this /// has mutable samples, it does not enforce that pixel can not alias and that samples are /// packed enough for a mutable pixel reference. This is slightly cheaper than the chain /// `self.into_inner().as_view_mut()` and keeps the `View` alive on failure. /// /// ``` /// # use image::RgbImage; /// # use image::Rgb; /// let mut buffer = RgbImage::new(480, 640).into_flat_samples(); /// let view = buffer.as_view_with_mut_samples::>().unwrap(); /// /// // Inspect some pixels, … /// /// // Doesn't fail because it was originally an `RgbImage`. /// let view_mut = view.try_upgrade().unwrap(); /// ``` pub fn try_upgrade(self) -> Result, (Error, Self)> where Buffer: AsMut<[P::Subpixel]>, { if !self.inner.is_normal(NormalForm::PixelPacked) { return Err((Error::NormalFormRequired(NormalForm::PixelPacked), self)); } // No length check or channel count check required, all the same. Ok(ViewMut { inner: self.inner, phantom: PhantomData, }) } } impl ViewMut where Buffer: AsMut<[P::Subpixel]>, { /// Take out the sample buffer. /// /// Gives up the normalization invariants on the buffer format. pub fn into_inner(self) -> FlatSamples { self.inner } /// Get a reference on the sample buffer descriptor. /// /// There is no mutable counterpart as modifying the buffer format, including strides and /// lengths, could invalidate the accessibility invariants of the `View`. It is not specified /// if the inner buffer is the same as the buffer of the image from which this view was /// created. It might have been truncated as an optimization. pub fn flat(&self) -> &FlatSamples { &self.inner } /// Get a reference on the inner buffer. /// /// There is no mutable counter part since it is not intended to allow you to reassign the /// buffer or otherwise change its size or properties. However, its contents can be accessed /// mutable through a slice with `image_mut_slice`. pub fn samples(&self) -> &Buffer { &self.inner.samples } /// Get the minimum length of a buffer such that all in-bounds samples have valid indices. /// /// See `FlatSamples::min_length`. This method will always succeed. pub fn min_length(&self) -> usize { self.inner.min_length().unwrap() } /// Get a reference to a selected subpixel. /// /// This method will return `None` when the sample is out-of-bounds. All errors that could /// occur due to overflow have been eliminated while construction the `View`. pub fn get_sample(&self, channel: u8, x: u32, y: u32) -> Option<&P::Subpixel> where Buffer: AsRef<[P::Subpixel]>, { if !self.inner.in_bounds(channel, x, y) { return None; } let index = self.inner.in_bounds_index(channel, x, y); // Should always be `Some(_)` but checking is more costly. self.samples().as_ref().get(index) } /// Get a mutable reference to a selected sample. /// /// This method will return `None` when the sample is out-of-bounds. All errors that could /// occur due to overflow have been eliminated while construction the `View`. pub fn get_mut_sample(&mut self, channel: u8, x: u32, y: u32) -> Option<&mut P::Subpixel> { if !self.inner.in_bounds(channel, x, y) { return None; } let index = self.inner.in_bounds_index(channel, x, y); // Should always be `Some(_)` but checking is more costly. self.inner.samples.as_mut().get_mut(index) } /// Return the portion of the buffer that holds sample values. /// /// While this can not fail–the validity of all coordinates has been validated during the /// conversion from `FlatSamples`–the resulting slice may still contain holes. pub fn image_slice(&self) -> &[P::Subpixel] where Buffer: AsRef<[P::Subpixel]>, { &self.inner.samples.as_ref()[..self.min_length()] } /// Return the mutable buffer that holds sample values. pub fn image_mut_slice(&mut self) -> &mut [P::Subpixel] { let length = self.min_length(); &mut self.inner.samples.as_mut()[..length] } /// Shrink the inner image. /// /// The new dimensions will be the minimum of the previous dimensions. Since the set of /// in-bounds pixels afterwards is a subset of the current ones, this is allowed on a `View`. /// Note that you can not change the number of channels as an intrinsic property of `P`. pub fn shrink_to(&mut self, width: u32, height: u32) { let channels = self.inner.layout.channels; self.inner.shrink_to(channels, width, height) } } // The out-of-bounds panic for single sample access similar to `slice::index`. #[inline(never)] #[cold] fn panic_cwh_out_of_bounds( (c, x, y): (u8, u32, u32), bounds: (u8, u32, u32), strides: (usize, usize, usize), ) -> ! { panic!( "Sample coordinates {:?} out of sample matrix bounds {:?} with strides {:?}", (c, x, y), bounds, strides ) } // The out-of-bounds panic for pixel access similar to `slice::index`. #[inline(never)] #[cold] fn panic_pixel_out_of_bounds((x, y): (u32, u32), bounds: (u32, u32)) -> ! { panic!("Image index {:?} out of bounds {:?}", (x, y), bounds) } impl Index<(u8, u32, u32)> for FlatSamples where Buffer: Index, { type Output = Buffer::Output; /// Return a reference to a single sample at specified coordinates. /// /// # Panics /// /// When the coordinates are out of bounds or the index calculation fails. fn index(&self, (c, x, y): (u8, u32, u32)) -> &Self::Output { let bounds = self.bounds(); let strides = self.strides_cwh(); let index = self .index(c, x, y) .unwrap_or_else(|| panic_cwh_out_of_bounds((c, x, y), bounds, strides)); &self.samples[index] } } impl IndexMut<(u8, u32, u32)> for FlatSamples where Buffer: IndexMut, { /// Return a mutable reference to a single sample at specified coordinates. /// /// # Panics /// /// When the coordinates are out of bounds or the index calculation fails. fn index_mut(&mut self, (c, x, y): (u8, u32, u32)) -> &mut Self::Output { let bounds = self.bounds(); let strides = self.strides_cwh(); let index = self .index(c, x, y) .unwrap_or_else(|| panic_cwh_out_of_bounds((c, x, y), bounds, strides)); &mut self.samples[index] } } impl GenericImageView for View where Buffer: AsRef<[P::Subpixel]>, { type Pixel = P; fn dimensions(&self) -> (u32, u32) { (self.inner.layout.width, self.inner.layout.height) } fn bounds(&self) -> (u32, u32, u32, u32) { let (w, h) = self.dimensions(); (0, w, 0, h) } fn in_bounds(&self, x: u32, y: u32) -> bool { let (w, h) = self.dimensions(); x < w && y < h } fn get_pixel(&self, x: u32, y: u32) -> Self::Pixel { if !self.inner.in_bounds(0, x, y) { panic_pixel_out_of_bounds((x, y), self.dimensions()) } let image = self.inner.samples.as_ref(); let base_index = self.inner.in_bounds_index(0, x, y); let channels = P::CHANNEL_COUNT as usize; let mut buffer = [Zero::zero(); 256]; buffer .iter_mut() .enumerate() .take(channels) .for_each(|(c, to)| { let index = base_index + c * self.inner.layout.channel_stride; *to = image[index]; }); *P::from_slice(&buffer[..channels]) } } impl GenericImageView for ViewMut where Buffer: AsMut<[P::Subpixel]> + AsRef<[P::Subpixel]>, { type Pixel = P; fn dimensions(&self) -> (u32, u32) { (self.inner.layout.width, self.inner.layout.height) } fn bounds(&self) -> (u32, u32, u32, u32) { let (w, h) = self.dimensions(); (0, w, 0, h) } fn in_bounds(&self, x: u32, y: u32) -> bool { let (w, h) = self.dimensions(); x < w && y < h } fn get_pixel(&self, x: u32, y: u32) -> Self::Pixel { if !self.inner.in_bounds(0, x, y) { panic_pixel_out_of_bounds((x, y), self.dimensions()) } let image = self.inner.samples.as_ref(); let base_index = self.inner.in_bounds_index(0, x, y); let channels = P::CHANNEL_COUNT as usize; let mut buffer = [Zero::zero(); 256]; buffer .iter_mut() .enumerate() .take(channels) .for_each(|(c, to)| { let index = base_index + c * self.inner.layout.channel_stride; *to = image[index]; }); *P::from_slice(&buffer[..channels]) } } impl GenericImage for ViewMut where Buffer: AsMut<[P::Subpixel]> + AsRef<[P::Subpixel]>, { fn get_pixel_mut(&mut self, x: u32, y: u32) -> &mut Self::Pixel { if !self.inner.in_bounds(0, x, y) { panic_pixel_out_of_bounds((x, y), self.dimensions()) } let base_index = self.inner.in_bounds_index(0, x, y); let channel_count =

::CHANNEL_COUNT as usize; let pixel_range = base_index..base_index + channel_count; P::from_slice_mut(&mut self.inner.samples.as_mut()[pixel_range]) } #[allow(deprecated)] fn put_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) { *self.get_pixel_mut(x, y) = pixel; } #[allow(deprecated)] fn blend_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) { self.get_pixel_mut(x, y).blend(&pixel); } } impl From for ImageError { fn from(error: Error) -> ImageError { #[derive(Debug)] struct NormalFormRequiredError(NormalForm); impl fmt::Display for NormalFormRequiredError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Required sample buffer in normal form {:?}", self.0) } } impl error::Error for NormalFormRequiredError {} match error { Error::TooLarge => ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, )), Error::NormalFormRequired(form) => ImageError::Decoding(DecodingError::new( ImageFormatHint::Unknown, NormalFormRequiredError(form), )), Error::ChannelCountMismatch(_lc, _pc) => ImageError::Parameter( ParameterError::from_kind(ParameterErrorKind::DimensionMismatch), ), Error::WrongColor(color) => { ImageError::Unsupported(UnsupportedError::from_format_and_kind( ImageFormatHint::Unknown, UnsupportedErrorKind::Color(color.into()), )) } } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::TooLarge => write!(f, "The layout is too large"), Error::NormalFormRequired(form) => write!( f, "The layout needs to {}", match form { NormalForm::ColumnMajorPacked => "be packed and in column major form", NormalForm::ImagePacked => "be fully packed", NormalForm::PixelPacked => "have packed pixels", NormalForm::RowMajorPacked => "be packed and in row major form", NormalForm::Unaliased => "not have any aliasing channels", } ), Error::ChannelCountMismatch(layout_channels, pixel_channels) => write!( f, "The channel count of the chosen pixel (={}) does agree with the layout (={})", pixel_channels, layout_channels ), Error::WrongColor(color) => write!( f, "The chosen color type does not match the hint {:?}", color ), } } } impl error::Error for Error {} impl PartialOrd for NormalForm { /// Compares the logical preconditions. /// /// `a < b` if the normal form `a` has less preconditions than `b`. fn partial_cmp(&self, other: &Self) -> Option { match (*self, *other) { (NormalForm::Unaliased, NormalForm::Unaliased) => Some(cmp::Ordering::Equal), (NormalForm::PixelPacked, NormalForm::PixelPacked) => Some(cmp::Ordering::Equal), (NormalForm::ImagePacked, NormalForm::ImagePacked) => Some(cmp::Ordering::Equal), (NormalForm::RowMajorPacked, NormalForm::RowMajorPacked) => Some(cmp::Ordering::Equal), (NormalForm::ColumnMajorPacked, NormalForm::ColumnMajorPacked) => { Some(cmp::Ordering::Equal) } (NormalForm::Unaliased, _) => Some(cmp::Ordering::Less), (_, NormalForm::Unaliased) => Some(cmp::Ordering::Greater), (NormalForm::PixelPacked, NormalForm::ColumnMajorPacked) => Some(cmp::Ordering::Less), (NormalForm::PixelPacked, NormalForm::RowMajorPacked) => Some(cmp::Ordering::Less), (NormalForm::RowMajorPacked, NormalForm::PixelPacked) => Some(cmp::Ordering::Greater), (NormalForm::ColumnMajorPacked, NormalForm::PixelPacked) => { Some(cmp::Ordering::Greater) } (NormalForm::ImagePacked, NormalForm::ColumnMajorPacked) => Some(cmp::Ordering::Less), (NormalForm::ImagePacked, NormalForm::RowMajorPacked) => Some(cmp::Ordering::Less), (NormalForm::RowMajorPacked, NormalForm::ImagePacked) => Some(cmp::Ordering::Greater), (NormalForm::ColumnMajorPacked, NormalForm::ImagePacked) => { Some(cmp::Ordering::Greater) } (NormalForm::ImagePacked, NormalForm::PixelPacked) => None, (NormalForm::PixelPacked, NormalForm::ImagePacked) => None, (NormalForm::RowMajorPacked, NormalForm::ColumnMajorPacked) => None, (NormalForm::ColumnMajorPacked, NormalForm::RowMajorPacked) => None, } } } #[cfg(test)] mod tests { use super::*; use crate::buffer_::GrayAlphaImage; use crate::color::{LumaA, Rgb}; #[test] fn aliasing_view() { let buffer = FlatSamples { samples: &[42], layout: SampleLayout { channels: 3, channel_stride: 0, width: 100, width_stride: 0, height: 100, height_stride: 0, }, color_hint: None, }; let view = buffer.as_view::>().expect("This is a valid view"); let pixel_count = view .pixels() .inspect(|pixel| assert!(pixel.2 == Rgb([42, 42, 42]))) .count(); assert_eq!(pixel_count, 100 * 100); } #[test] fn mutable_view() { let mut buffer = FlatSamples { samples: [0; 18], layout: SampleLayout { channels: 2, channel_stride: 1, width: 3, width_stride: 2, height: 3, height_stride: 6, }, color_hint: None, }; { let mut view = buffer .as_view_mut::>() .expect("This should be a valid mutable buffer"); assert_eq!(view.dimensions(), (3, 3)); #[allow(deprecated)] for i in 0..9 { *view.get_pixel_mut(i % 3, i / 3) = LumaA([2 * i as u16, 2 * i as u16 + 1]); } } buffer .samples .iter() .enumerate() .for_each(|(idx, sample)| assert_eq!(idx, *sample as usize)); } #[test] fn normal_forms() { assert!(FlatSamples { samples: [0u8; 0], layout: SampleLayout { channels: 2, channel_stride: 1, width: 3, width_stride: 9, height: 3, height_stride: 28, }, color_hint: None, } .is_normal(NormalForm::PixelPacked)); assert!(FlatSamples { samples: [0u8; 0], layout: SampleLayout { channels: 2, channel_stride: 8, width: 4, width_stride: 1, height: 2, height_stride: 4, }, color_hint: None, } .is_normal(NormalForm::ImagePacked)); assert!(FlatSamples { samples: [0u8; 0], layout: SampleLayout { channels: 2, channel_stride: 1, width: 4, width_stride: 2, height: 2, height_stride: 8, }, color_hint: None, } .is_normal(NormalForm::RowMajorPacked)); assert!(FlatSamples { samples: [0u8; 0], layout: SampleLayout { channels: 2, channel_stride: 1, width: 4, width_stride: 4, height: 2, height_stride: 2, }, color_hint: None, } .is_normal(NormalForm::ColumnMajorPacked)); } #[test] fn image_buffer_conversion() { let expected_layout = SampleLayout { channels: 2, channel_stride: 1, width: 4, width_stride: 2, height: 2, height_stride: 8, }; let initial = GrayAlphaImage::new(expected_layout.width, expected_layout.height); let buffer = initial.into_flat_samples(); assert_eq!(buffer.layout, expected_layout); let _: GrayAlphaImage = buffer.try_into_buffer().unwrap_or_else(|(error, _)| { panic!("Expected buffer to be convertible but {:?}", error) }); } } image-0.24.7/src/image.rs000064400000000000000000001740741046102023000132340ustar 00000000000000#![allow(clippy::too_many_arguments)] use std::convert::TryFrom; use std::ffi::OsStr; use std::io; use std::io::Read; use std::ops::{Deref, DerefMut}; use std::path::Path; use std::usize; use crate::color::{ColorType, ExtendedColorType}; use crate::error::{ ImageError, ImageFormatHint, ImageResult, LimitError, LimitErrorKind, ParameterError, ParameterErrorKind, }; use crate::math::Rect; use crate::traits::Pixel; use crate::ImageBuffer; use crate::animation::Frames; #[cfg(feature = "pnm")] use crate::codecs::pnm::PnmSubtype; /// An enumeration of supported image formats. /// Not all formats support both encoding and decoding. #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] #[non_exhaustive] pub enum ImageFormat { /// An Image in PNG Format Png, /// An Image in JPEG Format Jpeg, /// An Image in GIF Format Gif, /// An Image in WEBP Format WebP, /// An Image in general PNM Format Pnm, /// An Image in TIFF Format Tiff, /// An Image in TGA Format Tga, /// An Image in DDS Format Dds, /// An Image in BMP Format Bmp, /// An Image in ICO Format Ico, /// An Image in Radiance HDR Format Hdr, /// An Image in OpenEXR Format OpenExr, /// An Image in farbfeld Format Farbfeld, /// An Image in AVIF format. Avif, /// An Image in QOI format. Qoi, } impl ImageFormat { /// Return the image format specified by a path's file extension. /// /// # Example /// /// ``` /// use image::ImageFormat; /// /// let format = ImageFormat::from_extension("jpg"); /// assert_eq!(format, Some(ImageFormat::Jpeg)); /// ``` #[inline] pub fn from_extension(ext: S) -> Option where S: AsRef, { // thin wrapper function to strip generics fn inner(ext: &OsStr) -> Option { let ext = ext.to_str()?.to_ascii_lowercase(); Some(match ext.as_str() { "avif" => ImageFormat::Avif, "jpg" | "jpeg" => ImageFormat::Jpeg, "png" => ImageFormat::Png, "gif" => ImageFormat::Gif, "webp" => ImageFormat::WebP, "tif" | "tiff" => ImageFormat::Tiff, "tga" => ImageFormat::Tga, "dds" => ImageFormat::Dds, "bmp" => ImageFormat::Bmp, "ico" => ImageFormat::Ico, "hdr" => ImageFormat::Hdr, "exr" => ImageFormat::OpenExr, "pbm" | "pam" | "ppm" | "pgm" => ImageFormat::Pnm, "ff" | "farbfeld" => ImageFormat::Farbfeld, "qoi" => ImageFormat::Qoi, _ => return None, }) } inner(ext.as_ref()) } /// Return the image format specified by the path's file extension. /// /// # Example /// /// ``` /// use image::ImageFormat; /// /// let format = ImageFormat::from_path("images/ferris.png")?; /// assert_eq!(format, ImageFormat::Png); /// /// # Ok::<(), image::error::ImageError>(()) /// ``` #[inline] pub fn from_path

(path: P) -> ImageResult where P: AsRef, { // thin wrapper function to strip generics fn inner(path: &Path) -> ImageResult { let exact_ext = path.extension(); exact_ext .and_then(ImageFormat::from_extension) .ok_or_else(|| { let format_hint = match exact_ext { None => ImageFormatHint::Unknown, Some(os) => ImageFormatHint::PathExtension(os.into()), }; ImageError::Unsupported(format_hint.into()) }) } inner(path.as_ref()) } /// Return the image format specified by a MIME type. /// /// # Example /// /// ``` /// use image::ImageFormat; /// /// let format = ImageFormat::from_mime_type("image/png").unwrap(); /// assert_eq!(format, ImageFormat::Png); /// ``` pub fn from_mime_type(mime_type: M) -> Option where M: AsRef, { match mime_type.as_ref() { "image/avif" => Some(ImageFormat::Avif), "image/jpeg" => Some(ImageFormat::Jpeg), "image/png" => Some(ImageFormat::Png), "image/gif" => Some(ImageFormat::Gif), "image/webp" => Some(ImageFormat::WebP), "image/tiff" => Some(ImageFormat::Tiff), "image/x-targa" | "image/x-tga" => Some(ImageFormat::Tga), "image/vnd-ms.dds" => Some(ImageFormat::Dds), "image/bmp" => Some(ImageFormat::Bmp), "image/x-icon" => Some(ImageFormat::Ico), "image/vnd.radiance" => Some(ImageFormat::Hdr), "image/x-exr" => Some(ImageFormat::OpenExr), "image/x-portable-bitmap" | "image/x-portable-graymap" | "image/x-portable-pixmap" | "image/x-portable-anymap" => Some(ImageFormat::Pnm), // Qoi's MIME type is being worked on. // See: https://github.com/phoboslab/qoi/issues/167 "image/x-qoi" => Some(ImageFormat::Qoi), _ => None, } } /// Return the MIME type for this image format or "application/octet-stream" if no MIME type /// exists for the format. /// /// Some notes on a few of the MIME types: /// /// - The portable anymap format has a separate MIME type for the pixmap, graymap and bitmap /// formats, but this method returns the general "image/x-portable-anymap" MIME type. /// - The Targa format has two common MIME types, "image/x-targa" and "image/x-tga"; this /// method returns "image/x-targa" for that format. /// - The QOI MIME type is still a work in progress. This method returns "image/x-qoi" for /// that format. /// /// # Example /// /// ``` /// use image::ImageFormat; /// /// let mime_type = ImageFormat::Png.to_mime_type(); /// assert_eq!(mime_type, "image/png"); /// ``` pub fn to_mime_type(&self) -> &'static str { match self { ImageFormat::Avif => "image/avif", ImageFormat::Jpeg => "image/jpeg", ImageFormat::Png => "image/png", ImageFormat::Gif => "image/gif", ImageFormat::WebP => "image/webp", ImageFormat::Tiff => "image/tiff", // the targa MIME type has two options, but this one seems to be used more ImageFormat::Tga => "image/x-targa", ImageFormat::Dds => "image/vnd-ms.dds", ImageFormat::Bmp => "image/bmp", ImageFormat::Ico => "image/x-icon", ImageFormat::Hdr => "image/vnd.radiance", ImageFormat::OpenExr => "image/x-exr", // return the most general MIME type ImageFormat::Pnm => "image/x-portable-anymap", // Qoi's MIME type is being worked on. // See: https://github.com/phoboslab/qoi/issues/167 ImageFormat::Qoi => "image/x-qoi", // farbfield's MIME type taken from https://www.wikidata.org/wiki/Q28206109 ImageFormat::Farbfeld => "application/octet-stream", } } /// Return if the ImageFormat can be decoded by the lib. #[inline] pub fn can_read(&self) -> bool { // Needs to be updated once a new variant's decoder is added to free_functions.rs::load match self { ImageFormat::Png => true, ImageFormat::Gif => true, ImageFormat::Jpeg => true, ImageFormat::WebP => true, ImageFormat::Tiff => true, ImageFormat::Tga => true, ImageFormat::Dds => false, ImageFormat::Bmp => true, ImageFormat::Ico => true, ImageFormat::Hdr => true, ImageFormat::OpenExr => true, ImageFormat::Pnm => true, ImageFormat::Farbfeld => true, ImageFormat::Avif => true, ImageFormat::Qoi => true, } } /// Return if the ImageFormat can be encoded by the lib. #[inline] pub fn can_write(&self) -> bool { // Needs to be updated once a new variant's encoder is added to free_functions.rs::save_buffer_with_format_impl match self { ImageFormat::Gif => true, ImageFormat::Ico => true, ImageFormat::Jpeg => true, ImageFormat::Png => true, ImageFormat::Bmp => true, ImageFormat::Tiff => true, ImageFormat::Tga => true, ImageFormat::Pnm => true, ImageFormat::Farbfeld => true, ImageFormat::Avif => true, ImageFormat::WebP => true, ImageFormat::Hdr => false, ImageFormat::OpenExr => true, ImageFormat::Dds => false, ImageFormat::Qoi => true, } } /// Return a list of applicable extensions for this format. /// /// All currently recognized image formats specify at least on extension but for future /// compatibility you should not rely on this fact. The list may be empty if the format has no /// recognized file representation, for example in case it is used as a purely transient memory /// format. /// /// The method name `extensions` remains reserved for introducing another method in the future /// that yields a slice of `OsStr` which is blocked by several features of const evaluation. pub fn extensions_str(self) -> &'static [&'static str] { match self { ImageFormat::Png => &["png"], ImageFormat::Jpeg => &["jpg", "jpeg"], ImageFormat::Gif => &["gif"], ImageFormat::WebP => &["webp"], ImageFormat::Pnm => &["pbm", "pam", "ppm", "pgm"], ImageFormat::Tiff => &["tiff", "tif"], ImageFormat::Tga => &["tga"], ImageFormat::Dds => &["dds"], ImageFormat::Bmp => &["bmp"], ImageFormat::Ico => &["ico"], ImageFormat::Hdr => &["hdr"], ImageFormat::OpenExr => &["exr"], ImageFormat::Farbfeld => &["ff"], // According to: https://aomediacodec.github.io/av1-avif/#mime-registration ImageFormat::Avif => &["avif"], ImageFormat::Qoi => &["qoi"], } } } /// An enumeration of supported image formats for encoding. #[derive(Clone, PartialEq, Eq, Debug)] #[non_exhaustive] pub enum ImageOutputFormat { #[cfg(feature = "png")] /// An Image in PNG Format Png, #[cfg(feature = "jpeg")] /// An Image in JPEG Format with specified quality, up to 100 Jpeg(u8), #[cfg(feature = "pnm")] /// An Image in one of the PNM Formats Pnm(PnmSubtype), #[cfg(feature = "gif")] /// An Image in GIF Format Gif, #[cfg(feature = "ico")] /// An Image in ICO Format Ico, #[cfg(feature = "bmp")] /// An Image in BMP Format Bmp, #[cfg(feature = "farbfeld")] /// An Image in farbfeld Format Farbfeld, #[cfg(feature = "tga")] /// An Image in TGA Format Tga, #[cfg(feature = "exr")] /// An Image in OpenEXR Format OpenExr, #[cfg(feature = "tiff")] /// An Image in TIFF Format Tiff, #[cfg(feature = "avif-encoder")] /// An image in AVIF Format Avif, #[cfg(feature = "qoi")] /// An image in QOI Format Qoi, #[cfg(feature = "webp-encoder")] /// An image in WebP Format. WebP, /// A value for signalling an error: An unsupported format was requested // Note: When TryFrom is stabilized, this value should not be needed, and // a TryInto should be used instead of an Into. Unsupported(String), } impl From for ImageOutputFormat { fn from(fmt: ImageFormat) -> Self { match fmt { #[cfg(feature = "png")] ImageFormat::Png => ImageOutputFormat::Png, #[cfg(feature = "jpeg")] ImageFormat::Jpeg => ImageOutputFormat::Jpeg(75), #[cfg(feature = "pnm")] ImageFormat::Pnm => ImageOutputFormat::Pnm(PnmSubtype::ArbitraryMap), #[cfg(feature = "gif")] ImageFormat::Gif => ImageOutputFormat::Gif, #[cfg(feature = "ico")] ImageFormat::Ico => ImageOutputFormat::Ico, #[cfg(feature = "bmp")] ImageFormat::Bmp => ImageOutputFormat::Bmp, #[cfg(feature = "farbfeld")] ImageFormat::Farbfeld => ImageOutputFormat::Farbfeld, #[cfg(feature = "tga")] ImageFormat::Tga => ImageOutputFormat::Tga, #[cfg(feature = "exr")] ImageFormat::OpenExr => ImageOutputFormat::OpenExr, #[cfg(feature = "tiff")] ImageFormat::Tiff => ImageOutputFormat::Tiff, #[cfg(feature = "avif-encoder")] ImageFormat::Avif => ImageOutputFormat::Avif, #[cfg(feature = "webp-encoder")] ImageFormat::WebP => ImageOutputFormat::WebP, #[cfg(feature = "qoi")] ImageFormat::Qoi => ImageOutputFormat::Qoi, f => ImageOutputFormat::Unsupported(format!("{:?}", f)), } } } // This struct manages buffering associated with implementing `Read` and `Seek` on decoders that can // must decode ranges of bytes at a time. #[allow(dead_code)] // When no image formats that use it are enabled pub(crate) struct ImageReadBuffer { scanline_bytes: usize, buffer: Vec, consumed: usize, total_bytes: u64, offset: u64, } impl ImageReadBuffer { /// Create a new ImageReadBuffer. /// /// Panics if scanline_bytes doesn't fit into a usize, because that would mean reading anything /// from the image would take more RAM than the entire virtual address space. In other words, /// actually using this struct would instantly OOM so just get it out of the way now. #[allow(dead_code)] // When no image formats that use it are enabled pub(crate) fn new(scanline_bytes: u64, total_bytes: u64) -> Self { Self { scanline_bytes: usize::try_from(scanline_bytes).unwrap(), buffer: Vec::new(), consumed: 0, total_bytes, offset: 0, } } #[allow(dead_code)] // When no image formats that use it are enabled pub(crate) fn read(&mut self, buf: &mut [u8], mut read_scanline: F) -> io::Result where F: FnMut(&mut [u8]) -> io::Result, { if self.buffer.len() == self.consumed { if self.offset == self.total_bytes { return Ok(0); } else if buf.len() >= self.scanline_bytes { // If there is nothing buffered and the user requested a full scanline worth of // data, skip buffering. let bytes_read = read_scanline(&mut buf[..self.scanline_bytes])?; self.offset += u64::try_from(bytes_read).unwrap(); return Ok(bytes_read); } else { // Lazily allocate buffer the first time that read is called with a buffer smaller // than the scanline size. if self.buffer.is_empty() { self.buffer.resize(self.scanline_bytes, 0); } self.consumed = 0; let bytes_read = read_scanline(&mut self.buffer[..])?; self.buffer.resize(bytes_read, 0); self.offset += u64::try_from(bytes_read).unwrap(); assert!(bytes_read == self.scanline_bytes || self.offset == self.total_bytes); } } // Finally, copy bytes into output buffer. let bytes_buffered = self.buffer.len() - self.consumed; if bytes_buffered > buf.len() { buf.copy_from_slice(&self.buffer[self.consumed..][..buf.len()]); self.consumed += buf.len(); Ok(buf.len()) } else { buf[..bytes_buffered].copy_from_slice(&self.buffer[self.consumed..][..bytes_buffered]); self.consumed = self.buffer.len(); Ok(bytes_buffered) } } } /// Decodes a specific region of the image, represented by the rectangle /// starting from ```x``` and ```y``` and having ```length``` and ```width``` #[allow(dead_code)] // When no image formats that use it are enabled pub(crate) fn load_rect<'a, D, F, F1, F2, E>( x: u32, y: u32, width: u32, height: u32, buf: &mut [u8], progress_callback: F, decoder: &mut D, mut seek_scanline: F1, mut read_scanline: F2, ) -> ImageResult<()> where D: ImageDecoder<'a>, F: Fn(Progress), F1: FnMut(&mut D, u64) -> io::Result<()>, F2: FnMut(&mut D, &mut [u8]) -> Result<(), E>, ImageError: From, { let (x, y, width, height) = ( u64::from(x), u64::from(y), u64::from(width), u64::from(height), ); let dimensions = decoder.dimensions(); let bytes_per_pixel = u64::from(decoder.color_type().bytes_per_pixel()); let row_bytes = bytes_per_pixel * u64::from(dimensions.0); let scanline_bytes = decoder.scanline_bytes(); let total_bytes = width * height * bytes_per_pixel; if buf.len() < usize::try_from(total_bytes).unwrap_or(usize::max_value()) { panic!( "output buffer too short\n expected `{}`, provided `{}`", total_bytes, buf.len() ); } let mut bytes_read = 0u64; let mut current_scanline = 0; let mut tmp = Vec::new(); let mut tmp_scanline = None; { // Read a range of the image starting from byte number `start` and continuing until byte // number `end`. Updates `current_scanline` and `bytes_read` appropriately. let mut read_image_range = |mut start: u64, end: u64| -> ImageResult<()> { // If the first scanline we need is already stored in the temporary buffer, then handle // it first. let target_scanline = start / scanline_bytes; if tmp_scanline == Some(target_scanline) { let position = target_scanline * scanline_bytes; let offset = start.saturating_sub(position); let len = (end - start) .min(scanline_bytes - offset) .min(end - position); buf[(bytes_read as usize)..][..len as usize] .copy_from_slice(&tmp[offset as usize..][..len as usize]); bytes_read += len; start += len; progress_callback(Progress { current: bytes_read, total: total_bytes, }); if start == end { return Ok(()); } } let target_scanline = start / scanline_bytes; if target_scanline != current_scanline { seek_scanline(decoder, target_scanline)?; current_scanline = target_scanline; } let mut position = current_scanline * scanline_bytes; while position < end { if position >= start && end - position >= scanline_bytes { read_scanline( decoder, &mut buf[(bytes_read as usize)..][..(scanline_bytes as usize)], )?; bytes_read += scanline_bytes; } else { tmp.resize(scanline_bytes as usize, 0u8); read_scanline(decoder, &mut tmp)?; tmp_scanline = Some(current_scanline); let offset = start.saturating_sub(position); let len = (end - start) .min(scanline_bytes - offset) .min(end - position); buf[(bytes_read as usize)..][..len as usize] .copy_from_slice(&tmp[offset as usize..][..len as usize]); bytes_read += len; } current_scanline += 1; position += scanline_bytes; progress_callback(Progress { current: bytes_read, total: total_bytes, }); } Ok(()) }; if x + width > u64::from(dimensions.0) || y + height > u64::from(dimensions.1) || width == 0 || height == 0 { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))); } if scanline_bytes > usize::max_value() as u64 { return Err(ImageError::Limits(LimitError::from_kind( LimitErrorKind::InsufficientMemory, ))); } progress_callback(Progress { current: 0, total: total_bytes, }); if x == 0 && width == u64::from(dimensions.0) { let start = x * bytes_per_pixel + y * row_bytes; let end = (x + width) * bytes_per_pixel + (y + height - 1) * row_bytes; read_image_range(start, end)?; } else { for row in y..(y + height) { let start = x * bytes_per_pixel + row * row_bytes; let end = (x + width) * bytes_per_pixel + row * row_bytes; read_image_range(start, end)?; } } } // Seek back to the start Ok(seek_scanline(decoder, 0)?) } /// Reads all of the bytes of a decoder into a Vec. No particular alignment /// of the output buffer is guaranteed. /// /// Panics if there isn't enough memory to decode the image. pub(crate) fn decoder_to_vec<'a, T>(decoder: impl ImageDecoder<'a>) -> ImageResult> where T: crate::traits::Primitive + bytemuck::Pod, { let total_bytes = usize::try_from(decoder.total_bytes()); if total_bytes.is_err() || total_bytes.unwrap() > isize::max_value() as usize { return Err(ImageError::Limits(LimitError::from_kind( LimitErrorKind::InsufficientMemory, ))); } let mut buf = vec![num_traits::Zero::zero(); total_bytes.unwrap() / std::mem::size_of::()]; decoder.read_image(bytemuck::cast_slice_mut(buf.as_mut_slice()))?; Ok(buf) } /// Represents the progress of an image operation. /// /// Note that this is not necessarily accurate and no change to the values passed to the progress /// function during decoding will be considered breaking. A decoder could in theory report the /// progress `(0, 0)` if progress is unknown, without violating the interface contract of the type. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Progress { current: u64, total: u64, } impl Progress { /// Create Progress. Result in invalid progress if you provide a greater `current` than `total`. pub(crate) fn new(current: u64, total: u64) -> Self { Self { current, total } } /// A measure of completed decoding. pub fn current(self) -> u64 { self.current } /// A measure of all necessary decoding work. /// /// This is in general greater or equal than `current`. pub fn total(self) -> u64 { self.total } /// Calculate a measure for remaining decoding work. pub fn remaining(self) -> u64 { self.total.max(self.current) - self.current } } /// The trait that all decoders implement pub trait ImageDecoder<'a>: Sized { /// The type of reader produced by `into_reader`. type Reader: Read + 'a; /// Returns a tuple containing the width and height of the image fn dimensions(&self) -> (u32, u32); /// Returns the color type of the image data produced by this decoder fn color_type(&self) -> ColorType; /// Returns the color type of the image file before decoding fn original_color_type(&self) -> ExtendedColorType { self.color_type().into() } /// Returns the ICC color profile embedded in the image /// /// For formats that don't support embedded profiles this function will always return `None`. /// This feature is currently only supported for the JPEG, PNG, and AVIF formats. fn icc_profile(&mut self) -> Option> { None } /// Returns a reader that can be used to obtain the bytes of the image. For the best /// performance, always try to read at least `scanline_bytes` from the reader at a time. Reading /// fewer bytes will cause the reader to perform internal buffering. fn into_reader(self) -> ImageResult; /// Returns the total number of bytes in the decoded image. /// /// This is the size of the buffer that must be passed to `read_image` or /// `read_image_with_progress`. The returned value may exceed usize::MAX, in /// which case it isn't actually possible to construct a buffer to decode all the image data /// into. If, however, the size does not fit in a u64 then u64::MAX is returned. fn total_bytes(&self) -> u64 { let dimensions = self.dimensions(); let total_pixels = u64::from(dimensions.0) * u64::from(dimensions.1); let bytes_per_pixel = u64::from(self.color_type().bytes_per_pixel()); total_pixels.saturating_mul(bytes_per_pixel) } /// Returns the minimum number of bytes that can be efficiently read from this decoder. This may /// be as few as 1 or as many as `total_bytes()`. fn scanline_bytes(&self) -> u64 { self.total_bytes() } /// Returns all the bytes in the image. /// /// This function takes a slice of bytes and writes the pixel data of the image into it. /// Although not required, for certain color types callers may want to pass buffers which are /// aligned to 2 or 4 byte boundaries to the slice can be cast to a [u16] or [u32]. To accommodate /// such casts, the returned contents will always be in native endian. /// /// # Panics /// /// This function panics if buf.len() != self.total_bytes(). /// /// # Examples /// /// ```no_build /// use zerocopy::{AsBytes, FromBytes}; /// fn read_16bit_image(decoder: impl ImageDecoder) -> Vec<16> { /// let mut buf: Vec = vec![0; decoder.total_bytes()/2]; /// decoder.read_image(buf.as_bytes()); /// buf /// } /// ``` fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { self.read_image_with_progress(buf, |_| {}) } /// Same as `read_image` but periodically calls the provided callback to give updates on loading /// progress. fn read_image_with_progress( self, buf: &mut [u8], progress_callback: F, ) -> ImageResult<()> { assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); let total_bytes = self.total_bytes() as usize; let scanline_bytes = self.scanline_bytes() as usize; let target_read_size = if scanline_bytes < 4096 { (4096 / scanline_bytes) * scanline_bytes } else { scanline_bytes }; let mut reader = self.into_reader()?; let mut bytes_read = 0; while bytes_read < total_bytes { let read_size = target_read_size.min(total_bytes - bytes_read); reader.read_exact(&mut buf[bytes_read..][..read_size])?; bytes_read += read_size; progress_callback(Progress { current: bytes_read as u64, total: total_bytes as u64, }); } Ok(()) } /// Set decoding limits for this decoder. See [`Limits`] for the different kinds of /// limits that is possible to set. /// /// Note to implementors: make sure you call [`Limits::check_support`] so that /// decoding fails if any unsupported strict limits are set. Also make sure /// you call [`Limits::check_dimensions`] to check the `max_image_width` and /// `max_image_height` limits. /// /// [`Limits`]: ./io/struct.Limits.html /// [`Limits::check_support`]: ./io/struct.Limits.html#method.check_support /// [`Limits::check_dimensions`]: ./io/struct.Limits.html#method.check_dimensions fn set_limits(&mut self, limits: crate::io::Limits) -> ImageResult<()> { limits.check_support(&crate::io::LimitSupport::default())?; let (width, height) = self.dimensions(); limits.check_dimensions(width, height)?; Ok(()) } } /// Specialized image decoding not be supported by all formats pub trait ImageDecoderRect<'a>: ImageDecoder<'a> + Sized { /// Decode a rectangular section of the image; see [`read_rect_with_progress()`](#fn.read_rect_with_progress). fn read_rect( &mut self, x: u32, y: u32, width: u32, height: u32, buf: &mut [u8], ) -> ImageResult<()> { self.read_rect_with_progress(x, y, width, height, buf, |_| {}) } /// Decode a rectangular section of the image, periodically reporting progress. /// /// The output buffer will be filled with fields specified by /// [`ImageDecoder::color_type()`](trait.ImageDecoder.html#fn.color_type), /// in that order, each field represented in native-endian. /// /// The progress callback will be called at least once at the start and the end of decoding, /// implementations are encouraged to call this more often, /// with a frequency meaningful for display to the end-user. /// /// This function will panic if the output buffer isn't at least /// `color_type().bytes_per_pixel() * color_type().channel_count() * width * height` bytes long. fn read_rect_with_progress( &mut self, x: u32, y: u32, width: u32, height: u32, buf: &mut [u8], progress_callback: F, ) -> ImageResult<()>; } /// AnimationDecoder trait pub trait AnimationDecoder<'a> { /// Consume the decoder producing a series of frames. fn into_frames(self) -> Frames<'a>; } /// The trait all encoders implement pub trait ImageEncoder { /// Writes all the bytes in an image to the encoder. /// /// This function takes a slice of bytes of the pixel data of the image /// and encodes them. Unlike particular format encoders inherent impl encode /// methods where endianness is not specified, here image data bytes should /// always be in native endian. The implementor will reorder the endianness /// as necessary for the target encoding format. /// /// See also `ImageDecoder::read_image` which reads byte buffers into /// native endian. fn write_image( self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()>; } /// Immutable pixel iterator #[derive(Debug)] pub struct Pixels<'a, I: ?Sized + 'a> { image: &'a I, x: u32, y: u32, width: u32, height: u32, } impl<'a, I: GenericImageView> Iterator for Pixels<'a, I> { type Item = (u32, u32, I::Pixel); fn next(&mut self) -> Option<(u32, u32, I::Pixel)> { if self.x >= self.width { self.x = 0; self.y += 1; } if self.y >= self.height { None } else { let pixel = self.image.get_pixel(self.x, self.y); let p = (self.x, self.y, pixel); self.x += 1; Some(p) } } } impl Clone for Pixels<'_, I> { fn clone(&self) -> Self { Pixels { ..*self } } } /// Trait to inspect an image. /// /// ``` /// use image::{GenericImageView, Rgb, RgbImage}; /// /// let buffer = RgbImage::new(10, 10); /// let image: &dyn GenericImageView> = &buffer; /// ``` pub trait GenericImageView { /// The type of pixel. type Pixel: Pixel; /// The width and height of this image. fn dimensions(&self) -> (u32, u32); /// The width of this image. fn width(&self) -> u32 { let (w, _) = self.dimensions(); w } /// The height of this image. fn height(&self) -> u32 { let (_, h) = self.dimensions(); h } /// The bounding rectangle of this image. fn bounds(&self) -> (u32, u32, u32, u32); /// Returns true if this x, y coordinate is contained inside the image. fn in_bounds(&self, x: u32, y: u32) -> bool { let (ix, iy, iw, ih) = self.bounds(); x >= ix && x < ix + iw && y >= iy && y < iy + ih } /// Returns the pixel located at (x, y). Indexed from top left. /// /// # Panics /// /// Panics if `(x, y)` is out of bounds. fn get_pixel(&self, x: u32, y: u32) -> Self::Pixel; /// Returns the pixel located at (x, y). Indexed from top left. /// /// This function can be implemented in a way that ignores bounds checking. /// # Safety /// /// The coordinates must be [`in_bounds`] of the image. /// /// [`in_bounds`]: #method.in_bounds unsafe fn unsafe_get_pixel(&self, x: u32, y: u32) -> Self::Pixel { self.get_pixel(x, y) } /// Returns an Iterator over the pixels of this image. /// The iterator yields the coordinates of each pixel /// along with their value fn pixels(&self) -> Pixels where Self: Sized, { let (width, height) = self.dimensions(); Pixels { image: self, x: 0, y: 0, width, height, } } /// Returns a subimage that is an immutable view into this image. /// You can use [`GenericImage::sub_image`] if you need a mutable view instead. /// The coordinates set the position of the top left corner of the view. fn view(&self, x: u32, y: u32, width: u32, height: u32) -> SubImage<&Self> where Self: Sized, { assert!(x as u64 + width as u64 <= self.width() as u64); assert!(y as u64 + height as u64 <= self.height() as u64); SubImage::new(self, x, y, width, height) } } /// A trait for manipulating images. pub trait GenericImage: GenericImageView { /// Gets a reference to the mutable pixel at location `(x, y)`. Indexed from top left. /// /// # Panics /// /// Panics if `(x, y)` is out of bounds. /// /// Panics for dynamic images (this method is deprecated and will be removed). /// /// ## Known issues /// /// This requires the buffer to contain a unique set of continuous channels in the exact order /// and byte representation that the pixel type requires. This is somewhat restrictive. /// /// TODO: Maybe use some kind of entry API? this would allow pixel type conversion on the fly /// while still doing only one array lookup: /// /// ```ignore /// let px = image.pixel_entry_at(x,y); /// px.set_from_rgba(rgba) /// ``` #[deprecated(since = "0.24.0", note = "Use `get_pixel` and `put_pixel` instead.")] fn get_pixel_mut(&mut self, x: u32, y: u32) -> &mut Self::Pixel; /// Put a pixel at location (x, y). Indexed from top left. /// /// # Panics /// /// Panics if `(x, y)` is out of bounds. fn put_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel); /// Puts a pixel at location (x, y). Indexed from top left. /// /// This function can be implemented in a way that ignores bounds checking. /// # Safety /// /// The coordinates must be [`in_bounds`] of the image. /// /// [`in_bounds`]: traits.GenericImageView.html#method.in_bounds unsafe fn unsafe_put_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) { self.put_pixel(x, y, pixel); } /// Put a pixel at location (x, y), taking into account alpha channels #[deprecated( since = "0.24.0", note = "Use iterator `pixels_mut` to blend the pixels directly" )] fn blend_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel); /// Copies all of the pixels from another image into this image. /// /// The other image is copied with the top-left corner of the /// other image placed at (x, y). /// /// In order to copy only a piece of the other image, use [`GenericImageView::view`]. /// /// You can use [`FlatSamples`] to source pixels from an arbitrary regular raster of channel /// values, for example from a foreign interface or a fixed image. /// /// # Returns /// Returns an error if the image is too large to be copied at the given position /// /// [`GenericImageView::view`]: trait.GenericImageView.html#method.view /// [`FlatSamples`]: flat/struct.FlatSamples.html fn copy_from(&mut self, other: &O, x: u32, y: u32) -> ImageResult<()> where O: GenericImageView, { // Do bounds checking here so we can use the non-bounds-checking // functions to copy pixels. if self.width() < other.width() + x || self.height() < other.height() + y { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))); } for k in 0..other.height() { for i in 0..other.width() { let p = other.get_pixel(i, k); self.put_pixel(i + x, k + y, p); } } Ok(()) } /// Copies all of the pixels from one part of this image to another part of this image. /// /// The destination rectangle of the copy is specified with the top-left corner placed at (x, y). /// /// # Returns /// `true` if the copy was successful, `false` if the image could not /// be copied due to size constraints. fn copy_within(&mut self, source: Rect, x: u32, y: u32) -> bool { let Rect { x: sx, y: sy, width, height, } = source; let dx = x; let dy = y; assert!(sx < self.width() && dx < self.width()); assert!(sy < self.height() && dy < self.height()); if self.width() - dx.max(sx) < width || self.height() - dy.max(sy) < height { return false; } // since `.rev()` creates a new dype we would either have to go with dynamic dispatch for the ranges // or have quite a lot of code bloat. A macro gives us static dispatch with less visible bloat. macro_rules! copy_within_impl_ { ($xiter:expr, $yiter:expr) => { for y in $yiter { let sy = sy + y; let dy = dy + y; for x in $xiter { let sx = sx + x; let dx = dx + x; let pixel = self.get_pixel(sx, sy); self.put_pixel(dx, dy, pixel); } } }; } // check how target and source rectangles relate to each other so we dont overwrite data before we copied it. match (sx < dx, sy < dy) { (true, true) => copy_within_impl_!((0..width).rev(), (0..height).rev()), (true, false) => copy_within_impl_!((0..width).rev(), 0..height), (false, true) => copy_within_impl_!(0..width, (0..height).rev()), (false, false) => copy_within_impl_!(0..width, 0..height), } true } /// Returns a mutable subimage that is a view into this image. /// If you want an immutable subimage instead, use [`GenericImageView::view`] /// The coordinates set the position of the top left corner of the SubImage. fn sub_image(&mut self, x: u32, y: u32, width: u32, height: u32) -> SubImage<&mut Self> where Self: Sized, { assert!(x as u64 + width as u64 <= self.width() as u64); assert!(y as u64 + height as u64 <= self.height() as u64); SubImage::new(self, x, y, width, height) } } /// A View into another image /// /// Instances of this struct can be created using: /// - [`GenericImage::sub_image`] to create a mutable view, /// - [`GenericImageView::view`] to create an immutable view, /// - [`SubImage::new`] to instantiate the struct directly. /// /// Note that this does _not_ implement `GenericImage`, but it dereferences to one which allows you /// to use it as if it did. See [Design Considerations](#Design-Considerations) below for details. /// /// # Design Considerations /// /// For reasons relating to coherence, this is not itself a `GenericImage` or a `GenericImageView`. /// In short, we want to reserve the ability of adding traits implemented for _all_ generic images /// but in a different manner for `SubImage`. This may be required to ensure that stacking /// sub-images comes at no double indirect cost. /// /// If, ultimately, this is not needed then a directly implementation of `GenericImage` can and /// will get added. This inconvenience may alternatively get resolved if Rust allows some forms of /// specialization, which might make this trick unnecessary and thus also allows for a direct /// implementation. #[derive(Copy, Clone)] pub struct SubImage { inner: SubImageInner, } /// The inner type of `SubImage` that implements `GenericImage{,View}`. /// /// This type is _nominally_ `pub` but it is not exported from the crate. It should be regarded as /// an existential type in any case. #[derive(Copy, Clone)] pub struct SubImageInner { image: I, xoffset: u32, yoffset: u32, xstride: u32, ystride: u32, } /// Alias to access Pixel behind a reference type DerefPixel = <::Target as GenericImageView>::Pixel; /// Alias to access Subpixel behind a reference type DerefSubpixel = as Pixel>::Subpixel; impl SubImage { /// Construct a new subimage /// The coordinates set the position of the top left corner of the SubImage. pub fn new(image: I, x: u32, y: u32, width: u32, height: u32) -> SubImage { SubImage { inner: SubImageInner { image, xoffset: x, yoffset: y, xstride: width, ystride: height, }, } } /// Change the coordinates of this subimage. pub fn change_bounds(&mut self, x: u32, y: u32, width: u32, height: u32) { self.inner.xoffset = x; self.inner.yoffset = y; self.inner.xstride = width; self.inner.ystride = height; } /// Convert this subimage to an ImageBuffer pub fn to_image(&self) -> ImageBuffer, Vec>> where I: Deref, I::Target: GenericImageView + 'static, { let mut out = ImageBuffer::new(self.inner.xstride, self.inner.ystride); let borrowed = self.inner.image.deref(); for y in 0..self.inner.ystride { for x in 0..self.inner.xstride { let p = borrowed.get_pixel(x + self.inner.xoffset, y + self.inner.yoffset); out.put_pixel(x, y, p); } } out } } /// Methods for readable images. impl SubImage where I: Deref, I::Target: GenericImageView, { /// Create a sub-view of the image. /// /// The coordinates given are relative to the current view on the underlying image. /// /// Note that this method is preferred to the one from `GenericImageView`. This is accessible /// with the explicit method call syntax but it should rarely be needed due to causing an /// extra level of indirection. /// /// ``` /// use image::{GenericImageView, RgbImage, SubImage}; /// let buffer = RgbImage::new(10, 10); /// /// let subimage: SubImage<&RgbImage> = buffer.view(0, 0, 10, 10); /// let subview: SubImage<&RgbImage> = subimage.view(0, 0, 10, 10); /// /// // Less efficient and NOT &RgbImage /// let _: SubImage<&_> = GenericImageView::view(&*subimage, 0, 0, 10, 10); /// ``` pub fn view(&self, x: u32, y: u32, width: u32, height: u32) -> SubImage<&I::Target> { use crate::GenericImageView as _; assert!(x as u64 + width as u64 <= self.inner.width() as u64); assert!(y as u64 + height as u64 <= self.inner.height() as u64); let x = self.inner.xoffset + x; let y = self.inner.yoffset + y; SubImage::new(&*self.inner.image, x, y, width, height) } /// Get a reference to the underlying image. pub fn inner(&self) -> &I::Target { &self.inner.image } } impl SubImage where I: DerefMut, I::Target: GenericImage, { /// Create a mutable sub-view of the image. /// /// The coordinates given are relative to the current view on the underlying image. pub fn sub_image( &mut self, x: u32, y: u32, width: u32, height: u32, ) -> SubImage<&mut I::Target> { assert!(x as u64 + width as u64 <= self.inner.width() as u64); assert!(y as u64 + height as u64 <= self.inner.height() as u64); let x = self.inner.xoffset + x; let y = self.inner.yoffset + y; SubImage::new(&mut *self.inner.image, x, y, width, height) } /// Get a mutable reference to the underlying image. pub fn inner_mut(&mut self) -> &mut I::Target { &mut self.inner.image } } impl Deref for SubImage where I: Deref, { type Target = SubImageInner; fn deref(&self) -> &Self::Target { &self.inner } } impl DerefMut for SubImage where I: DerefMut, { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } #[allow(deprecated)] impl GenericImageView for SubImageInner where I: Deref, I::Target: GenericImageView, { type Pixel = DerefPixel; fn dimensions(&self) -> (u32, u32) { (self.xstride, self.ystride) } fn bounds(&self) -> (u32, u32, u32, u32) { (self.xoffset, self.yoffset, self.xstride, self.ystride) } fn get_pixel(&self, x: u32, y: u32) -> Self::Pixel { self.image.get_pixel(x + self.xoffset, y + self.yoffset) } } #[allow(deprecated)] impl GenericImage for SubImageInner where I: DerefMut, I::Target: GenericImage + Sized, { fn get_pixel_mut(&mut self, x: u32, y: u32) -> &mut Self::Pixel { self.image.get_pixel_mut(x + self.xoffset, y + self.yoffset) } fn put_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) { self.image .put_pixel(x + self.xoffset, y + self.yoffset, pixel) } /// DEPRECATED: This method will be removed. Blend the pixel directly instead. fn blend_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) { self.image .blend_pixel(x + self.xoffset, y + self.yoffset, pixel) } } #[cfg(test)] mod tests { use std::io; use std::path::Path; use super::{ load_rect, ColorType, GenericImage, GenericImageView, ImageDecoder, ImageFormat, ImageResult, }; use crate::color::Rgba; use crate::math::Rect; use crate::{GrayImage, ImageBuffer}; #[test] #[allow(deprecated)] /// Test that alpha blending works as expected fn test_image_alpha_blending() { let mut target = ImageBuffer::new(1, 1); target.put_pixel(0, 0, Rgba([255u8, 0, 0, 255])); assert!(*target.get_pixel(0, 0) == Rgba([255, 0, 0, 255])); target.blend_pixel(0, 0, Rgba([0, 255, 0, 255])); assert!(*target.get_pixel(0, 0) == Rgba([0, 255, 0, 255])); // Blending an alpha channel onto a solid background target.blend_pixel(0, 0, Rgba([255, 0, 0, 127])); assert!(*target.get_pixel(0, 0) == Rgba([127, 127, 0, 255])); // Blending two alpha channels target.put_pixel(0, 0, Rgba([0, 255, 0, 127])); target.blend_pixel(0, 0, Rgba([255, 0, 0, 127])); assert!(*target.get_pixel(0, 0) == Rgba([169, 85, 0, 190])); } #[test] fn test_in_bounds() { let mut target = ImageBuffer::new(2, 2); target.put_pixel(0, 0, Rgba([255u8, 0, 0, 255])); assert!(target.in_bounds(0, 0)); assert!(target.in_bounds(1, 0)); assert!(target.in_bounds(0, 1)); assert!(target.in_bounds(1, 1)); assert!(!target.in_bounds(2, 0)); assert!(!target.in_bounds(0, 2)); assert!(!target.in_bounds(2, 2)); } #[test] fn test_can_subimage_clone_nonmut() { let mut source = ImageBuffer::new(3, 3); source.put_pixel(1, 1, Rgba([255u8, 0, 0, 255])); // A non-mutable copy of the source image let source = source.clone(); // Clone a view into non-mutable to a separate buffer let cloned = source.view(1, 1, 1, 1).to_image(); assert!(cloned.get_pixel(0, 0) == source.get_pixel(1, 1)); } #[test] fn test_can_nest_views() { let mut source = ImageBuffer::from_pixel(3, 3, Rgba([255u8, 0, 0, 255])); { let mut sub1 = source.sub_image(0, 0, 2, 2); let mut sub2 = sub1.sub_image(1, 1, 1, 1); sub2.put_pixel(0, 0, Rgba([0, 0, 0, 0])); } assert_eq!(*source.get_pixel(1, 1), Rgba([0, 0, 0, 0])); let view1 = source.view(0, 0, 2, 2); assert_eq!(*source.get_pixel(1, 1), view1.get_pixel(1, 1)); let view2 = view1.view(1, 1, 1, 1); assert_eq!(*source.get_pixel(1, 1), view2.get_pixel(0, 0)); } #[test] #[should_panic] fn test_view_out_of_bounds() { let source = ImageBuffer::from_pixel(3, 3, Rgba([255u8, 0, 0, 255])); source.view(1, 1, 3, 3); } #[test] #[should_panic] fn test_view_coordinates_out_of_bounds() { let source = ImageBuffer::from_pixel(3, 3, Rgba([255u8, 0, 0, 255])); source.view(3, 3, 3, 3); } #[test] #[should_panic] fn test_view_width_out_of_bounds() { let source = ImageBuffer::from_pixel(3, 3, Rgba([255u8, 0, 0, 255])); source.view(1, 1, 3, 2); } #[test] #[should_panic] fn test_view_height_out_of_bounds() { let source = ImageBuffer::from_pixel(3, 3, Rgba([255u8, 0, 0, 255])); source.view(1, 1, 2, 3); } #[test] #[should_panic] fn test_view_x_out_of_bounds() { let source = ImageBuffer::from_pixel(3, 3, Rgba([255u8, 0, 0, 255])); source.view(3, 1, 3, 3); } #[test] #[should_panic] fn test_view_y_out_of_bounds() { let source = ImageBuffer::from_pixel(3, 3, Rgba([255u8, 0, 0, 255])); source.view(1, 3, 3, 3); } #[test] fn test_view_in_bounds() { let source = ImageBuffer::from_pixel(3, 3, Rgba([255u8, 0, 0, 255])); source.view(0, 0, 3, 3); source.view(1, 1, 2, 2); source.view(2, 2, 0, 0); } #[test] fn test_copy_sub_image() { let source = ImageBuffer::from_pixel(3, 3, Rgba([255u8, 0, 0, 255])); let view = source.view(0, 0, 3, 3); let mut views = Vec::new(); views.push(view); view.to_image(); } #[test] fn test_load_rect() { struct MockDecoder { scanline_number: u64, scanline_bytes: u64, } impl<'a> ImageDecoder<'a> for MockDecoder { type Reader = Box; fn dimensions(&self) -> (u32, u32) { (5, 5) } fn color_type(&self) -> ColorType { ColorType::L8 } fn into_reader(self) -> ImageResult { unimplemented!() } fn scanline_bytes(&self) -> u64 { self.scanline_bytes } } const DATA: [u8; 25] = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, ]; fn seek_scanline(m: &mut MockDecoder, n: u64) -> io::Result<()> { m.scanline_number = n; Ok(()) } fn read_scanline(m: &mut MockDecoder, buf: &mut [u8]) -> io::Result<()> { let bytes_read = m.scanline_number * m.scanline_bytes; if bytes_read >= 25 { return Ok(()); } let len = m.scanline_bytes.min(25 - bytes_read); buf[..(len as usize)].copy_from_slice(&DATA[(bytes_read as usize)..][..(len as usize)]); m.scanline_number += 1; Ok(()) } for scanline_bytes in 1..30 { let mut output = [0u8; 26]; load_rect( 0, 0, 5, 5, &mut output, |_| {}, &mut MockDecoder { scanline_number: 0, scanline_bytes, }, seek_scanline, read_scanline, ) .unwrap(); assert_eq!(output[0..25], DATA); assert_eq!(output[25], 0); output = [0u8; 26]; load_rect( 3, 2, 1, 1, &mut output, |_| {}, &mut MockDecoder { scanline_number: 0, scanline_bytes, }, seek_scanline, read_scanline, ) .unwrap(); assert_eq!(output[0..2], [13, 0]); output = [0u8; 26]; load_rect( 3, 2, 2, 2, &mut output, |_| {}, &mut MockDecoder { scanline_number: 0, scanline_bytes, }, seek_scanline, read_scanline, ) .unwrap(); assert_eq!(output[0..5], [13, 14, 18, 19, 0]); output = [0u8; 26]; load_rect( 1, 1, 2, 4, &mut output, |_| {}, &mut MockDecoder { scanline_number: 0, scanline_bytes, }, seek_scanline, read_scanline, ) .unwrap(); assert_eq!(output[0..9], [6, 7, 11, 12, 16, 17, 21, 22, 0]); } } #[test] fn test_load_rect_single_scanline() { const DATA: [u8; 25] = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, ]; struct MockDecoder; impl<'a> ImageDecoder<'a> for MockDecoder { type Reader = Box; fn dimensions(&self) -> (u32, u32) { (5, 5) } fn color_type(&self) -> ColorType { ColorType::L8 } fn into_reader(self) -> ImageResult { unimplemented!() } fn scanline_bytes(&self) -> u64 { 25 } } // Ensure that seek scanline is called only once. let mut seeks = 0; let seek_scanline = |_d: &mut MockDecoder, n: u64| -> io::Result<()> { seeks += 1; assert_eq!(n, 0); assert_eq!(seeks, 1); Ok(()) }; fn read_scanline(_m: &mut MockDecoder, buf: &mut [u8]) -> io::Result<()> { buf.copy_from_slice(&DATA); Ok(()) } let mut output = [0; 26]; load_rect( 1, 1, 2, 4, &mut output, |_| {}, &mut MockDecoder, seek_scanline, read_scanline, ) .unwrap(); assert_eq!(output[0..9], [6, 7, 11, 12, 16, 17, 21, 22, 0]); } #[test] fn test_image_format_from_path() { fn from_path(s: &str) -> ImageResult { ImageFormat::from_path(Path::new(s)) } assert_eq!(from_path("./a.jpg").unwrap(), ImageFormat::Jpeg); assert_eq!(from_path("./a.jpeg").unwrap(), ImageFormat::Jpeg); assert_eq!(from_path("./a.JPEG").unwrap(), ImageFormat::Jpeg); assert_eq!(from_path("./a.pNg").unwrap(), ImageFormat::Png); assert_eq!(from_path("./a.gif").unwrap(), ImageFormat::Gif); assert_eq!(from_path("./a.webp").unwrap(), ImageFormat::WebP); assert_eq!(from_path("./a.tiFF").unwrap(), ImageFormat::Tiff); assert_eq!(from_path("./a.tif").unwrap(), ImageFormat::Tiff); assert_eq!(from_path("./a.tga").unwrap(), ImageFormat::Tga); assert_eq!(from_path("./a.dds").unwrap(), ImageFormat::Dds); assert_eq!(from_path("./a.bmp").unwrap(), ImageFormat::Bmp); assert_eq!(from_path("./a.Ico").unwrap(), ImageFormat::Ico); assert_eq!(from_path("./a.hdr").unwrap(), ImageFormat::Hdr); assert_eq!(from_path("./a.exr").unwrap(), ImageFormat::OpenExr); assert_eq!(from_path("./a.pbm").unwrap(), ImageFormat::Pnm); assert_eq!(from_path("./a.pAM").unwrap(), ImageFormat::Pnm); assert_eq!(from_path("./a.Ppm").unwrap(), ImageFormat::Pnm); assert_eq!(from_path("./a.pgm").unwrap(), ImageFormat::Pnm); assert_eq!(from_path("./a.AViF").unwrap(), ImageFormat::Avif); assert!(from_path("./a.txt").is_err()); assert!(from_path("./a").is_err()); } #[test] fn test_generic_image_copy_within_oob() { let mut image: GrayImage = ImageBuffer::from_raw(4, 4, vec![0u8; 16]).unwrap(); assert!(!image.sub_image(0, 0, 4, 4).copy_within( Rect { x: 0, y: 0, width: 5, height: 4 }, 0, 0 )); assert!(!image.sub_image(0, 0, 4, 4).copy_within( Rect { x: 0, y: 0, width: 4, height: 5 }, 0, 0 )); assert!(!image.sub_image(0, 0, 4, 4).copy_within( Rect { x: 1, y: 0, width: 4, height: 4 }, 0, 0 )); assert!(!image.sub_image(0, 0, 4, 4).copy_within( Rect { x: 0, y: 0, width: 4, height: 4 }, 1, 0 )); assert!(!image.sub_image(0, 0, 4, 4).copy_within( Rect { x: 0, y: 1, width: 4, height: 4 }, 0, 0 )); assert!(!image.sub_image(0, 0, 4, 4).copy_within( Rect { x: 0, y: 0, width: 4, height: 4 }, 0, 1 )); assert!(!image.sub_image(0, 0, 4, 4).copy_within( Rect { x: 1, y: 1, width: 4, height: 4 }, 0, 0 )); } #[test] fn test_generic_image_copy_within_tl() { let data = &[ 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15, ]; let expected = [ 00, 01, 02, 03, 04, 00, 01, 02, 08, 04, 05, 06, 12, 08, 09, 10, ]; let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); assert!(image.sub_image(0, 0, 4, 4).copy_within( Rect { x: 0, y: 0, width: 3, height: 3 }, 1, 1 )); assert_eq!(&image.into_raw(), &expected); } #[test] fn test_generic_image_copy_within_tr() { let data = &[ 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15, ]; let expected = [ 00, 01, 02, 03, 01, 02, 03, 07, 05, 06, 07, 11, 09, 10, 11, 15, ]; let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); assert!(image.sub_image(0, 0, 4, 4).copy_within( Rect { x: 1, y: 0, width: 3, height: 3 }, 0, 1 )); assert_eq!(&image.into_raw(), &expected); } #[test] fn test_generic_image_copy_within_bl() { let data = &[ 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15, ]; let expected = [ 00, 04, 05, 06, 04, 08, 09, 10, 08, 12, 13, 14, 12, 13, 14, 15, ]; let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); assert!(image.sub_image(0, 0, 4, 4).copy_within( Rect { x: 0, y: 1, width: 3, height: 3 }, 1, 0 )); assert_eq!(&image.into_raw(), &expected); } #[test] fn test_generic_image_copy_within_br() { let data = &[ 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15, ]; let expected = [ 05, 06, 07, 03, 09, 10, 11, 07, 13, 14, 15, 11, 12, 13, 14, 15, ]; let mut image: GrayImage = ImageBuffer::from_raw(4, 4, Vec::from(&data[..])).unwrap(); assert!(image.sub_image(0, 0, 4, 4).copy_within( Rect { x: 1, y: 1, width: 3, height: 3 }, 0, 0 )); assert_eq!(&image.into_raw(), &expected); } #[test] fn image_formats_are_recognized() { use ImageFormat::*; const ALL_FORMATS: &'static [ImageFormat] = &[ Avif, Png, Jpeg, Gif, WebP, Pnm, Tiff, Tga, Dds, Bmp, Ico, Hdr, Farbfeld, OpenExr, ]; for &format in ALL_FORMATS { let mut file = Path::new("file.nothing").to_owned(); for ext in format.extensions_str() { assert!(file.set_extension(ext)); match ImageFormat::from_path(&file) { Err(_) => panic!("Path {} not recognized as {:?}", file.display(), format), Ok(result) => assert_eq!(format, result), } } } } #[test] fn total_bytes_overflow() { struct D; impl<'a> ImageDecoder<'a> for D { type Reader = std::io::Cursor>; fn color_type(&self) -> ColorType { ColorType::Rgb8 } fn dimensions(&self) -> (u32, u32) { (0xffffffff, 0xffffffff) } fn into_reader(self) -> ImageResult { unreachable!() } } assert_eq!(D.total_bytes(), u64::max_value()); let v: ImageResult> = super::decoder_to_vec(D); assert!(v.is_err()); } } image-0.24.7/src/imageops/affine.rs000064400000000000000000000275261046102023000152050ustar 00000000000000//! Functions for performing affine transformations. use crate::error::{ImageError, ParameterError, ParameterErrorKind}; use crate::image::{GenericImage, GenericImageView}; use crate::traits::Pixel; use crate::ImageBuffer; /// Rotate an image 90 degrees clockwise. pub fn rotate90( image: &I, ) -> ImageBuffer::Subpixel>> where I::Pixel: 'static, { let (width, height) = image.dimensions(); let mut out = ImageBuffer::new(height, width); let _ = rotate90_in(image, &mut out); out } /// Rotate an image 180 degrees clockwise. pub fn rotate180( image: &I, ) -> ImageBuffer::Subpixel>> where I::Pixel: 'static, { let (width, height) = image.dimensions(); let mut out = ImageBuffer::new(width, height); let _ = rotate180_in(image, &mut out); out } /// Rotate an image 270 degrees clockwise. pub fn rotate270( image: &I, ) -> ImageBuffer::Subpixel>> where I::Pixel: 'static, { let (width, height) = image.dimensions(); let mut out = ImageBuffer::new(height, width); let _ = rotate270_in(image, &mut out); out } /// Rotate an image 90 degrees clockwise and put the result into the destination [`ImageBuffer`]. pub fn rotate90_in( image: &I, destination: &mut ImageBuffer, ) -> crate::ImageResult<()> where I: GenericImageView, I::Pixel: 'static, Container: std::ops::DerefMut::Subpixel]>, { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != h1 || h0 != w1 { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))); } for y in 0..h0 { for x in 0..w0 { let p = image.get_pixel(x, y); destination.put_pixel(h0 - y - 1, x, p); } } Ok(()) } /// Rotate an image 180 degrees clockwise and put the result into the destination [`ImageBuffer`]. pub fn rotate180_in( image: &I, destination: &mut ImageBuffer, ) -> crate::ImageResult<()> where I: GenericImageView, I::Pixel: 'static, Container: std::ops::DerefMut::Subpixel]>, { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != w1 || h0 != h1 { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))); } for y in 0..h0 { for x in 0..w0 { let p = image.get_pixel(x, y); destination.put_pixel(w0 - x - 1, h0 - y - 1, p); } } Ok(()) } /// Rotate an image 270 degrees clockwise and put the result into the destination [`ImageBuffer`]. pub fn rotate270_in( image: &I, destination: &mut ImageBuffer, ) -> crate::ImageResult<()> where I: GenericImageView, I::Pixel: 'static, Container: std::ops::DerefMut::Subpixel]>, { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != h1 || h0 != w1 { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))); } for y in 0..h0 { for x in 0..w0 { let p = image.get_pixel(x, y); destination.put_pixel(y, w0 - x - 1, p); } } Ok(()) } /// Flip an image horizontally pub fn flip_horizontal( image: &I, ) -> ImageBuffer::Subpixel>> where I::Pixel: 'static, { let (width, height) = image.dimensions(); let mut out = ImageBuffer::new(width, height); let _ = flip_horizontal_in(image, &mut out); out } /// Flip an image vertically pub fn flip_vertical( image: &I, ) -> ImageBuffer::Subpixel>> where I::Pixel: 'static, { let (width, height) = image.dimensions(); let mut out = ImageBuffer::new(width, height); let _ = flip_vertical_in(image, &mut out); out } /// Flip an image horizontally and put the result into the destination [`ImageBuffer`]. pub fn flip_horizontal_in( image: &I, destination: &mut ImageBuffer, ) -> crate::ImageResult<()> where I: GenericImageView, I::Pixel: 'static, Container: std::ops::DerefMut::Subpixel]>, { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != w1 || h0 != h1 { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))); } for y in 0..h0 { for x in 0..w0 { let p = image.get_pixel(x, y); destination.put_pixel(w0 - x - 1, y, p); } } Ok(()) } /// Flip an image vertically and put the result into the destination [`ImageBuffer`]. pub fn flip_vertical_in( image: &I, destination: &mut ImageBuffer, ) -> crate::ImageResult<()> where I: GenericImageView, I::Pixel: 'static, Container: std::ops::DerefMut::Subpixel]>, { let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions()); if w0 != w1 || h0 != h1 { return Err(ImageError::Parameter(ParameterError::from_kind( ParameterErrorKind::DimensionMismatch, ))); } for y in 0..h0 { for x in 0..w0 { let p = image.get_pixel(x, y); destination.put_pixel(x, h0 - 1 - y, p); } } Ok(()) } /// Rotate an image 180 degrees clockwise in place. pub fn rotate180_in_place(image: &mut I) { let (width, height) = image.dimensions(); for y in 0..height / 2 { for x in 0..width { let p = image.get_pixel(x, y); let x2 = width - x - 1; let y2 = height - y - 1; let p2 = image.get_pixel(x2, y2); image.put_pixel(x, y, p2); image.put_pixel(x2, y2, p); } } if height % 2 != 0 { let middle = height / 2; for x in 0..width / 2 { let p = image.get_pixel(x, middle); let x2 = width - x - 1; let p2 = image.get_pixel(x2, middle); image.put_pixel(x, middle, p2); image.put_pixel(x2, middle, p); } } } /// Flip an image horizontally in place. pub fn flip_horizontal_in_place(image: &mut I) { let (width, height) = image.dimensions(); for y in 0..height { for x in 0..width / 2 { let x2 = width - x - 1; let p2 = image.get_pixel(x2, y); let p = image.get_pixel(x, y); image.put_pixel(x2, y, p); image.put_pixel(x, y, p2); } } } /// Flip an image vertically in place. pub fn flip_vertical_in_place(image: &mut I) { let (width, height) = image.dimensions(); for y in 0..height / 2 { for x in 0..width { let y2 = height - y - 1; let p2 = image.get_pixel(x, y2); let p = image.get_pixel(x, y); image.put_pixel(x, y2, p); image.put_pixel(x, y, p2); } } } #[cfg(test)] mod test { use super::{ flip_horizontal, flip_horizontal_in_place, flip_vertical, flip_vertical_in_place, rotate180, rotate180_in_place, rotate270, rotate90, }; use crate::image::GenericImage; use crate::traits::Pixel; use crate::{GrayImage, ImageBuffer}; macro_rules! assert_pixels_eq { ($actual:expr, $expected:expr) => {{ let actual_dim = $actual.dimensions(); let expected_dim = $expected.dimensions(); if actual_dim != expected_dim { panic!( "dimensions do not match. \ actual: {:?}, expected: {:?}", actual_dim, expected_dim ) } let diffs = pixel_diffs($actual, $expected); if !diffs.is_empty() { let mut err = "".to_string(); let diff_messages = diffs .iter() .take(5) .map(|d| format!("\nactual: {:?}, expected {:?} ", d.0, d.1)) .collect::>() .join(""); err.push_str(&diff_messages); panic!("pixels do not match. {:?}", err) } }}; } #[test] fn test_rotate90() { let image: GrayImage = ImageBuffer::from_raw(3, 2, vec![00u8, 01u8, 02u8, 10u8, 11u8, 12u8]).unwrap(); let expected: GrayImage = ImageBuffer::from_raw(2, 3, vec![10u8, 00u8, 11u8, 01u8, 12u8, 02u8]).unwrap(); assert_pixels_eq!(&rotate90(&image), &expected); } #[test] fn test_rotate180() { let image: GrayImage = ImageBuffer::from_raw(3, 2, vec![00u8, 01u8, 02u8, 10u8, 11u8, 12u8]).unwrap(); let expected: GrayImage = ImageBuffer::from_raw(3, 2, vec![12u8, 11u8, 10u8, 02u8, 01u8, 00u8]).unwrap(); assert_pixels_eq!(&rotate180(&image), &expected); } #[test] fn test_rotate270() { let image: GrayImage = ImageBuffer::from_raw(3, 2, vec![00u8, 01u8, 02u8, 10u8, 11u8, 12u8]).unwrap(); let expected: GrayImage = ImageBuffer::from_raw(2, 3, vec![02u8, 12u8, 01u8, 11u8, 00u8, 10u8]).unwrap(); assert_pixels_eq!(&rotate270(&image), &expected); } #[test] fn test_rotate180_in_place() { let mut image: GrayImage = ImageBuffer::from_raw(3, 2, vec![00u8, 01u8, 02u8, 10u8, 11u8, 12u8]).unwrap(); let expected: GrayImage = ImageBuffer::from_raw(3, 2, vec![12u8, 11u8, 10u8, 02u8, 01u8, 00u8]).unwrap(); rotate180_in_place(&mut image); assert_pixels_eq!(&image, &expected); } #[test] fn test_flip_horizontal() { let image: GrayImage = ImageBuffer::from_raw(3, 2, vec![00u8, 01u8, 02u8, 10u8, 11u8, 12u8]).unwrap(); let expected: GrayImage = ImageBuffer::from_raw(3, 2, vec![02u8, 01u8, 00u8, 12u8, 11u8, 10u8]).unwrap(); assert_pixels_eq!(&flip_horizontal(&image), &expected); } #[test] fn test_flip_vertical() { let image: GrayImage = ImageBuffer::from_raw(3, 2, vec![00u8, 01u8, 02u8, 10u8, 11u8, 12u8]).unwrap(); let expected: GrayImage = ImageBuffer::from_raw(3, 2, vec![10u8, 11u8, 12u8, 00u8, 01u8, 02u8]).unwrap(); assert_pixels_eq!(&flip_vertical(&image), &expected); } #[test] fn test_flip_horizontal_in_place() { let mut image: GrayImage = ImageBuffer::from_raw(3, 2, vec![00u8, 01u8, 02u8, 10u8, 11u8, 12u8]).unwrap(); let expected: GrayImage = ImageBuffer::from_raw(3, 2, vec![02u8, 01u8, 00u8, 12u8, 11u8, 10u8]).unwrap(); flip_horizontal_in_place(&mut image); assert_pixels_eq!(&image, &expected); } #[test] fn test_flip_vertical_in_place() { let mut image: GrayImage = ImageBuffer::from_raw(3, 2, vec![00u8, 01u8, 02u8, 10u8, 11u8, 12u8]).unwrap(); let expected: GrayImage = ImageBuffer::from_raw(3, 2, vec![10u8, 11u8, 12u8, 00u8, 01u8, 02u8]).unwrap(); flip_vertical_in_place(&mut image); assert_pixels_eq!(&image, &expected); } fn pixel_diffs(left: &I, right: &J) -> Vec<((u32, u32, P), (u32, u32, P))> where I: GenericImage, J: GenericImage, P: Pixel + Eq, { left.pixels() .zip(right.pixels()) .filter(|&(p, q)| p != q) .collect::>() } } image-0.24.7/src/imageops/colorops.rs000064400000000000000000000475151046102023000156150ustar 00000000000000//! Functions for altering and converting the color of pixelbufs use num_traits::NumCast; use std::f64::consts::PI; use crate::color::{FromColor, IntoColor, Luma, LumaA, Rgba}; use crate::image::{GenericImage, GenericImageView}; use crate::traits::{Pixel, Primitive}; use crate::utils::clamp; use crate::ImageBuffer; type Subpixel = <::Pixel as Pixel>::Subpixel; /// Convert the supplied image to grayscale. Alpha channel is discarded. pub fn grayscale( image: &I, ) -> ImageBuffer>, Vec>> { grayscale_with_type(image) } /// Convert the supplied image to grayscale. Alpha channel is preserved. pub fn grayscale_alpha( image: &I, ) -> ImageBuffer>, Vec>> { grayscale_with_type_alpha(image) } /// Convert the supplied image to a grayscale image with the specified pixel type. Alpha channel is discarded. pub fn grayscale_with_type( image: &I, ) -> ImageBuffer> where NewPixel: Pixel + FromColor>>, { let (width, height) = image.dimensions(); let mut out = ImageBuffer::new(width, height); for (x, y, pixel) in image.pixels() { let grayscale = pixel.to_luma(); let new_pixel = grayscale.into_color(); // no-op for luma->luma out.put_pixel(x, y, new_pixel); } out } /// Convert the supplied image to a grayscale image with the specified pixel type. Alpha channel is preserved. pub fn grayscale_with_type_alpha( image: &I, ) -> ImageBuffer> where NewPixel: Pixel + FromColor>>, { let (width, height) = image.dimensions(); let mut out = ImageBuffer::new(width, height); for (x, y, pixel) in image.pixels() { let grayscale = pixel.to_luma_alpha(); let new_pixel = grayscale.into_color(); // no-op for luma->luma out.put_pixel(x, y, new_pixel); } out } /// Invert each pixel within the supplied image. /// This function operates in place. pub fn invert(image: &mut I) { // TODO find a way to use pixels? let (width, height) = image.dimensions(); for y in 0..height { for x in 0..width { let mut p = image.get_pixel(x, y); p.invert(); image.put_pixel(x, y, p); } } } /// Adjust the contrast of the supplied image. /// ```contrast``` is the amount to adjust the contrast by. /// Negative values decrease the contrast and positive values increase the contrast. /// /// *[See also `contrast_in_place`.][contrast_in_place]* pub fn contrast(image: &I, contrast: f32) -> ImageBuffer> where I: GenericImageView, P: Pixel + 'static, S: Primitive + 'static, { let (width, height) = image.dimensions(); let mut out = ImageBuffer::new(width, height); let max = S::DEFAULT_MAX_VALUE; let max: f32 = NumCast::from(max).unwrap(); let percent = ((100.0 + contrast) / 100.0).powi(2); for (x, y, pixel) in image.pixels() { let f = pixel.map(|b| { let c: f32 = NumCast::from(b).unwrap(); let d = ((c / max - 0.5) * percent + 0.5) * max; let e = clamp(d, 0.0, max); NumCast::from(e).unwrap() }); out.put_pixel(x, y, f); } out } /// Adjust the contrast of the supplied image in place. /// ```contrast``` is the amount to adjust the contrast by. /// Negative values decrease the contrast and positive values increase the contrast. /// /// *[See also `contrast`.][contrast]* pub fn contrast_in_place(image: &mut I, contrast: f32) where I: GenericImage, { let (width, height) = image.dimensions(); let max = ::Subpixel::DEFAULT_MAX_VALUE; let max: f32 = NumCast::from(max).unwrap(); let percent = ((100.0 + contrast) / 100.0).powi(2); // TODO find a way to use pixels? for y in 0..height { for x in 0..width { let f = image.get_pixel(x, y).map(|b| { let c: f32 = NumCast::from(b).unwrap(); let d = ((c / max - 0.5) * percent + 0.5) * max; let e = clamp(d, 0.0, max); NumCast::from(e).unwrap() }); image.put_pixel(x, y, f); } } } /// Brighten the supplied image. /// ```value``` is the amount to brighten each pixel by. /// Negative values decrease the brightness and positive values increase it. /// /// *[See also `brighten_in_place`.][brighten_in_place]* pub fn brighten(image: &I, value: i32) -> ImageBuffer> where I: GenericImageView, P: Pixel + 'static, S: Primitive + 'static, { let (width, height) = image.dimensions(); let mut out = ImageBuffer::new(width, height); let max = S::DEFAULT_MAX_VALUE; let max: i32 = NumCast::from(max).unwrap(); for (x, y, pixel) in image.pixels() { let e = pixel.map_with_alpha( |b| { let c: i32 = NumCast::from(b).unwrap(); let d = clamp(c + value, 0, max); NumCast::from(d).unwrap() }, |alpha| alpha, ); out.put_pixel(x, y, e); } out } /// Brighten the supplied image in place. /// ```value``` is the amount to brighten each pixel by. /// Negative values decrease the brightness and positive values increase it. /// /// *[See also `brighten`.][brighten]* pub fn brighten_in_place(image: &mut I, value: i32) where I: GenericImage, { let (width, height) = image.dimensions(); let max = ::Subpixel::DEFAULT_MAX_VALUE; let max: i32 = NumCast::from(max).unwrap(); // TODO what does this do for f32? clamp at 1?? // TODO find a way to use pixels? for y in 0..height { for x in 0..width { let e = image.get_pixel(x, y).map_with_alpha( |b| { let c: i32 = NumCast::from(b).unwrap(); let d = clamp(c + value, 0, max); NumCast::from(d).unwrap() }, |alpha| alpha, ); image.put_pixel(x, y, e); } } } /// Hue rotate the supplied image. /// `value` is the degrees to rotate each pixel by. /// 0 and 360 do nothing, the rest rotates by the given degree value. /// just like the css webkit filter hue-rotate(180) /// /// *[See also `huerotate_in_place`.][huerotate_in_place]* pub fn huerotate(image: &I, value: i32) -> ImageBuffer> where I: GenericImageView, P: Pixel + 'static, S: Primitive + 'static, { let (width, height) = image.dimensions(); let mut out = ImageBuffer::new(width, height); let angle: f64 = NumCast::from(value).unwrap(); let cosv = (angle * PI / 180.0).cos(); let sinv = (angle * PI / 180.0).sin(); let matrix: [f64; 9] = [ // Reds 0.213 + cosv * 0.787 - sinv * 0.213, 0.715 - cosv * 0.715 - sinv * 0.715, 0.072 - cosv * 0.072 + sinv * 0.928, // Greens 0.213 - cosv * 0.213 + sinv * 0.143, 0.715 + cosv * 0.285 + sinv * 0.140, 0.072 - cosv * 0.072 - sinv * 0.283, // Blues 0.213 - cosv * 0.213 - sinv * 0.787, 0.715 - cosv * 0.715 + sinv * 0.715, 0.072 + cosv * 0.928 + sinv * 0.072, ]; for (x, y, pixel) in out.enumerate_pixels_mut() { let p = image.get_pixel(x, y); #[allow(deprecated)] let (k1, k2, k3, k4) = p.channels4(); let vec: (f64, f64, f64, f64) = ( NumCast::from(k1).unwrap(), NumCast::from(k2).unwrap(), NumCast::from(k3).unwrap(), NumCast::from(k4).unwrap(), ); let r = vec.0; let g = vec.1; let b = vec.2; let new_r = matrix[0] * r + matrix[1] * g + matrix[2] * b; let new_g = matrix[3] * r + matrix[4] * g + matrix[5] * b; let new_b = matrix[6] * r + matrix[7] * g + matrix[8] * b; let max = 255f64; #[allow(deprecated)] let outpixel = Pixel::from_channels( NumCast::from(clamp(new_r, 0.0, max)).unwrap(), NumCast::from(clamp(new_g, 0.0, max)).unwrap(), NumCast::from(clamp(new_b, 0.0, max)).unwrap(), NumCast::from(clamp(vec.3, 0.0, max)).unwrap(), ); *pixel = outpixel; } out } /// Hue rotate the supplied image in place. /// `value` is the degrees to rotate each pixel by. /// 0 and 360 do nothing, the rest rotates by the given degree value. /// just like the css webkit filter hue-rotate(180) /// /// *[See also `huerotate`.][huerotate]* pub fn huerotate_in_place(image: &mut I, value: i32) where I: GenericImage, { let (width, height) = image.dimensions(); let angle: f64 = NumCast::from(value).unwrap(); let cosv = (angle * PI / 180.0).cos(); let sinv = (angle * PI / 180.0).sin(); let matrix: [f64; 9] = [ // Reds 0.213 + cosv * 0.787 - sinv * 0.213, 0.715 - cosv * 0.715 - sinv * 0.715, 0.072 - cosv * 0.072 + sinv * 0.928, // Greens 0.213 - cosv * 0.213 + sinv * 0.143, 0.715 + cosv * 0.285 + sinv * 0.140, 0.072 - cosv * 0.072 - sinv * 0.283, // Blues 0.213 - cosv * 0.213 - sinv * 0.787, 0.715 - cosv * 0.715 + sinv * 0.715, 0.072 + cosv * 0.928 + sinv * 0.072, ]; // TODO find a way to use pixels? for y in 0..height { for x in 0..width { let pixel = image.get_pixel(x, y); #[allow(deprecated)] let (k1, k2, k3, k4) = pixel.channels4(); let vec: (f64, f64, f64, f64) = ( NumCast::from(k1).unwrap(), NumCast::from(k2).unwrap(), NumCast::from(k3).unwrap(), NumCast::from(k4).unwrap(), ); let r = vec.0; let g = vec.1; let b = vec.2; let new_r = matrix[0] * r + matrix[1] * g + matrix[2] * b; let new_g = matrix[3] * r + matrix[4] * g + matrix[5] * b; let new_b = matrix[6] * r + matrix[7] * g + matrix[8] * b; let max = 255f64; #[allow(deprecated)] let outpixel = Pixel::from_channels( NumCast::from(clamp(new_r, 0.0, max)).unwrap(), NumCast::from(clamp(new_g, 0.0, max)).unwrap(), NumCast::from(clamp(new_b, 0.0, max)).unwrap(), NumCast::from(clamp(vec.3, 0.0, max)).unwrap(), ); image.put_pixel(x, y, outpixel); } } } /// A color map pub trait ColorMap { /// The color type on which the map operates on type Color; /// Returns the index of the closest match of `color` /// in the color map. fn index_of(&self, color: &Self::Color) -> usize; /// Looks up color by index in the color map. If `idx` is out of range for the color map, or /// ColorMap doesn't implement `lookup` `None` is returned. fn lookup(&self, index: usize) -> Option { let _ = index; None } /// Determine if this implementation of ColorMap overrides the default `lookup`. fn has_lookup(&self) -> bool { false } /// Maps `color` to the closest color in the color map. fn map_color(&self, color: &mut Self::Color); } /// A bi-level color map /// /// # Examples /// ``` /// use image::imageops::colorops::{index_colors, BiLevel, ColorMap}; /// use image::{ImageBuffer, Luma}; /// /// let (w, h) = (16, 16); /// // Create an image with a smooth horizontal gradient from black (0) to white (255). /// let gray = ImageBuffer::from_fn(w, h, |x, y| -> Luma { [(255 * x / w) as u8].into() }); /// // Mapping the gray image through the `BiLevel` filter should map gray pixels less than half /// // intensity (127) to black (0), and anything greater to white (255). /// let cmap = BiLevel; /// let palletized = index_colors(&gray, &cmap); /// let mapped = ImageBuffer::from_fn(w, h, |x, y| { /// let p = palletized.get_pixel(x, y); /// cmap.lookup(p.0[0] as usize) /// .expect("indexed color out-of-range") /// }); /// // Create an black and white image of expected output. /// let bw = ImageBuffer::from_fn(w, h, |x, y| -> Luma { /// if x <= (w / 2) { /// [0].into() /// } else { /// [255].into() /// } /// }); /// assert_eq!(mapped, bw); /// ``` #[derive(Clone, Copy)] pub struct BiLevel; impl ColorMap for BiLevel { type Color = Luma; #[inline(always)] fn index_of(&self, color: &Luma) -> usize { let luma = color.0; if luma[0] > 127 { 1 } else { 0 } } #[inline(always)] fn lookup(&self, idx: usize) -> Option { match idx { 0 => Some([0].into()), 1 => Some([255].into()), _ => None, } } /// Indicate NeuQuant implements `lookup`. fn has_lookup(&self) -> bool { true } #[inline(always)] fn map_color(&self, color: &mut Luma) { let new_color = 0xFF * self.index_of(color) as u8; let luma = &mut color.0; luma[0] = new_color; } } impl ColorMap for color_quant::NeuQuant { type Color = Rgba; #[inline(always)] fn index_of(&self, color: &Rgba) -> usize { self.index_of(color.channels()) } #[inline(always)] fn lookup(&self, idx: usize) -> Option { self.lookup(idx).map(|p| p.into()) } /// Indicate NeuQuant implements `lookup`. fn has_lookup(&self) -> bool { true } #[inline(always)] fn map_color(&self, color: &mut Rgba) { self.map_pixel(color.channels_mut()) } } /// Floyd-Steinberg error diffusion fn diffuse_err>(pixel: &mut P, error: [i16; 3], factor: i16) { for (e, c) in error.iter().zip(pixel.channels_mut().iter_mut()) { *c = match >::from(*c) + e * factor / 16 { val if val < 0 => 0, val if val > 0xFF => 0xFF, val => val as u8, } } } macro_rules! do_dithering( ($map:expr, $image:expr, $err:expr, $x:expr, $y:expr) => ( { let old_pixel = $image[($x, $y)]; let new_pixel = $image.get_pixel_mut($x, $y); $map.map_color(new_pixel); for ((e, &old), &new) in $err.iter_mut() .zip(old_pixel.channels().iter()) .zip(new_pixel.channels().iter()) { *e = >::from(old) - >::from(new) } } ) ); /// Reduces the colors of the image using the supplied `color_map` while applying /// Floyd-Steinberg dithering to improve the visual conception pub fn dither(image: &mut ImageBuffer>, color_map: &Map) where Map: ColorMap + ?Sized, Pix: Pixel + 'static, { let (width, height) = image.dimensions(); let mut err: [i16; 3] = [0; 3]; for y in 0..height - 1 { let x = 0; do_dithering!(color_map, image, err, x, y); diffuse_err(image.get_pixel_mut(x + 1, y), err, 7); diffuse_err(image.get_pixel_mut(x, y + 1), err, 5); diffuse_err(image.get_pixel_mut(x + 1, y + 1), err, 1); for x in 1..width - 1 { do_dithering!(color_map, image, err, x, y); diffuse_err(image.get_pixel_mut(x + 1, y), err, 7); diffuse_err(image.get_pixel_mut(x - 1, y + 1), err, 3); diffuse_err(image.get_pixel_mut(x, y + 1), err, 5); diffuse_err(image.get_pixel_mut(x + 1, y + 1), err, 1); } let x = width - 1; do_dithering!(color_map, image, err, x, y); diffuse_err(image.get_pixel_mut(x - 1, y + 1), err, 3); diffuse_err(image.get_pixel_mut(x, y + 1), err, 5); } let y = height - 1; let x = 0; do_dithering!(color_map, image, err, x, y); diffuse_err(image.get_pixel_mut(x + 1, y), err, 7); for x in 1..width - 1 { do_dithering!(color_map, image, err, x, y); diffuse_err(image.get_pixel_mut(x + 1, y), err, 7); } let x = width - 1; do_dithering!(color_map, image, err, x, y); } /// Reduces the colors using the supplied `color_map` and returns an image of the indices pub fn index_colors( image: &ImageBuffer>, color_map: &Map, ) -> ImageBuffer, Vec> where Map: ColorMap + ?Sized, Pix: Pixel + 'static, { let mut indices = ImageBuffer::new(image.width(), image.height()); for (pixel, idx) in image.pixels().zip(indices.pixels_mut()) { *idx = Luma([color_map.index_of(pixel) as u8]) } indices } #[cfg(test)] mod test { use super::*; use crate::{GrayImage, ImageBuffer}; macro_rules! assert_pixels_eq { ($actual:expr, $expected:expr) => {{ let actual_dim = $actual.dimensions(); let expected_dim = $expected.dimensions(); if actual_dim != expected_dim { panic!( "dimensions do not match. \ actual: {:?}, expected: {:?}", actual_dim, expected_dim ) } let diffs = pixel_diffs($actual, $expected); if !diffs.is_empty() { let mut err = "".to_string(); let diff_messages = diffs .iter() .take(5) .map(|d| format!("\nactual: {:?}, expected {:?} ", d.0, d.1)) .collect::>() .join(""); err.push_str(&diff_messages); panic!("pixels do not match. {:?}", err) } }}; } #[test] fn test_dither() { let mut image = ImageBuffer::from_raw(2, 2, vec![127, 127, 127, 127]).unwrap(); let cmap = BiLevel; dither(&mut image, &cmap); assert_eq!(&*image, &[0, 0xFF, 0xFF, 0]); assert_eq!(index_colors(&image, &cmap).into_raw(), vec![0, 1, 1, 0]) } #[test] fn test_grayscale() { let mut image: GrayImage = ImageBuffer::from_raw(3, 2, vec![00u8, 01u8, 02u8, 10u8, 11u8, 12u8]).unwrap(); let expected: GrayImage = ImageBuffer::from_raw(3, 2, vec![00u8, 01u8, 02u8, 10u8, 11u8, 12u8]).unwrap(); assert_pixels_eq!(&grayscale(&mut image), &expected); } #[test] fn test_invert() { let mut image: GrayImage = ImageBuffer::from_raw(3, 2, vec![00u8, 01u8, 02u8, 10u8, 11u8, 12u8]).unwrap(); let expected: GrayImage = ImageBuffer::from_raw(3, 2, vec![255u8, 254u8, 253u8, 245u8, 244u8, 243u8]).unwrap(); invert(&mut image); assert_pixels_eq!(&image, &expected); } #[test] fn test_brighten() { let image: GrayImage = ImageBuffer::from_raw(3, 2, vec![00u8, 01u8, 02u8, 10u8, 11u8, 12u8]).unwrap(); let expected: GrayImage = ImageBuffer::from_raw(3, 2, vec![10u8, 11u8, 12u8, 20u8, 21u8, 22u8]).unwrap(); assert_pixels_eq!(&brighten(&image, 10), &expected); } #[test] fn test_brighten_place() { let mut image: GrayImage = ImageBuffer::from_raw(3, 2, vec![00u8, 01u8, 02u8, 10u8, 11u8, 12u8]).unwrap(); let expected: GrayImage = ImageBuffer::from_raw(3, 2, vec![10u8, 11u8, 12u8, 20u8, 21u8, 22u8]).unwrap(); brighten_in_place(&mut image, 10); assert_pixels_eq!(&image, &expected); } fn pixel_diffs(left: &I, right: &J) -> Vec<((u32, u32, P), (u32, u32, P))> where I: GenericImage, J: GenericImage, P: Pixel + Eq, { left.pixels() .zip(right.pixels()) .filter(|&(p, q)| p != q) .collect::>() } } image-0.24.7/src/imageops/mod.rs000064400000000000000000000401721046102023000145240ustar 00000000000000//! Image Processing Functions use std::cmp; use crate::image::{GenericImage, GenericImageView, SubImage}; use crate::traits::{Lerp, Pixel, Primitive}; pub use self::sample::FilterType; pub use self::sample::FilterType::{CatmullRom, Gaussian, Lanczos3, Nearest, Triangle}; /// Affine transformations pub use self::affine::{ flip_horizontal, flip_horizontal_in, flip_horizontal_in_place, flip_vertical, flip_vertical_in, flip_vertical_in_place, rotate180, rotate180_in, rotate180_in_place, rotate270, rotate270_in, rotate90, rotate90_in, }; /// Image sampling pub use self::sample::{ blur, filter3x3, interpolate_bilinear, interpolate_nearest, resize, sample_bilinear, sample_nearest, thumbnail, unsharpen, }; /// Color operations pub use self::colorops::{ brighten, contrast, dither, grayscale, grayscale_alpha, grayscale_with_type, grayscale_with_type_alpha, huerotate, index_colors, invert, BiLevel, ColorMap, }; mod affine; // Public only because of Rust bug: // https://github.com/rust-lang/rust/issues/18241 pub mod colorops; mod sample; /// Return a mutable view into an image /// The coordinates set the position of the top left corner of the crop. pub fn crop( image: &mut I, x: u32, y: u32, width: u32, height: u32, ) -> SubImage<&mut I> { let (x, y, width, height) = crop_dimms(image, x, y, width, height); SubImage::new(image, x, y, width, height) } /// Return an immutable view into an image /// The coordinates set the position of the top left corner of the crop. pub fn crop_imm( image: &I, x: u32, y: u32, width: u32, height: u32, ) -> SubImage<&I> { let (x, y, width, height) = crop_dimms(image, x, y, width, height); SubImage::new(image, x, y, width, height) } fn crop_dimms( image: &I, x: u32, y: u32, width: u32, height: u32, ) -> (u32, u32, u32, u32) { let (iwidth, iheight) = image.dimensions(); let x = cmp::min(x, iwidth); let y = cmp::min(y, iheight); let height = cmp::min(height, iheight - y); let width = cmp::min(width, iwidth - x); (x, y, width, height) } /// Calculate the region that can be copied from top to bottom. /// /// Given image size of bottom and top image, and a point at which we want to place the top image /// onto the bottom image, how large can we be? Have to wary of the following issues: /// * Top might be larger than bottom /// * Overflows in the computation /// * Coordinates could be completely out of bounds /// /// The main idea is to make use of inequalities provided by the nature of `saturating_add` and /// `saturating_sub`. These intrinsically validate that all resulting coordinates will be in bounds /// for both images. /// /// We want that all these coordinate accesses are safe: /// 1. `bottom.get_pixel(x + [0..x_range), y + [0..y_range))` /// 2. `top.get_pixel([0..x_range), [0..y_range))` /// /// Proof that the function provides the necessary bounds for width. Note that all unaugmented math /// operations are to be read in standard arithmetic, not integer arithmetic. Since no direct /// integer arithmetic occurs in the implementation, this is unambiguous. /// /// ```text /// Three short notes/lemmata: /// - Iff `(a - b) <= 0` then `a.saturating_sub(b) = 0` /// - Iff `(a - b) >= 0` then `a.saturating_sub(b) = a - b` /// - If `a <= c` then `a.saturating_sub(b) <= c.saturating_sub(b)` /// /// 1.1 We show that if `bottom_width <= x`, then `x_range = 0` therefore `x + [0..x_range)` is empty. /// /// x_range /// = (top_width.saturating_add(x).min(bottom_width)).saturating_sub(x) /// <= bottom_width.saturating_sub(x) /// /// bottom_width <= x /// <==> bottom_width - x <= 0 /// <==> bottom_width.saturating_sub(x) = 0 /// ==> x_range <= 0 /// ==> x_range = 0 /// /// 1.2 If `x < bottom_width` then `x + x_range < bottom_width` /// /// x + x_range /// <= x + bottom_width.saturating_sub(x) /// = x + (bottom_width - x) /// = bottom_width /// /// 2. We show that `x_range <= top_width` /// /// x_range /// = (top_width.saturating_add(x).min(bottom_width)).saturating_sub(x) /// <= top_width.saturating_add(x).saturating_sub(x) /// <= (top_wdith + x).saturating_sub(x) /// = top_width (due to `top_width >= 0` and `x >= 0`) /// ``` /// /// Proof is the same for height. pub fn overlay_bounds( (bottom_width, bottom_height): (u32, u32), (top_width, top_height): (u32, u32), x: u32, y: u32, ) -> (u32, u32) { let x_range = top_width .saturating_add(x) // Calculate max coordinate .min(bottom_width) // Restrict to lower width .saturating_sub(x); // Determinate length from start `x` let y_range = top_height .saturating_add(y) .min(bottom_height) .saturating_sub(y); (x_range, y_range) } /// Calculate the region that can be copied from top to bottom. /// /// Given image size of bottom and top image, and a point at which we want to place the top image /// onto the bottom image, how large can we be? Have to wary of the following issues: /// * Top might be larger than bottom /// * Overflows in the computation /// * Coordinates could be completely out of bounds /// /// The returned value is of the form: /// /// `(origin_bottom_x, origin_bottom_y, origin_top_x, origin_top_y, x_range, y_range)` /// /// The main idea is to do computations on i64's and then clamp to image dimensions. /// In particular, we want to ensure that all these coordinate accesses are safe: /// 1. `bottom.get_pixel(origin_bottom_x + [0..x_range), origin_bottom_y + [0..y_range))` /// 2. `top.get_pixel(origin_top_y + [0..x_range), origin_top_y + [0..y_range))` /// fn overlay_bounds_ext( (bottom_width, bottom_height): (u32, u32), (top_width, top_height): (u32, u32), x: i64, y: i64, ) -> (u32, u32, u32, u32, u32, u32) { // Return a predictable value if the two images don't overlap at all. if x > i64::from(bottom_width) || y > i64::from(bottom_height) || x.saturating_add(i64::from(top_width)) <= 0 || y.saturating_add(i64::from(top_height)) <= 0 { return (0, 0, 0, 0, 0, 0); } // Find the maximum x and y coordinates in terms of the bottom image. let max_x = x.saturating_add(i64::from(top_width)); let max_y = y.saturating_add(i64::from(top_height)); // Clip the origin and maximum coordinates to the bounds of the bottom image. // Casting to a u32 is safe because both 0 and `bottom_{width,height}` fit // into 32-bits. let max_inbounds_x = max_x.clamp(0, i64::from(bottom_width)) as u32; let max_inbounds_y = max_y.clamp(0, i64::from(bottom_height)) as u32; let origin_bottom_x = x.clamp(0, i64::from(bottom_width)) as u32; let origin_bottom_y = y.clamp(0, i64::from(bottom_height)) as u32; // The range is the difference between the maximum inbounds coordinates and // the clipped origin. Unchecked subtraction is safe here because both are // always positive and `max_inbounds_{x,y}` >= `origin_{x,y}` due to // `top_{width,height}` being >= 0. let x_range = max_inbounds_x - origin_bottom_x; let y_range = max_inbounds_y - origin_bottom_y; // If x (or y) is negative, then the origin of the top image is shifted by -x (or -y). let origin_top_x = x.saturating_mul(-1).clamp(0, i64::from(top_width)) as u32; let origin_top_y = y.saturating_mul(-1).clamp(0, i64::from(top_height)) as u32; ( origin_bottom_x, origin_bottom_y, origin_top_x, origin_top_y, x_range, y_range, ) } /// Overlay an image at a given coordinate (x, y) pub fn overlay(bottom: &mut I, top: &J, x: i64, y: i64) where I: GenericImage, J: GenericImageView, { let bottom_dims = bottom.dimensions(); let top_dims = top.dimensions(); // Crop our top image if we're going out of bounds let (origin_bottom_x, origin_bottom_y, origin_top_x, origin_top_y, range_width, range_height) = overlay_bounds_ext(bottom_dims, top_dims, x, y); for y in 0..range_height { for x in 0..range_width { let p = top.get_pixel(origin_top_x + x, origin_top_y + y); let mut bottom_pixel = bottom.get_pixel(origin_bottom_x + x, origin_bottom_y + y); bottom_pixel.blend(&p); bottom.put_pixel(origin_bottom_x + x, origin_bottom_y + y, bottom_pixel); } } } /// Tile an image by repeating it multiple times /// /// # Examples /// ```no_run /// use image::{RgbaImage}; /// /// let mut img = RgbaImage::new(1920, 1080); /// let tile = image::open("tile.png").unwrap(); /// /// image::imageops::tile(&mut img, &tile); /// img.save("tiled_wallpaper.png").unwrap(); /// ``` pub fn tile(bottom: &mut I, top: &J) where I: GenericImage, J: GenericImageView, { for x in (0..bottom.width()).step_by(top.width() as usize) { for y in (0..bottom.height()).step_by(top.height() as usize) { overlay(bottom, top, i64::from(x), i64::from(y)); } } } /// Fill the image with a linear vertical gradient /// /// This function assumes a linear color space. /// /// # Examples /// ```no_run /// use image::{Rgba, RgbaImage, Pixel}; /// /// let mut img = RgbaImage::new(100, 100); /// let start = Rgba::from_slice(&[0, 128, 0, 0]); /// let end = Rgba::from_slice(&[255, 255, 255, 255]); /// /// image::imageops::vertical_gradient(&mut img, start, end); /// img.save("vertical_gradient.png").unwrap(); pub fn vertical_gradient(img: &mut I, start: &P, stop: &P) where I: GenericImage, P: Pixel + 'static, S: Primitive + Lerp + 'static, { for y in 0..img.height() { let pixel = start.map2(stop, |a, b| { let y = ::from(y).unwrap(); let height = ::from(img.height() - 1).unwrap(); S::lerp(a, b, y / height) }); for x in 0..img.width() { img.put_pixel(x, y, pixel); } } } /// Fill the image with a linear horizontal gradient /// /// This function assumes a linear color space. /// /// # Examples /// ```no_run /// use image::{Rgba, RgbaImage, Pixel}; /// /// let mut img = RgbaImage::new(100, 100); /// let start = Rgba::from_slice(&[0, 128, 0, 0]); /// let end = Rgba::from_slice(&[255, 255, 255, 255]); /// /// image::imageops::horizontal_gradient(&mut img, start, end); /// img.save("horizontal_gradient.png").unwrap(); pub fn horizontal_gradient(img: &mut I, start: &P, stop: &P) where I: GenericImage, P: Pixel + 'static, S: Primitive + Lerp + 'static, { for x in 0..img.width() { let pixel = start.map2(stop, |a, b| { let x = ::from(x).unwrap(); let width = ::from(img.width() - 1).unwrap(); S::lerp(a, b, x / width) }); for y in 0..img.height() { img.put_pixel(x, y, pixel); } } } /// Replace the contents of an image at a given coordinate (x, y) pub fn replace(bottom: &mut I, top: &J, x: i64, y: i64) where I: GenericImage, J: GenericImageView, { let bottom_dims = bottom.dimensions(); let top_dims = top.dimensions(); // Crop our top image if we're going out of bounds let (origin_bottom_x, origin_bottom_y, origin_top_x, origin_top_y, range_width, range_height) = overlay_bounds_ext(bottom_dims, top_dims, x, y); for y in 0..range_height { for x in 0..range_width { let p = top.get_pixel(origin_top_x + x, origin_top_y + y); bottom.put_pixel(origin_bottom_x + x, origin_bottom_y + y, p); } } } #[cfg(test)] mod tests { use super::{overlay, overlay_bounds_ext}; use crate::color::Rgb; use crate::ImageBuffer; use crate::RgbaImage; #[test] fn test_overlay_bounds_ext() { assert_eq!( overlay_bounds_ext((10, 10), (10, 10), 0, 0), (0, 0, 0, 0, 10, 10) ); assert_eq!( overlay_bounds_ext((10, 10), (10, 10), 1, 0), (1, 0, 0, 0, 9, 10) ); assert_eq!( overlay_bounds_ext((10, 10), (10, 10), 0, 11), (0, 0, 0, 0, 0, 0) ); assert_eq!( overlay_bounds_ext((10, 10), (10, 10), -1, 0), (0, 0, 1, 0, 9, 10) ); assert_eq!( overlay_bounds_ext((10, 10), (10, 10), -10, 0), (0, 0, 0, 0, 0, 0) ); assert_eq!( overlay_bounds_ext((10, 10), (10, 10), 1i64 << 50, 0), (0, 0, 0, 0, 0, 0) ); assert_eq!( overlay_bounds_ext((10, 10), (10, 10), -(1i64 << 50), 0), (0, 0, 0, 0, 0, 0) ); assert_eq!( overlay_bounds_ext((10, 10), (u32::MAX, 10), 10 - i64::from(u32::MAX), 0), (0, 0, u32::MAX - 10, 0, 10, 10) ); } #[test] /// Test that images written into other images works fn test_image_in_image() { let mut target = ImageBuffer::new(32, 32); let source = ImageBuffer::from_pixel(16, 16, Rgb([255u8, 0, 0])); overlay(&mut target, &source, 0, 0); assert!(*target.get_pixel(0, 0) == Rgb([255u8, 0, 0])); assert!(*target.get_pixel(15, 0) == Rgb([255u8, 0, 0])); assert!(*target.get_pixel(16, 0) == Rgb([0u8, 0, 0])); assert!(*target.get_pixel(0, 15) == Rgb([255u8, 0, 0])); assert!(*target.get_pixel(0, 16) == Rgb([0u8, 0, 0])); } #[test] /// Test that images written outside of a frame doesn't blow up fn test_image_in_image_outside_of_bounds() { let mut target = ImageBuffer::new(32, 32); let source = ImageBuffer::from_pixel(32, 32, Rgb([255u8, 0, 0])); overlay(&mut target, &source, 1, 1); assert!(*target.get_pixel(0, 0) == Rgb([0, 0, 0])); assert!(*target.get_pixel(1, 1) == Rgb([255u8, 0, 0])); assert!(*target.get_pixel(31, 31) == Rgb([255u8, 0, 0])); } #[test] /// Test that images written to coordinates out of the frame doesn't blow up /// (issue came up in #848) fn test_image_outside_image_no_wrap_around() { let mut target = ImageBuffer::new(32, 32); let source = ImageBuffer::from_pixel(32, 32, Rgb([255u8, 0, 0])); overlay(&mut target, &source, 33, 33); assert!(*target.get_pixel(0, 0) == Rgb([0, 0, 0])); assert!(*target.get_pixel(1, 1) == Rgb([0, 0, 0])); assert!(*target.get_pixel(31, 31) == Rgb([0, 0, 0])); } #[test] /// Test that images written to coordinates with overflow works fn test_image_coordinate_overflow() { let mut target = ImageBuffer::new(16, 16); let source = ImageBuffer::from_pixel(32, 32, Rgb([255u8, 0, 0])); // Overflows to 'sane' coordinates but top is larger than bot. overlay( &mut target, &source, i64::from(u32::max_value() - 31), i64::from(u32::max_value() - 31), ); assert!(*target.get_pixel(0, 0) == Rgb([0, 0, 0])); assert!(*target.get_pixel(1, 1) == Rgb([0, 0, 0])); assert!(*target.get_pixel(15, 15) == Rgb([0, 0, 0])); } use super::{horizontal_gradient, vertical_gradient}; #[test] /// Test that horizontal gradients are correctly generated fn test_image_horizontal_gradient_limits() { let mut img = ImageBuffer::new(100, 1); let start = Rgb([0u8, 128, 0]); let end = Rgb([255u8, 255, 255]); horizontal_gradient(&mut img, &start, &end); assert_eq!(img.get_pixel(0, 0), &start); assert_eq!(img.get_pixel(img.width() - 1, 0), &end); } #[test] /// Test that vertical gradients are correctly generated fn test_image_vertical_gradient_limits() { let mut img = ImageBuffer::new(1, 100); let start = Rgb([0u8, 128, 0]); let end = Rgb([255u8, 255, 255]); vertical_gradient(&mut img, &start, &end); assert_eq!(img.get_pixel(0, 0), &start); assert_eq!(img.get_pixel(0, img.height() - 1), &end); } #[test] /// Test blur doesn't panick when passed 0.0 fn test_blur_zero() { let image = RgbaImage::new(50, 50); let _ = super::blur(&image, 0.0); } } image-0.24.7/src/imageops/sample.rs000064400000000000000000001157031046102023000152310ustar 00000000000000//! Functions and filters for the sampling of pixels. // See http://cs.brown.edu/courses/cs123/lectures/08_Image_Processing_IV.pdf // for some of the theory behind image scaling and convolution use std::f32; use num_traits::{NumCast, ToPrimitive, Zero}; use crate::image::{GenericImage, GenericImageView}; use crate::traits::{Enlargeable, Pixel, Primitive}; use crate::utils::clamp; use crate::{ImageBuffer, Rgba32FImage}; /// Available Sampling Filters. /// /// ## Examples /// /// To test the different sampling filters on a real example, you can find two /// examples called /// [`scaledown`](https://github.com/image-rs/image/tree/master/examples/scaledown) /// and /// [`scaleup`](https://github.com/image-rs/image/tree/master/examples/scaleup) /// in the `examples` directory of the crate source code. /// /// Here is a 3.58 MiB /// [test image](https://github.com/image-rs/image/blob/master/examples/scaledown/test.jpg) /// that has been scaled down to 300x225 px: /// /// ///

///
///
/// Nearest Neighbor ///
///
///
/// Linear: Triangle ///
///
///
/// Cubic: Catmull-Rom ///
///
///
/// Gaussian ///
///
///
/// Lanczos with window 3 ///
///
/// /// ## Speed /// /// Time required to create each of the examples above, tested on an Intel /// i7-4770 CPU with Rust 1.37 in release mode: /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// ///
Nearest31 ms
Triangle414 ms
CatmullRom817 ms
Gaussian1180 ms
Lanczos31170 ms
#[derive(Clone, Copy, Debug, PartialEq)] pub enum FilterType { /// Nearest Neighbor Nearest, /// Linear Filter Triangle, /// Cubic Filter CatmullRom, /// Gaussian Filter Gaussian, /// Lanczos with window 3 Lanczos3, } /// A Representation of a separable filter. pub(crate) struct Filter<'a> { /// The filter's filter function. pub(crate) kernel: Box f32 + 'a>, /// The window on which this filter operates. pub(crate) support: f32, } struct FloatNearest(f32); // to_i64, to_u64, and to_f64 implicitly affect all other lower conversions. // Note that to_f64 by default calls to_i64 and thus needs to be overridden. impl ToPrimitive for FloatNearest { // to_{i,u}64 is required, to_{i,u}{8,16} are useful. // If a usecase for full 32 bits is found its trivial to add fn to_i8(&self) -> Option { self.0.round().to_i8() } fn to_i16(&self) -> Option { self.0.round().to_i16() } fn to_i64(&self) -> Option { self.0.round().to_i64() } fn to_u8(&self) -> Option { self.0.round().to_u8() } fn to_u16(&self) -> Option { self.0.round().to_u16() } fn to_u64(&self) -> Option { self.0.round().to_u64() } fn to_f64(&self) -> Option { self.0.to_f64() } } // sinc function: the ideal sampling filter. fn sinc(t: f32) -> f32 { let a = t * f32::consts::PI; if t == 0.0 { 1.0 } else { a.sin() / a } } // lanczos kernel function. A windowed sinc function. fn lanczos(x: f32, t: f32) -> f32 { if x.abs() < t { sinc(x) * sinc(x / t) } else { 0.0 } } // Calculate a splice based on the b and c parameters. // from authors Mitchell and Netravali. fn bc_cubic_spline(x: f32, b: f32, c: f32) -> f32 { let a = x.abs(); let k = if a < 1.0 { (12.0 - 9.0 * b - 6.0 * c) * a.powi(3) + (-18.0 + 12.0 * b + 6.0 * c) * a.powi(2) + (6.0 - 2.0 * b) } else if a < 2.0 { (-b - 6.0 * c) * a.powi(3) + (6.0 * b + 30.0 * c) * a.powi(2) + (-12.0 * b - 48.0 * c) * a + (8.0 * b + 24.0 * c) } else { 0.0 }; k / 6.0 } /// The Gaussian Function. /// ```r``` is the standard deviation. pub(crate) fn gaussian(x: f32, r: f32) -> f32 { ((2.0 * f32::consts::PI).sqrt() * r).recip() * (-x.powi(2) / (2.0 * r.powi(2))).exp() } /// Calculate the lanczos kernel with a window of 3 pub(crate) fn lanczos3_kernel(x: f32) -> f32 { lanczos(x, 3.0) } /// Calculate the gaussian function with a /// standard deviation of 0.5 pub(crate) fn gaussian_kernel(x: f32) -> f32 { gaussian(x, 0.5) } /// Calculate the Catmull-Rom cubic spline. /// Also known as a form of `BiCubic` sampling in two dimensions. pub(crate) fn catmullrom_kernel(x: f32) -> f32 { bc_cubic_spline(x, 0.0, 0.5) } /// Calculate the triangle function. /// Also known as `BiLinear` sampling in two dimensions. pub(crate) fn triangle_kernel(x: f32) -> f32 { if x.abs() < 1.0 { 1.0 - x.abs() } else { 0.0 } } /// Calculate the box kernel. /// Only pixels inside the box should be considered, and those /// contribute equally. So this method simply returns 1. pub(crate) fn box_kernel(_x: f32) -> f32 { 1.0 } // Sample the rows of the supplied image using the provided filter. // The height of the image remains unchanged. // ```new_width``` is the desired width of the new image // ```filter``` is the filter to use for sampling. // ```image``` is not necessarily Rgba and the order of channels is passed through. fn horizontal_sample( image: &Rgba32FImage, new_width: u32, filter: &mut Filter, ) -> ImageBuffer> where P: Pixel + 'static, S: Primitive + 'static, { let (width, height) = image.dimensions(); let mut out = ImageBuffer::new(new_width, height); let mut ws = Vec::new(); let max: f32 = NumCast::from(S::DEFAULT_MAX_VALUE).unwrap(); let min: f32 = NumCast::from(S::DEFAULT_MIN_VALUE).unwrap(); let ratio = width as f32 / new_width as f32; let sratio = if ratio < 1.0 { 1.0 } else { ratio }; let src_support = filter.support * sratio; for outx in 0..new_width { // Find the point in the input image corresponding to the centre // of the current pixel in the output image. let inputx = (outx as f32 + 0.5) * ratio; // Left and right are slice bounds for the input pixels relevant // to the output pixel we are calculating. Pixel x is relevant // if and only if (x >= left) && (x < right). // Invariant: 0 <= left < right <= width let left = (inputx - src_support).floor() as i64; let left = clamp(left, 0, >::from(width) - 1) as u32; let right = (inputx + src_support).ceil() as i64; let right = clamp( right, >::from(left) + 1, >::from(width), ) as u32; // Go back to left boundary of pixel, to properly compare with i // below, as the kernel treats the centre of a pixel as 0. let inputx = inputx - 0.5; ws.clear(); let mut sum = 0.0; for i in left..right { let w = (filter.kernel)((i as f32 - inputx) / sratio); ws.push(w); sum += w; } ws.iter_mut().for_each(|w| *w /= sum); for y in 0..height { let mut t = (0.0, 0.0, 0.0, 0.0); for (i, w) in ws.iter().enumerate() { let p = image.get_pixel(left + i as u32, y); #[allow(deprecated)] let vec = p.channels4(); t.0 += vec.0 * w; t.1 += vec.1 * w; t.2 += vec.2 * w; t.3 += vec.3 * w; } #[allow(deprecated)] let t = Pixel::from_channels( NumCast::from(FloatNearest(clamp(t.0, min, max))).unwrap(), NumCast::from(FloatNearest(clamp(t.1, min, max))).unwrap(), NumCast::from(FloatNearest(clamp(t.2, min, max))).unwrap(), NumCast::from(FloatNearest(clamp(t.3, min, max))).unwrap(), ); out.put_pixel(outx, y, t); } } out } /// Linearly sample from an image using coordinates in [0, 1]. pub fn sample_bilinear( img: &impl GenericImageView, u: f32, v: f32, ) -> Option

{ if ![u, v].iter().all(|c| (0.0..=1.0).contains(c)) { return None; } let (w, h) = img.dimensions(); if w == 0 || h == 0 { return None; } let ui = w as f32 * u - 0.5; let vi = h as f32 * v - 0.5; interpolate_bilinear( img, ui.max(0.).min((w - 1) as f32), vi.max(0.).min((h - 1) as f32), ) } /// Sample from an image using coordinates in [0, 1], taking the nearest coordinate. pub fn sample_nearest( img: &impl GenericImageView, u: f32, v: f32, ) -> Option

{ if ![u, v].iter().all(|c| (0.0..=1.0).contains(c)) { return None; } let (w, h) = img.dimensions(); let ui = w as f32 * u - 0.5; let ui = ui.max(0.).min((w.saturating_sub(1)) as f32); let vi = h as f32 * v - 0.5; let vi = vi.max(0.).min((h.saturating_sub(1)) as f32); interpolate_nearest(img, ui, vi) } /// Sample from an image using coordinates in [0, w-1] and [0, h-1], taking the /// nearest pixel. /// /// Coordinates outside the image bounds will return `None`, however the /// behavior for points within half a pixel of the image bounds may change in /// the future. pub fn interpolate_nearest( img: &impl GenericImageView, x: f32, y: f32, ) -> Option

{ let (w, h) = img.dimensions(); if w == 0 || h == 0 { return None; } if !(0.0..=((w - 1) as f32)).contains(&x) { return None; } if !(0.0..=((h - 1) as f32)).contains(&y) { return None; } Some(img.get_pixel(x.round() as u32, y.round() as u32)) } /// Linearly sample from an image using coordinates in [0, w-1] and [0, h-1]. pub fn interpolate_bilinear( img: &impl GenericImageView, x: f32, y: f32, ) -> Option

{ let (w, h) = img.dimensions(); if w == 0 || h == 0 { return None; } if !(0.0..=((w - 1) as f32)).contains(&x) { return None; } if !(0.0..=((h - 1) as f32)).contains(&y) { return None; } let uf = x.floor(); let vf = y.floor(); let uc = (x + 1.).min((w - 1) as f32); let vc = (y + 1.).min((h - 1) as f32); // clamp coords to the range of the image let coords = [[uf, vf], [uf, vc], [uc, vf], [uc, vc]]; assert!(coords .iter() .all(|&[u, v]| { img.in_bounds(u as u32, v as u32) })); let samples = coords.map(|[u, v]| img.get_pixel(u as u32, v as u32)); assert!(P::CHANNEL_COUNT <= 4); // convert samples to f32 // currently rgba is the largest one, // so just store as many items as necessary, // because there's not a simple way to be generic over all of them. let [sff, sfc, scf, scc] = samples.map(|s| { let mut out = [0.; 4]; for (i, c) in s.channels().iter().enumerate() { out[i] = c.to_f32().unwrap(); } out }); // weights let [ufw, vfw] = [x - uf, y - vf]; let [ucw, vcw] = [1. - ufw, 1. - vfw]; // https://en.wikipedia.org/wiki/Bilinear_interpolation#Weighted_mean // the distance between pixels is 1 so there is no denominator let wff = ucw * vcw; let wfc = ucw * vfw; let wcf = ufw * vcw; let wcc = ufw * vfw; assert!(f32::abs((wff + wfc + wcf + wcc) - 1.) < 1e-3); // hack to get around not being able to construct a generic Pixel let mut out = samples[0]; for (i, c) in out.channels_mut().iter_mut().enumerate() { let v = wff * sff[i] + wfc * sfc[i] + wcf * scf[i] + wcc * scc[i]; // this rounding may introduce quantization errors, // but cannot do anything about it. *c = ::from(v.round()).unwrap_or({ if v < 0.0 { P::Subpixel::DEFAULT_MIN_VALUE } else { P::Subpixel::DEFAULT_MAX_VALUE } }) } Some(out) } // Sample the columns of the supplied image using the provided filter. // The width of the image remains unchanged. // ```new_height``` is the desired height of the new image // ```filter``` is the filter to use for sampling. // The return value is not necessarily Rgba, the underlying order of channels in ```image``` is // preserved. fn vertical_sample(image: &I, new_height: u32, filter: &mut Filter) -> Rgba32FImage where I: GenericImageView, P: Pixel + 'static, S: Primitive + 'static, { let (width, height) = image.dimensions(); let mut out = ImageBuffer::new(width, new_height); let mut ws = Vec::new(); let ratio = height as f32 / new_height as f32; let sratio = if ratio < 1.0 { 1.0 } else { ratio }; let src_support = filter.support * sratio; for outy in 0..new_height { // For an explanation of this algorithm, see the comments // in horizontal_sample. let inputy = (outy as f32 + 0.5) * ratio; let left = (inputy - src_support).floor() as i64; let left = clamp(left, 0, >::from(height) - 1) as u32; let right = (inputy + src_support).ceil() as i64; let right = clamp( right, >::from(left) + 1, >::from(height), ) as u32; let inputy = inputy - 0.5; ws.clear(); let mut sum = 0.0; for i in left..right { let w = (filter.kernel)((i as f32 - inputy) / sratio); ws.push(w); sum += w; } ws.iter_mut().for_each(|w| *w /= sum); for x in 0..width { let mut t = (0.0, 0.0, 0.0, 0.0); for (i, w) in ws.iter().enumerate() { let p = image.get_pixel(x, left + i as u32); #[allow(deprecated)] let (k1, k2, k3, k4) = p.channels4(); let vec: (f32, f32, f32, f32) = ( NumCast::from(k1).unwrap(), NumCast::from(k2).unwrap(), NumCast::from(k3).unwrap(), NumCast::from(k4).unwrap(), ); t.0 += vec.0 * w; t.1 += vec.1 * w; t.2 += vec.2 * w; t.3 += vec.3 * w; } #[allow(deprecated)] // This is not necessarily Rgba. let t = Pixel::from_channels(t.0, t.1, t.2, t.3); out.put_pixel(x, outy, t); } } out } /// Local struct for keeping track of pixel sums for fast thumbnail averaging struct ThumbnailSum(S::Larger, S::Larger, S::Larger, S::Larger); impl ThumbnailSum { fn zeroed() -> Self { ThumbnailSum( S::Larger::zero(), S::Larger::zero(), S::Larger::zero(), S::Larger::zero(), ) } fn sample_val(val: S) -> S::Larger { ::from(val).unwrap() } fn add_pixel>(&mut self, pixel: P) { #[allow(deprecated)] let pixel = pixel.channels4(); self.0 += Self::sample_val(pixel.0); self.1 += Self::sample_val(pixel.1); self.2 += Self::sample_val(pixel.2); self.3 += Self::sample_val(pixel.3); } } /// Resize the supplied image to the specific dimensions. /// /// For downscaling, this method uses a fast integer algorithm where each source pixel contributes /// to exactly one target pixel. May give aliasing artifacts if new size is close to old size. /// /// In case the current width is smaller than the new width or similar for the height, another /// strategy is used instead. For each pixel in the output, a rectangular region of the input is /// determined, just as previously. But when no input pixel is part of this region, the nearest /// pixels are interpolated instead. /// /// For speed reasons, all interpolation is performed linearly over the colour values. It will not /// take the pixel colour spaces into account. pub fn thumbnail(image: &I, new_width: u32, new_height: u32) -> ImageBuffer> where I: GenericImageView, P: Pixel + 'static, S: Primitive + Enlargeable + 'static, { let (width, height) = image.dimensions(); let mut out = ImageBuffer::new(new_width, new_height); let x_ratio = width as f32 / new_width as f32; let y_ratio = height as f32 / new_height as f32; for outy in 0..new_height { let bottomf = outy as f32 * y_ratio; let topf = bottomf + y_ratio; let bottom = clamp(bottomf.ceil() as u32, 0, height - 1); let top = clamp(topf.ceil() as u32, bottom, height); for outx in 0..new_width { let leftf = outx as f32 * x_ratio; let rightf = leftf + x_ratio; let left = clamp(leftf.ceil() as u32, 0, width - 1); let right = clamp(rightf.ceil() as u32, left, width); let avg = if bottom != top && left != right { thumbnail_sample_block(image, left, right, bottom, top) } else if bottom != top { // && left == right // In the first column we have left == 0 and right > ceil(y_scale) > 0 so this // assertion can never trigger. debug_assert!( left > 0 && right > 0, "First output column must have corresponding pixels" ); let fraction_horizontal = (leftf.fract() + rightf.fract()) / 2.; thumbnail_sample_fraction_horizontal( image, right - 1, fraction_horizontal, bottom, top, ) } else if left != right { // && bottom == top // In the first line we have bottom == 0 and top > ceil(x_scale) > 0 so this // assertion can never trigger. debug_assert!( bottom > 0 && top > 0, "First output row must have corresponding pixels" ); let fraction_vertical = (topf.fract() + bottomf.fract()) / 2.; thumbnail_sample_fraction_vertical(image, left, right, top - 1, fraction_vertical) } else { // bottom == top && left == right let fraction_horizontal = (topf.fract() + bottomf.fract()) / 2.; let fraction_vertical = (leftf.fract() + rightf.fract()) / 2.; thumbnail_sample_fraction_both( image, right - 1, fraction_horizontal, top - 1, fraction_vertical, ) }; #[allow(deprecated)] let pixel = Pixel::from_channels(avg.0, avg.1, avg.2, avg.3); out.put_pixel(outx, outy, pixel); } } out } /// Get a pixel for a thumbnail where the input window encloses at least a full pixel. fn thumbnail_sample_block( image: &I, left: u32, right: u32, bottom: u32, top: u32, ) -> (S, S, S, S) where I: GenericImageView, P: Pixel, S: Primitive + Enlargeable, { let mut sum = ThumbnailSum::zeroed(); for y in bottom..top { for x in left..right { let k = image.get_pixel(x, y); sum.add_pixel(k); } } let n = ::from((right - left) * (top - bottom)).unwrap(); let round = ::from(n / NumCast::from(2).unwrap()).unwrap(); ( S::clamp_from((sum.0 + round) / n), S::clamp_from((sum.1 + round) / n), S::clamp_from((sum.2 + round) / n), S::clamp_from((sum.3 + round) / n), ) } /// Get a thumbnail pixel where the input window encloses at least a vertical pixel. fn thumbnail_sample_fraction_horizontal( image: &I, left: u32, fraction_horizontal: f32, bottom: u32, top: u32, ) -> (S, S, S, S) where I: GenericImageView, P: Pixel, S: Primitive + Enlargeable, { let fract = fraction_horizontal; let mut sum_left = ThumbnailSum::zeroed(); let mut sum_right = ThumbnailSum::zeroed(); for x in bottom..top { let k_left = image.get_pixel(left, x); sum_left.add_pixel(k_left); let k_right = image.get_pixel(left + 1, x); sum_right.add_pixel(k_right); } // Now we approximate: left/n*(1-fract) + right/n*fract let fact_right = fract / ((top - bottom) as f32); let fact_left = (1. - fract) / ((top - bottom) as f32); let mix_left_and_right = |leftv: S::Larger, rightv: S::Larger| { ::from( fact_left * leftv.to_f32().unwrap() + fact_right * rightv.to_f32().unwrap(), ) .expect("Average sample value should fit into sample type") }; ( mix_left_and_right(sum_left.0, sum_right.0), mix_left_and_right(sum_left.1, sum_right.1), mix_left_and_right(sum_left.2, sum_right.2), mix_left_and_right(sum_left.3, sum_right.3), ) } /// Get a thumbnail pixel where the input window encloses at least a horizontal pixel. fn thumbnail_sample_fraction_vertical( image: &I, left: u32, right: u32, bottom: u32, fraction_vertical: f32, ) -> (S, S, S, S) where I: GenericImageView, P: Pixel, S: Primitive + Enlargeable, { let fract = fraction_vertical; let mut sum_bot = ThumbnailSum::zeroed(); let mut sum_top = ThumbnailSum::zeroed(); for x in left..right { let k_bot = image.get_pixel(x, bottom); sum_bot.add_pixel(k_bot); let k_top = image.get_pixel(x, bottom + 1); sum_top.add_pixel(k_top); } // Now we approximate: bot/n*fract + top/n*(1-fract) let fact_top = fract / ((right - left) as f32); let fact_bot = (1. - fract) / ((right - left) as f32); let mix_bot_and_top = |botv: S::Larger, topv: S::Larger| { ::from(fact_bot * botv.to_f32().unwrap() + fact_top * topv.to_f32().unwrap()) .expect("Average sample value should fit into sample type") }; ( mix_bot_and_top(sum_bot.0, sum_top.0), mix_bot_and_top(sum_bot.1, sum_top.1), mix_bot_and_top(sum_bot.2, sum_top.2), mix_bot_and_top(sum_bot.3, sum_top.3), ) } /// Get a single pixel for a thumbnail where the input window does not enclose any full pixel. fn thumbnail_sample_fraction_both( image: &I, left: u32, fraction_vertical: f32, bottom: u32, fraction_horizontal: f32, ) -> (S, S, S, S) where I: GenericImageView, P: Pixel, S: Primitive + Enlargeable, { #[allow(deprecated)] let k_bl = image.get_pixel(left, bottom).channels4(); #[allow(deprecated)] let k_tl = image.get_pixel(left, bottom + 1).channels4(); #[allow(deprecated)] let k_br = image.get_pixel(left + 1, bottom).channels4(); #[allow(deprecated)] let k_tr = image.get_pixel(left + 1, bottom + 1).channels4(); let frac_v = fraction_vertical; let frac_h = fraction_horizontal; let fact_tr = frac_v * frac_h; let fact_tl = frac_v * (1. - frac_h); let fact_br = (1. - frac_v) * frac_h; let fact_bl = (1. - frac_v) * (1. - frac_h); let mix = |br: S, tr: S, bl: S, tl: S| { ::from( fact_br * br.to_f32().unwrap() + fact_tr * tr.to_f32().unwrap() + fact_bl * bl.to_f32().unwrap() + fact_tl * tl.to_f32().unwrap(), ) .expect("Average sample value should fit into sample type") }; ( mix(k_br.0, k_tr.0, k_bl.0, k_tl.0), mix(k_br.1, k_tr.1, k_bl.1, k_tl.1), mix(k_br.2, k_tr.2, k_bl.2, k_tl.2), mix(k_br.3, k_tr.3, k_bl.3, k_tl.3), ) } /// Perform a 3x3 box filter on the supplied image. /// ```kernel``` is an array of the filter weights of length 9. pub fn filter3x3(image: &I, kernel: &[f32]) -> ImageBuffer> where I: GenericImageView, P: Pixel + 'static, S: Primitive + 'static, { // The kernel's input positions relative to the current pixel. let taps: &[(isize, isize)] = &[ (-1, -1), (0, -1), (1, -1), (-1, 0), (0, 0), (1, 0), (-1, 1), (0, 1), (1, 1), ]; let (width, height) = image.dimensions(); let mut out = ImageBuffer::new(width, height); let max = S::DEFAULT_MAX_VALUE; let max: f32 = NumCast::from(max).unwrap(); let sum = match kernel.iter().fold(0.0, |s, &item| s + item) { x if x == 0.0 => 1.0, sum => sum, }; let sum = (sum, sum, sum, sum); for y in 1..height - 1 { for x in 1..width - 1 { let mut t = (0.0, 0.0, 0.0, 0.0); // TODO: There is no need to recalculate the kernel for each pixel. // Only a subtract and addition is needed for pixels after the first // in each row. for (&k, &(a, b)) in kernel.iter().zip(taps.iter()) { let k = (k, k, k, k); let x0 = x as isize + a; let y0 = y as isize + b; let p = image.get_pixel(x0 as u32, y0 as u32); #[allow(deprecated)] let (k1, k2, k3, k4) = p.channels4(); let vec: (f32, f32, f32, f32) = ( NumCast::from(k1).unwrap(), NumCast::from(k2).unwrap(), NumCast::from(k3).unwrap(), NumCast::from(k4).unwrap(), ); t.0 += vec.0 * k.0; t.1 += vec.1 * k.1; t.2 += vec.2 * k.2; t.3 += vec.3 * k.3; } let (t1, t2, t3, t4) = (t.0 / sum.0, t.1 / sum.1, t.2 / sum.2, t.3 / sum.3); #[allow(deprecated)] let t = Pixel::from_channels( NumCast::from(clamp(t1, 0.0, max)).unwrap(), NumCast::from(clamp(t2, 0.0, max)).unwrap(), NumCast::from(clamp(t3, 0.0, max)).unwrap(), NumCast::from(clamp(t4, 0.0, max)).unwrap(), ); out.put_pixel(x, y, t); } } out } /// Resize the supplied image to the specified dimensions. /// ```nwidth``` and ```nheight``` are the new dimensions. /// ```filter``` is the sampling filter to use. pub fn resize( image: &I, nwidth: u32, nheight: u32, filter: FilterType, ) -> ImageBuffer::Subpixel>> where I::Pixel: 'static, ::Subpixel: 'static, { // check if the new dimensions are the same as the old. if they are, make a copy instead of resampling if (nwidth, nheight) == image.dimensions() { let mut tmp = ImageBuffer::new(image.width(), image.height()); tmp.copy_from(image, 0, 0).unwrap(); return tmp; } let mut method = match filter { FilterType::Nearest => Filter { kernel: Box::new(box_kernel), support: 0.0, }, FilterType::Triangle => Filter { kernel: Box::new(triangle_kernel), support: 1.0, }, FilterType::CatmullRom => Filter { kernel: Box::new(catmullrom_kernel), support: 2.0, }, FilterType::Gaussian => Filter { kernel: Box::new(gaussian_kernel), support: 3.0, }, FilterType::Lanczos3 => Filter { kernel: Box::new(lanczos3_kernel), support: 3.0, }, }; // Note: tmp is not necessarily actually Rgba let tmp: Rgba32FImage = vertical_sample(image, nheight, &mut method); horizontal_sample(&tmp, nwidth, &mut method) } /// Performs a Gaussian blur on the supplied image. /// ```sigma``` is a measure of how much to blur by. pub fn blur( image: &I, sigma: f32, ) -> ImageBuffer::Subpixel>> where I::Pixel: 'static, { let sigma = if sigma <= 0.0 { 1.0 } else { sigma }; let mut method = Filter { kernel: Box::new(|x| gaussian(x, sigma)), support: 2.0 * sigma, }; let (width, height) = image.dimensions(); // Keep width and height the same for horizontal and // vertical sampling. // Note: tmp is not necessarily actually Rgba let tmp: Rgba32FImage = vertical_sample(image, height, &mut method); horizontal_sample(&tmp, width, &mut method) } /// Performs an unsharpen mask on the supplied image. /// ```sigma``` is the amount to blur the image by. /// ```threshold``` is the threshold for minimal brightness change that will be sharpened. /// /// See pub fn unsharpen(image: &I, sigma: f32, threshold: i32) -> ImageBuffer> where I: GenericImageView, P: Pixel + 'static, S: Primitive + 'static, { let mut tmp = blur(image, sigma); let max = S::DEFAULT_MAX_VALUE; let max: i32 = NumCast::from(max).unwrap(); let (width, height) = image.dimensions(); for y in 0..height { for x in 0..width { let a = image.get_pixel(x, y); let b = tmp.get_pixel_mut(x, y); let p = a.map2(b, |c, d| { let ic: i32 = NumCast::from(c).unwrap(); let id: i32 = NumCast::from(d).unwrap(); let diff = (ic - id).abs(); if diff > threshold { let e = clamp(ic + diff, 0, max); // FIXME what does this do for f32? clamp 0-1 integers?? NumCast::from(e).unwrap() } else { c } }); *b = p; } } tmp } #[cfg(test)] mod tests { use super::{resize, sample_bilinear, sample_nearest, FilterType}; use crate::{GenericImageView, ImageBuffer, RgbImage}; #[cfg(feature = "benchmarks")] use test; #[bench] #[cfg(all(feature = "benchmarks", feature = "png"))] fn bench_resize(b: &mut test::Bencher) { use std::path::Path; let img = crate::open(&Path::new("./examples/fractal.png")).unwrap(); b.iter(|| { test::black_box(resize(&img, 200, 200, FilterType::Nearest)); }); b.bytes = 800 * 800 * 3 + 200 * 200 * 3; } #[test] #[cfg(feature = "png")] fn test_resize_same_size() { use std::path::Path; let img = crate::open(&Path::new("./examples/fractal.png")).unwrap(); let resize = img.resize(img.width(), img.height(), FilterType::Triangle); assert!(img.pixels().eq(resize.pixels())) } #[test] #[cfg(feature = "png")] fn test_sample_bilinear() { use std::path::Path; let img = crate::open(&Path::new("./examples/fractal.png")).unwrap(); assert!(sample_bilinear(&img, 0., 0.).is_some()); assert!(sample_bilinear(&img, 1., 0.).is_some()); assert!(sample_bilinear(&img, 0., 1.).is_some()); assert!(sample_bilinear(&img, 1., 1.).is_some()); assert!(sample_bilinear(&img, 0.5, 0.5).is_some()); assert!(sample_bilinear(&img, 1.2, 0.5).is_none()); assert!(sample_bilinear(&img, 0.5, 1.2).is_none()); assert!(sample_bilinear(&img, 1.2, 1.2).is_none()); assert!(sample_bilinear(&img, -0.1, 0.2).is_none()); assert!(sample_bilinear(&img, 0.2, -0.1).is_none()); assert!(sample_bilinear(&img, -0.1, -0.1).is_none()); } #[test] #[cfg(feature = "png")] fn test_sample_nearest() { use std::path::Path; let img = crate::open(&Path::new("./examples/fractal.png")).unwrap(); assert!(sample_nearest(&img, 0., 0.).is_some()); assert!(sample_nearest(&img, 1., 0.).is_some()); assert!(sample_nearest(&img, 0., 1.).is_some()); assert!(sample_nearest(&img, 1., 1.).is_some()); assert!(sample_nearest(&img, 0.5, 0.5).is_some()); assert!(sample_nearest(&img, 1.2, 0.5).is_none()); assert!(sample_nearest(&img, 0.5, 1.2).is_none()); assert!(sample_nearest(&img, 1.2, 1.2).is_none()); assert!(sample_nearest(&img, -0.1, 0.2).is_none()); assert!(sample_nearest(&img, 0.2, -0.1).is_none()); assert!(sample_nearest(&img, -0.1, -0.1).is_none()); } #[test] fn test_sample_bilinear_correctness() { use crate::Rgba; let img = ImageBuffer::from_fn(2, 2, |x, y| match (x, y) { (0, 0) => Rgba([255, 0, 0, 0]), (0, 1) => Rgba([0, 255, 0, 0]), (1, 0) => Rgba([0, 0, 255, 0]), (1, 1) => Rgba([0, 0, 0, 255]), _ => panic!(), }); assert_eq!(sample_bilinear(&img, 0.5, 0.5), Some(Rgba([64; 4]))); assert_eq!(sample_bilinear(&img, 0.0, 0.0), Some(Rgba([255, 0, 0, 0]))); assert_eq!(sample_bilinear(&img, 0.0, 1.0), Some(Rgba([0, 255, 0, 0]))); assert_eq!(sample_bilinear(&img, 1.0, 0.0), Some(Rgba([0, 0, 255, 0]))); assert_eq!(sample_bilinear(&img, 1.0, 1.0), Some(Rgba([0, 0, 0, 255]))); assert_eq!( sample_bilinear(&img, 0.5, 0.0), Some(Rgba([128, 0, 128, 0])) ); assert_eq!( sample_bilinear(&img, 0.0, 0.5), Some(Rgba([128, 128, 0, 0])) ); assert_eq!( sample_bilinear(&img, 0.5, 1.0), Some(Rgba([0, 128, 0, 128])) ); assert_eq!( sample_bilinear(&img, 1.0, 0.5), Some(Rgba([0, 0, 128, 128])) ); } #[test] fn test_sample_nearest_correctness() { use crate::Rgba; let img = ImageBuffer::from_fn(2, 2, |x, y| match (x, y) { (0, 0) => Rgba([255, 0, 0, 0]), (0, 1) => Rgba([0, 255, 0, 0]), (1, 0) => Rgba([0, 0, 255, 0]), (1, 1) => Rgba([0, 0, 0, 255]), _ => panic!(), }); assert_eq!(sample_nearest(&img, 0.0, 0.0), Some(Rgba([255, 0, 0, 0]))); assert_eq!(sample_nearest(&img, 0.0, 1.0), Some(Rgba([0, 255, 0, 0]))); assert_eq!(sample_nearest(&img, 1.0, 0.0), Some(Rgba([0, 0, 255, 0]))); assert_eq!(sample_nearest(&img, 1.0, 1.0), Some(Rgba([0, 0, 0, 255]))); assert_eq!(sample_nearest(&img, 0.5, 0.5), Some(Rgba([0, 0, 0, 255]))); assert_eq!(sample_nearest(&img, 0.5, 0.0), Some(Rgba([0, 0, 255, 0]))); assert_eq!(sample_nearest(&img, 0.0, 0.5), Some(Rgba([0, 255, 0, 0]))); assert_eq!(sample_nearest(&img, 0.5, 1.0), Some(Rgba([0, 0, 0, 255]))); assert_eq!(sample_nearest(&img, 1.0, 0.5), Some(Rgba([0, 0, 0, 255]))); } #[bench] #[cfg(all(feature = "benchmarks", feature = "tiff"))] fn bench_resize_same_size(b: &mut test::Bencher) { let path = concat!( env!("CARGO_MANIFEST_DIR"), "/tests/images/tiff/testsuite/mandrill.tiff" ); let image = crate::open(path).unwrap(); b.iter(|| { test::black_box(image.resize(image.width(), image.height(), FilterType::CatmullRom)); }); b.bytes = (image.width() * image.height() * 3) as u64; } #[test] fn test_issue_186() { let img: RgbImage = ImageBuffer::new(100, 100); let _ = resize(&img, 50, 50, FilterType::Lanczos3); } #[bench] #[cfg(all(feature = "benchmarks", feature = "tiff"))] fn bench_thumbnail(b: &mut test::Bencher) { let path = concat!( env!("CARGO_MANIFEST_DIR"), "/tests/images/tiff/testsuite/mandrill.tiff" ); let image = crate::open(path).unwrap(); b.iter(|| { test::black_box(image.thumbnail(256, 256)); }); b.bytes = 512 * 512 * 4 + 256 * 256 * 4; } #[bench] #[cfg(all(feature = "benchmarks", feature = "tiff"))] fn bench_thumbnail_upsize(b: &mut test::Bencher) { let path = concat!( env!("CARGO_MANIFEST_DIR"), "/tests/images/tiff/testsuite/mandrill.tiff" ); let image = crate::open(path).unwrap().thumbnail(256, 256); b.iter(|| { test::black_box(image.thumbnail(512, 512)); }); b.bytes = 512 * 512 * 4 + 256 * 256 * 4; } #[bench] #[cfg(all(feature = "benchmarks", feature = "tiff"))] fn bench_thumbnail_upsize_irregular(b: &mut test::Bencher) { let path = concat!( env!("CARGO_MANIFEST_DIR"), "/tests/images/tiff/testsuite/mandrill.tiff" ); let image = crate::open(path).unwrap().thumbnail(193, 193); b.iter(|| { test::black_box(image.thumbnail(256, 256)); }); b.bytes = 193 * 193 * 4 + 256 * 256 * 4; } #[test] #[cfg(feature = "png")] fn resize_transparent_image() { use super::FilterType::{CatmullRom, Gaussian, Lanczos3, Nearest, Triangle}; use crate::imageops::crop_imm; use crate::RgbaImage; fn assert_resize(image: &RgbaImage, filter: FilterType) { let resized = resize(image, 16, 16, filter); let cropped = crop_imm(&resized, 5, 5, 6, 6).to_image(); for pixel in cropped.pixels() { let alpha = pixel.0[3]; assert!( alpha != 254 && alpha != 253, "alpha value: {}, {:?}", alpha, filter ); } } let path = concat!( env!("CARGO_MANIFEST_DIR"), "/tests/images/png/transparency/tp1n3p08.png" ); let img = crate::open(path).unwrap(); let rgba8 = img.as_rgba8().unwrap(); let filters = &[Nearest, Triangle, CatmullRom, Gaussian, Lanczos3]; for filter in filters { assert_resize(rgba8, *filter); } } #[test] fn bug_1600() { let image = crate::RgbaImage::from_raw(629, 627, vec![255; 629 * 627 * 4]).unwrap(); let result = resize(&image, 22, 22, FilterType::Lanczos3); assert!(result.into_raw().into_iter().any(|c| c != 0)); } } image-0.24.7/src/io/free_functions.rs000064400000000000000000000265751046102023000155740ustar 00000000000000use std::fs::File; use std::io::{BufRead, BufReader, BufWriter, Seek}; use std::path::Path; use std::u32; use crate::codecs::*; use crate::dynimage::DynamicImage; use crate::error::{ImageError, ImageFormatHint, ImageResult}; use crate::image; use crate::image::ImageFormat; #[allow(unused_imports)] // When no features are supported use crate::image::{ImageDecoder, ImageEncoder}; use crate::{ color, error::{UnsupportedError, UnsupportedErrorKind}, ImageOutputFormat, }; pub(crate) fn open_impl(path: &Path) -> ImageResult { let buffered_read = BufReader::new(File::open(path).map_err(ImageError::IoError)?); load(buffered_read, ImageFormat::from_path(path)?) } /// Create a new image from a Reader. /// /// Assumes the reader is already buffered. For optimal performance, /// consider wrapping the reader with a `BufReader::new()`. /// /// Try [`io::Reader`] for more advanced uses. /// /// [`io::Reader`]: io/struct.Reader.html #[allow(unused_variables)] // r is unused if no features are supported. pub fn load(r: R, format: ImageFormat) -> ImageResult { load_inner(r, super::Limits::default(), format) } pub(crate) trait DecoderVisitor { type Result; fn visit_decoder<'a, D: ImageDecoder<'a>>(self, decoder: D) -> ImageResult; } pub(crate) fn load_decoder( r: R, format: ImageFormat, limits: super::Limits, visitor: V, ) -> ImageResult { #[allow(unreachable_patterns)] // Default is unreachable if all features are supported. match format { #[cfg(feature = "avif-decoder")] image::ImageFormat::Avif => visitor.visit_decoder(avif::AvifDecoder::new(r)?), #[cfg(feature = "png")] image::ImageFormat::Png => visitor.visit_decoder(png::PngDecoder::with_limits(r, limits)?), #[cfg(feature = "gif")] image::ImageFormat::Gif => visitor.visit_decoder(gif::GifDecoder::new(r)?), #[cfg(feature = "jpeg")] image::ImageFormat::Jpeg => visitor.visit_decoder(jpeg::JpegDecoder::new(r)?), #[cfg(feature = "webp")] image::ImageFormat::WebP => visitor.visit_decoder(webp::WebPDecoder::new(r)?), #[cfg(feature = "tiff")] image::ImageFormat::Tiff => visitor.visit_decoder(tiff::TiffDecoder::new(r)?), #[cfg(feature = "tga")] image::ImageFormat::Tga => visitor.visit_decoder(tga::TgaDecoder::new(r)?), #[cfg(feature = "dds")] image::ImageFormat::Dds => visitor.visit_decoder(dds::DdsDecoder::new(r)?), #[cfg(feature = "bmp")] image::ImageFormat::Bmp => visitor.visit_decoder(bmp::BmpDecoder::new(r)?), #[cfg(feature = "ico")] image::ImageFormat::Ico => visitor.visit_decoder(ico::IcoDecoder::new(r)?), #[cfg(feature = "hdr")] image::ImageFormat::Hdr => visitor.visit_decoder(hdr::HdrAdapter::new(BufReader::new(r))?), #[cfg(feature = "exr")] image::ImageFormat::OpenExr => visitor.visit_decoder(openexr::OpenExrDecoder::new(r)?), #[cfg(feature = "pnm")] image::ImageFormat::Pnm => visitor.visit_decoder(pnm::PnmDecoder::new(r)?), #[cfg(feature = "farbfeld")] image::ImageFormat::Farbfeld => visitor.visit_decoder(farbfeld::FarbfeldDecoder::new(r)?), #[cfg(feature = "qoi")] image::ImageFormat::Qoi => visitor.visit_decoder(qoi::QoiDecoder::new(r)?), _ => Err(ImageError::Unsupported( ImageFormatHint::Exact(format).into(), )), } } pub(crate) fn load_inner( r: R, limits: super::Limits, format: ImageFormat, ) -> ImageResult { struct LoadVisitor(super::Limits); impl DecoderVisitor for LoadVisitor { type Result = DynamicImage; fn visit_decoder<'a, D: ImageDecoder<'a>>( self, mut decoder: D, ) -> ImageResult { let mut limits = self.0; // Check that we do not allocate a bigger buffer than we are allowed to // FIXME: should this rather go in `DynamicImage::from_decoder` somehow? limits.reserve(decoder.total_bytes())?; decoder.set_limits(limits)?; DynamicImage::from_decoder(decoder) } } load_decoder(r, format, limits.clone(), LoadVisitor(limits)) } pub(crate) fn image_dimensions_impl(path: &Path) -> ImageResult<(u32, u32)> { let format = image::ImageFormat::from_path(path)?; let reader = BufReader::new(File::open(path)?); image_dimensions_with_format_impl(reader, format) } #[allow(unused_variables)] // fin is unused if no features are supported. pub(crate) fn image_dimensions_with_format_impl( buffered_read: R, format: ImageFormat, ) -> ImageResult<(u32, u32)> { struct DimVisitor; impl DecoderVisitor for DimVisitor { type Result = (u32, u32); fn visit_decoder<'a, D: ImageDecoder<'a>>(self, decoder: D) -> ImageResult { Ok(decoder.dimensions()) } } load_decoder(buffered_read, format, super::Limits::default(), DimVisitor) } #[allow(unused_variables)] // Most variables when no features are supported pub(crate) fn save_buffer_impl( path: &Path, buf: &[u8], width: u32, height: u32, color: color::ColorType, ) -> ImageResult<()> { let format = ImageFormat::from_path(path)?; save_buffer_with_format_impl(path, buf, width, height, color, format) } #[allow(unused_variables)] // Most variables when no features are supported pub(crate) fn save_buffer_with_format_impl( path: &Path, buf: &[u8], width: u32, height: u32, color: color::ColorType, format: ImageFormat, ) -> ImageResult<()> { let buffered_file_write = &mut BufWriter::new(File::create(path)?); // always seekable let format = match format { #[cfg(feature = "pnm")] image::ImageFormat::Pnm => { let ext = path .extension() .and_then(|s| s.to_str()) .map_or("".to_string(), |s| s.to_ascii_lowercase()); ImageOutputFormat::Pnm(match &*ext { "pbm" => pnm::PnmSubtype::Bitmap(pnm::SampleEncoding::Binary), "pgm" => pnm::PnmSubtype::Graymap(pnm::SampleEncoding::Binary), "ppm" => pnm::PnmSubtype::Pixmap(pnm::SampleEncoding::Binary), "pam" => pnm::PnmSubtype::ArbitraryMap, _ => { return Err(ImageError::Unsupported( ImageFormatHint::Exact(format).into(), )) } // Unsupported Pnm subtype. }) } // #[cfg(feature = "hdr")] // image::ImageFormat::Hdr => hdr::HdrEncoder::new(fout).encode(&[Rgb], width, height), // usize format => format.into(), }; write_buffer_impl(buffered_file_write, buf, width, height, color, format) } #[allow(unused_variables)] // Most variables when no features are supported pub(crate) fn write_buffer_impl( buffered_write: &mut W, buf: &[u8], width: u32, height: u32, color: color::ColorType, format: ImageOutputFormat, ) -> ImageResult<()> { match format { #[cfg(feature = "png")] ImageOutputFormat::Png => { png::PngEncoder::new(buffered_write).write_image(buf, width, height, color) } #[cfg(feature = "jpeg")] ImageOutputFormat::Jpeg(quality) => { jpeg::JpegEncoder::new_with_quality(buffered_write, quality) .write_image(buf, width, height, color) } #[cfg(feature = "pnm")] ImageOutputFormat::Pnm(subtype) => pnm::PnmEncoder::new(buffered_write) .with_subtype(subtype) .write_image(buf, width, height, color), #[cfg(feature = "gif")] ImageOutputFormat::Gif => { gif::GifEncoder::new(buffered_write).encode(buf, width, height, color) } #[cfg(feature = "ico")] ImageOutputFormat::Ico => { ico::IcoEncoder::new(buffered_write).write_image(buf, width, height, color) } #[cfg(feature = "bmp")] ImageOutputFormat::Bmp => { bmp::BmpEncoder::new(buffered_write).write_image(buf, width, height, color) } #[cfg(feature = "farbfeld")] ImageOutputFormat::Farbfeld => { farbfeld::FarbfeldEncoder::new(buffered_write).write_image(buf, width, height, color) } #[cfg(feature = "tga")] ImageOutputFormat::Tga => { tga::TgaEncoder::new(buffered_write).write_image(buf, width, height, color) } #[cfg(feature = "exr")] ImageOutputFormat::OpenExr => { openexr::OpenExrEncoder::new(buffered_write).write_image(buf, width, height, color) } #[cfg(feature = "tiff")] ImageOutputFormat::Tiff => { tiff::TiffEncoder::new(buffered_write).write_image(buf, width, height, color) } #[cfg(feature = "avif-encoder")] ImageOutputFormat::Avif => { avif::AvifEncoder::new(buffered_write).write_image(buf, width, height, color) } #[cfg(feature = "qoi")] ImageOutputFormat::Qoi => { qoi::QoiEncoder::new(buffered_write).write_image(buf, width, height, color) } #[cfg(feature = "webp-encoder")] ImageOutputFormat::WebP => { webp::WebPEncoder::new(buffered_write).write_image(buf, width, height, color) } image::ImageOutputFormat::Unsupported(msg) => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormatHint::Unknown, UnsupportedErrorKind::Format(ImageFormatHint::Name(msg)), ), )), } } static MAGIC_BYTES: [(&[u8], ImageFormat); 23] = [ (b"\x89PNG\r\n\x1a\n", ImageFormat::Png), (&[0xff, 0xd8, 0xff], ImageFormat::Jpeg), (b"GIF89a", ImageFormat::Gif), (b"GIF87a", ImageFormat::Gif), (b"RIFF", ImageFormat::WebP), // TODO: better magic byte detection, see https://github.com/image-rs/image/issues/660 (b"MM\x00*", ImageFormat::Tiff), (b"II*\x00", ImageFormat::Tiff), (b"DDS ", ImageFormat::Dds), (b"BM", ImageFormat::Bmp), (&[0, 0, 1, 0], ImageFormat::Ico), (b"#?RADIANCE", ImageFormat::Hdr), (b"P1", ImageFormat::Pnm), (b"P2", ImageFormat::Pnm), (b"P3", ImageFormat::Pnm), (b"P4", ImageFormat::Pnm), (b"P5", ImageFormat::Pnm), (b"P6", ImageFormat::Pnm), (b"P7", ImageFormat::Pnm), (b"farbfeld", ImageFormat::Farbfeld), (b"\0\0\0 ftypavif", ImageFormat::Avif), (b"\0\0\0\x1cftypavif", ImageFormat::Avif), (&[0x76, 0x2f, 0x31, 0x01], ImageFormat::OpenExr), // = &exr::meta::magic_number::BYTES (b"qoif", ImageFormat::Qoi), ]; /// Guess image format from memory block /// /// Makes an educated guess about the image format based on the Magic Bytes at the beginning. /// TGA is not supported by this function. /// This is not to be trusted on the validity of the whole memory block pub fn guess_format(buffer: &[u8]) -> ImageResult { match guess_format_impl(buffer) { Some(format) => Ok(format), None => Err(ImageError::Unsupported(ImageFormatHint::Unknown.into())), } } pub(crate) fn guess_format_impl(buffer: &[u8]) -> Option { for &(signature, format) in &MAGIC_BYTES { if buffer.starts_with(signature) { return Some(format); } } None } image-0.24.7/src/io/mod.rs000064400000000000000000000135161046102023000133310ustar 00000000000000//! Input and output of images. use std::convert::TryFrom; use crate::{error, ImageError, ImageResult}; pub(crate) mod free_functions; mod reader; pub use self::reader::Reader; /// Set of supported strict limits for a decoder. #[derive(Clone, Debug, Eq, PartialEq, Hash)] #[allow(missing_copy_implementations)] #[allow(clippy::manual_non_exhaustive)] pub struct LimitSupport { _non_exhaustive: (), } #[allow(clippy::derivable_impls)] impl Default for LimitSupport { fn default() -> LimitSupport { LimitSupport { _non_exhaustive: (), } } } /// Resource limits for decoding. /// /// Limits can be either *strict* or *non-strict*. Non-strict limits are best-effort /// limits where the library does not guarantee that limit will not be exceeded. Do note /// that it is still considered a bug if a non-strict limit is exceeded, however as /// some of the underlying decoders do not support not support such limits one cannot /// rely on these limits being supported. For stric limits the library makes a stronger /// guarantee that the limit will not be exceeded. Exceeding a strict limit is considered /// a critical bug. If a decoder cannot guarantee that it will uphold a strict limit it /// *must* fail with `image::error::LimitErrorKind::Unsupported`. /// /// Currently the only strict limits supported are the `max_image_width` and `max_image_height` /// limits, however more will be added in the future. [`LimitSupport`] will default to support /// being false and decoders should enable support for the limits they support in /// [`ImageDecoder::set_limits`]. /// /// The limit check should only ever fail if a limit will be exceeded or an unsupported strict /// limit is used. /// /// [`LimitSupport`]: ./struct.LimitSupport.html /// [`ImageDecoder::set_limits`]: ../trait.ImageDecoder.html#method.set_limits #[derive(Clone, Debug, Eq, PartialEq, Hash)] #[allow(missing_copy_implementations)] #[allow(clippy::manual_non_exhaustive)] pub struct Limits { /// The maximum allowed image width. This limit is strict. The default is no limit. pub max_image_width: Option, /// The maximum allowed image height. This limit is strict. The default is no limit. pub max_image_height: Option, /// The maximum allowed sum of allocations allocated by the decoder at any one time excluding /// allocator overhead. This limit is non-strict by default and some decoders may ignore it. /// The default is 512MiB. pub max_alloc: Option, _non_exhaustive: (), } impl Default for Limits { fn default() -> Limits { Limits { max_image_width: None, max_image_height: None, max_alloc: Some(512 * 1024 * 1024), _non_exhaustive: (), } } } impl Limits { /// Disable all limits. pub fn no_limits() -> Limits { Limits { max_image_width: None, max_image_height: None, max_alloc: None, _non_exhaustive: (), } } /// This function checks that all currently set strict limits are supported. pub fn check_support(&self, _supported: &LimitSupport) -> ImageResult<()> { Ok(()) } /// This function checks the `max_image_width` and `max_image_height` limits given /// the image width and height. pub fn check_dimensions(&self, width: u32, height: u32) -> ImageResult<()> { if let Some(max_width) = self.max_image_width { if width > max_width { return Err(ImageError::Limits(error::LimitError::from_kind( error::LimitErrorKind::DimensionError, ))); } } if let Some(max_height) = self.max_image_height { if height > max_height { return Err(ImageError::Limits(error::LimitError::from_kind( error::LimitErrorKind::DimensionError, ))); } } Ok(()) } /// This function checks that the current limit allows for reserving the set amount /// of bytes, it then reduces the limit accordingly. pub fn reserve(&mut self, amount: u64) -> ImageResult<()> { if let Some(max_alloc) = self.max_alloc.as_mut() { if *max_alloc < amount { return Err(ImageError::Limits(error::LimitError::from_kind( error::LimitErrorKind::InsufficientMemory, ))); } *max_alloc -= amount; } Ok(()) } /// This function acts identically to [`reserve`], but takes a `usize` for convenience. pub fn reserve_usize(&mut self, amount: usize) -> ImageResult<()> { match u64::try_from(amount) { Ok(n) => self.reserve(n), Err(_) if self.max_alloc.is_some() => Err(ImageError::Limits( error::LimitError::from_kind(error::LimitErrorKind::InsufficientMemory), )), Err(_) => { // Out of bounds, but we weren't asked to consider any limit. Ok(()) } } } /// This function increases the `max_alloc` limit with amount. Should only be used /// together with [`reserve`]. /// /// [`reserve`]: #method.reserve pub fn free(&mut self, amount: u64) { if let Some(max_alloc) = self.max_alloc.as_mut() { *max_alloc = max_alloc.saturating_add(amount); } } /// This function acts identically to [`free`], but takes a `usize` for convenience. pub fn free_usize(&mut self, amount: usize) { match u64::try_from(amount) { Ok(n) => self.free(n), Err(_) if self.max_alloc.is_some() => { panic!("max_alloc is set, we should have exited earlier when the reserve failed"); } Err(_) => { // Out of bounds, but we weren't asked to consider any limit. } } } } image-0.24.7/src/io/reader.rs000064400000000000000000000202451046102023000140110ustar 00000000000000use std::fs::File; use std::io::{self, BufRead, BufReader, Cursor, Read, Seek, SeekFrom}; use std::path::Path; use crate::dynimage::DynamicImage; use crate::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; use crate::image::ImageFormat; use crate::{ImageError, ImageResult}; use super::free_functions; /// A multi-format image reader. /// /// Wraps an input reader to facilitate automatic detection of an image's format, appropriate /// decoding method, and dispatches into the set of supported [`ImageDecoder`] implementations. /// /// ## Usage /// /// Opening a file, deducing the format based on the file path automatically, and trying to decode /// the image contained can be performed by constructing the reader and immediately consuming it. /// /// ```no_run /// # use image::ImageError; /// # use image::io::Reader; /// # fn main() -> Result<(), ImageError> { /// let image = Reader::open("path/to/image.png")? /// .decode()?; /// # Ok(()) } /// ``` /// /// It is also possible to make a guess based on the content. This is especially handy if the /// source is some blob in memory and you have constructed the reader in another way. Here is an /// example with a `pnm` black-and-white subformat that encodes its pixel matrix with ascii values. /// /// ``` /// # use image::ImageError; /// # use image::io::Reader; /// # fn main() -> Result<(), ImageError> { /// use std::io::Cursor; /// use image::ImageFormat; /// /// let raw_data = b"P1 2 2\n\ /// 0 1\n\ /// 1 0\n"; /// /// let mut reader = Reader::new(Cursor::new(raw_data)) /// .with_guessed_format() /// .expect("Cursor io never fails"); /// assert_eq!(reader.format(), Some(ImageFormat::Pnm)); /// /// # #[cfg(feature = "pnm")] /// let image = reader.decode()?; /// # Ok(()) } /// ``` /// /// As a final fallback or if only a specific format must be used, the reader always allows manual /// specification of the supposed image format with [`set_format`]. /// /// [`set_format`]: #method.set_format /// [`ImageDecoder`]: ../trait.ImageDecoder.html pub struct Reader { /// The reader. Should be buffered. inner: R, /// The format, if one has been set or deduced. format: Option, /// Decoding limits limits: super::Limits, } impl Reader { /// Create a new image reader without a preset format. /// /// Assumes the reader is already buffered. For optimal performance, /// consider wrapping the reader with a `BufReader::new()`. /// /// It is possible to guess the format based on the content of the read object with /// [`with_guessed_format`], or to set the format directly with [`set_format`]. /// /// [`with_guessed_format`]: #method.with_guessed_format /// [`set_format`]: method.set_format pub fn new(buffered_reader: R) -> Self { Reader { inner: buffered_reader, format: None, limits: super::Limits::default(), } } /// Construct a reader with specified format. /// /// Assumes the reader is already buffered. For optimal performance, /// consider wrapping the reader with a `BufReader::new()`. pub fn with_format(buffered_reader: R, format: ImageFormat) -> Self { Reader { inner: buffered_reader, format: Some(format), limits: super::Limits::default(), } } /// Get the currently determined format. pub fn format(&self) -> Option { self.format } /// Supply the format as which to interpret the read image. pub fn set_format(&mut self, format: ImageFormat) { self.format = Some(format); } /// Remove the current information on the image format. /// /// Note that many operations require format information to be present and will return e.g. an /// `ImageError::Unsupported` when the image format has not been set. pub fn clear_format(&mut self) { self.format = None; } /// Disable all decoding limits. pub fn no_limits(&mut self) { self.limits = super::Limits::no_limits(); } /// Set a custom set of decoding limits. pub fn limits(&mut self, limits: super::Limits) { self.limits = limits; } /// Unwrap the reader. pub fn into_inner(self) -> R { self.inner } } impl Reader> { /// Open a file to read, format will be guessed from path. /// /// This will not attempt any io operation on the opened file. /// /// If you want to inspect the content for a better guess on the format, which does not depend /// on file extensions, follow this call with a call to [`with_guessed_format`]. /// /// [`with_guessed_format`]: #method.with_guessed_format pub fn open

(path: P) -> io::Result where P: AsRef, { Self::open_impl(path.as_ref()) } fn open_impl(path: &Path) -> io::Result { Ok(Reader { inner: BufReader::new(File::open(path)?), format: ImageFormat::from_path(path).ok(), limits: super::Limits::default(), }) } } impl Reader { /// Make a format guess based on the content, replacing it on success. /// /// Returns `Ok` with the guess if no io error occurs. Additionally, replaces the current /// format if the guess was successful. If the guess was unable to determine a format then /// the current format of the reader is unchanged. /// /// Returns an error if the underlying reader fails. The format is unchanged. The error is a /// `std::io::Error` and not `ImageError` since the only error case is an error when the /// underlying reader seeks. /// /// When an error occurs, the reader may not have been properly reset and it is potentially /// hazardous to continue with more io. /// /// ## Usage /// /// This supplements the path based type deduction from [`open`](Reader::open) with content based deduction. /// This is more common in Linux and UNIX operating systems and also helpful if the path can /// not be directly controlled. /// /// ```no_run /// # use image::ImageError; /// # use image::io::Reader; /// # fn main() -> Result<(), ImageError> { /// let image = Reader::open("image.unknown")? /// .with_guessed_format()? /// .decode()?; /// # Ok(()) } /// ``` pub fn with_guessed_format(mut self) -> io::Result { let format = self.guess_format()?; // Replace format if found, keep current state if not. self.format = format.or(self.format); Ok(self) } fn guess_format(&mut self) -> io::Result> { let mut start = [0; 16]; // Save current offset, read start, restore offset. let cur = self.inner.stream_position()?; let len = io::copy( // Accept shorter files but read at most 16 bytes. &mut self.inner.by_ref().take(16), &mut Cursor::new(&mut start[..]), )?; self.inner.seek(SeekFrom::Start(cur))?; Ok(free_functions::guess_format_impl(&start[..len as usize])) } /// Read the image dimensions. /// /// Uses the current format to construct the correct reader for the format. /// /// If no format was determined, returns an `ImageError::Unsupported`. pub fn into_dimensions(mut self) -> ImageResult<(u32, u32)> { let format = self.require_format()?; free_functions::image_dimensions_with_format_impl(self.inner, format) } /// Read the image (replaces `load`). /// /// Uses the current format to construct the correct reader for the format. /// /// If no format was determined, returns an `ImageError::Unsupported`. pub fn decode(mut self) -> ImageResult { let format = self.require_format()?; free_functions::load_inner(self.inner, self.limits, format) } fn require_format(&mut self) -> ImageResult { self.format.ok_or_else(|| { ImageError::Unsupported(UnsupportedError::from_format_and_kind( ImageFormatHint::Unknown, UnsupportedErrorKind::Format(ImageFormatHint::Unknown), )) }) } } image-0.24.7/src/lib.rs000064400000000000000000000241121046102023000127030ustar 00000000000000//! # Overview //! //! This crate provides native rust implementations of image encoding and decoding as well as some //! basic image manipulation functions. Additional documentation can currently also be found in the //! [README.md file which is most easily viewed on //! github](https://github.com/image-rs/image/blob/master/README.md). //! //! There are two core problems for which this library provides solutions: a unified interface for image //! encodings and simple generic buffers for their content. It's possible to use either feature //! without the other. The focus is on a small and stable set of common operations that can be //! supplemented by other specialized crates. The library also prefers safe solutions with few //! dependencies. //! //! # High level API //! //! Load images using [`io::Reader`]: //! //! ```rust,no_run //! use std::io::Cursor; //! use image::io::Reader as ImageReader; //! # fn main() -> Result<(), image::ImageError> { //! # let bytes = vec![0u8]; //! //! let img = ImageReader::open("myimage.png")?.decode()?; //! let img2 = ImageReader::new(Cursor::new(bytes)).with_guessed_format()?.decode()?; //! # Ok(()) //! # } //! ``` //! //! And save them using [`save`] or [`write_to`] methods: //! //! ```rust,no_run //! # use std::io::{Write, Cursor}; //! # use image::{DynamicImage, ImageOutputFormat}; //! # #[cfg(feature = "png")] //! # fn main() -> Result<(), image::ImageError> { //! # let img: DynamicImage = unimplemented!(); //! # let img2: DynamicImage = unimplemented!(); //! img.save("empty.jpg")?; //! //! let mut bytes: Vec = Vec::new(); //! img2.write_to(&mut Cursor::new(&mut bytes), image::ImageOutputFormat::Png)?; //! # Ok(()) //! # } //! # #[cfg(not(feature = "png"))] fn main() {} //! ``` //! //! With default features, the crate includes support for [many common image formats](codecs/index.html#supported-formats). //! //! [`save`]: enum.DynamicImage.html#method.save //! [`write_to`]: enum.DynamicImage.html#method.write_to //! [`io::Reader`]: io/struct.Reader.html //! //! # Image buffers //! //! The two main types for storing images: //! * [`ImageBuffer`] which holds statically typed image contents. //! * [`DynamicImage`] which is an enum over the supported ImageBuffer formats //! and supports conversions between them. //! //! As well as a few more specialized options: //! * [`GenericImage`] trait for a mutable image buffer. //! * [`GenericImageView`] trait for read only references to a GenericImage. //! * [`flat`] module containing types for interoperability with generic channel //! matrices and foreign interfaces. //! //! [`GenericImageView`]: trait.GenericImageView.html //! [`GenericImage`]: trait.GenericImage.html //! [`ImageBuffer`]: struct.ImageBuffer.html //! [`DynamicImage`]: enum.DynamicImage.html //! [`flat`]: flat/index.html //! //! # Low level encoding/decoding API //! //! Implementations of [`ImageEncoder`] provides low level control over encoding: //! ```rust,no_run //! # use std::io::Write; //! # use image::DynamicImage; //! # use image::ImageEncoder; //! # #[cfg(feature = "jpeg")] //! # fn main() -> Result<(), image::ImageError> { //! # use image::codecs::jpeg::JpegEncoder; //! # let img: DynamicImage = unimplemented!(); //! # let writer: Box = unimplemented!(); //! let encoder = JpegEncoder::new_with_quality(&mut writer, 95); //! img.write_with_encoder(encoder)?; //! # Ok(()) //! # } //! # #[cfg(not(feature = "jpeg"))] fn main() {} //! ``` //! While [`ImageDecoder`] and [`ImageDecoderRect`] give access to more advanced decoding options: //! //! ```rust,no_run //! # use std::io::Read; //! # use image::DynamicImage; //! # use image::ImageDecoder; //! # #[cfg(feature = "png")] //! # fn main() -> Result<(), image::ImageError> { //! # use image::codecs::png::PngDecoder; //! # let img: DynamicImage = unimplemented!(); //! # let reader: Box = unimplemented!(); //! let decoder = PngDecoder::new(&mut reader)?; //! let icc = decoder.icc_profile(); //! let img = DynamicImage::from_decoder(decoder)?; //! # Ok(()) //! # } //! # #[cfg(not(feature = "png"))] fn main() {} //! ``` //! //! [`DynamicImage::from_decoder`]: enum.DynamicImage.html#method.from_decoder //! [`ImageDecoderRect`]: trait.ImageDecoderRect.html //! [`ImageDecoder`]: trait.ImageDecoder.html //! [`ImageEncoder`]: trait.ImageEncoder.html #![warn(missing_docs)] #![warn(unused_qualifications)] #![deny(unreachable_pub)] #![deny(deprecated)] #![deny(missing_copy_implementations)] #![cfg_attr(all(test, feature = "benchmarks"), feature(test))] // it's a bit of a pain otherwise #![allow(clippy::many_single_char_names)] // it's a backwards compatibility break #![allow(clippy::wrong_self_convention, clippy::enum_variant_names)] #[cfg(all(test, feature = "benchmarks"))] extern crate test; #[cfg(test)] #[macro_use] extern crate quickcheck; pub use crate::color::{ColorType, ExtendedColorType}; pub use crate::color::{Luma, LumaA, Rgb, Rgba}; pub use crate::error::{ImageError, ImageResult}; pub use crate::image::{ AnimationDecoder, GenericImage, GenericImageView, ImageDecoder, ImageDecoderRect, ImageEncoder, ImageFormat, ImageOutputFormat, // Iterators Pixels, Progress, SubImage, }; pub use crate::buffer_::{ GrayAlphaImage, GrayImage, // Image types ImageBuffer, Rgb32FImage, RgbImage, Rgba32FImage, RgbaImage, }; pub use crate::flat::FlatSamples; // Traits pub use crate::traits::{EncodableLayout, Pixel, PixelWithColorType, Primitive}; // Opening and loading images pub use crate::dynimage::{ image_dimensions, load_from_memory, load_from_memory_with_format, open, save_buffer, save_buffer_with_format, write_buffer_with_format, }; pub use crate::io::free_functions::{guess_format, load}; pub use crate::dynimage::DynamicImage; pub use crate::animation::{Delay, Frame, Frames}; // More detailed error type pub mod error; /// Iterators and other auxiliary structure for the `ImageBuffer` type. pub mod buffer { // Only those not exported at the top-level pub use crate::buffer_::{ ConvertBuffer, EnumeratePixels, EnumeratePixelsMut, EnumerateRows, EnumerateRowsMut, Pixels, PixelsMut, Rows, RowsMut, }; } // Math utils pub mod math; // Image processing functions pub mod imageops; // Io bindings pub mod io; // Buffer representations for ffi. pub mod flat; /// Encoding and decoding for various image file formats. /// /// # Supported formats /// /// /// /// | Format | Decoding | Encoding | /// | ------ | -------- | -------- | /// | AVIF | Only 8-bit | Lossy | /// | BMP | Yes | Rgb8, Rgba8, Gray8, GrayA8 | /// | DDS | DXT1, DXT3, DXT5 | No | /// | Farbfeld | Yes | Yes | /// | GIF | Yes | Yes | /// | ICO | Yes | Yes | /// | JPEG | Baseline and progressive | Baseline JPEG | /// | OpenEXR | Rgb32F, Rgba32F (no dwa compression) | Rgb32F, Rgba32F (no dwa compression) | /// | PNG | All supported color types | Same as decoding | /// | PNM | PBM, PGM, PPM, standard PAM | Yes | /// | QOI | Yes | Yes | /// | TGA | Yes | Rgb8, Rgba8, Bgr8, Bgra8, Gray8, GrayA8 | /// | TIFF | Baseline(no fax support) + LZW + PackBits | Rgb8, Rgba8, Gray8 | /// | WebP | Yes | Rgb8, Rgba8 | /// /// ## A note on format specific features /// /// One of the main goals of `image` is stability, in runtime but also for programmers. This /// ensures that performance as well as safety fixes reach a majority of its user base with little /// effort. Re-exporting all details of its dependencies would run counter to this goal as it /// linked _all_ major version bumps between them and `image`. As such, we are wary of exposing too /// many details, or configuration options, that are not shared between different image formats. /// /// Nevertheless, the advantage of precise control is hard to ignore. We will thus consider /// _wrappers_, not direct re-exports, in either of the following cases: /// /// 1. A standard specifies that configuration _x_ is required for decoders/encoders and there /// exists an essentially canonical way to control it. /// 2. At least two different implementations agree on some (sub-)set of features in practice. /// 3. A technical argument including measurements of the performance, space benefits, or otherwise /// objectively quantified benefits can be made, and the added interface is unlikely to require /// breaking changes. /// /// Features that fulfill two or more criteria are preferred. /// /// Re-exports of dependencies that reach version `1` will be discussed when it happens. pub mod codecs { #[cfg(any(feature = "avif-encoder", feature = "avif-decoder"))] pub mod avif; #[cfg(feature = "bmp")] pub mod bmp; #[cfg(feature = "dds")] pub mod dds; #[cfg(feature = "dxt")] #[deprecated = "DXT support will be removed or reworked in a future version. Prefer the `squish` crate instead. See https://github.com/image-rs/image/issues/1623"] pub mod dxt; #[cfg(feature = "farbfeld")] pub mod farbfeld; #[cfg(feature = "gif")] pub mod gif; #[cfg(feature = "hdr")] pub mod hdr; #[cfg(feature = "ico")] pub mod ico; #[cfg(feature = "jpeg")] pub mod jpeg; #[cfg(feature = "exr")] pub mod openexr; #[cfg(feature = "png")] pub mod png; #[cfg(feature = "pnm")] pub mod pnm; #[cfg(feature = "qoi")] pub mod qoi; #[cfg(feature = "tga")] pub mod tga; #[cfg(feature = "tiff")] pub mod tiff; #[cfg(any(feature = "webp", feature = "webp-encoder"))] pub mod webp; } mod animation; #[path = "buffer.rs"] mod buffer_; mod color; mod dynimage; mod image; mod traits; mod utils; // Can't use the macro-call itself within the `doc` attribute. So force it to eval it as part of // the macro invocation. // // The inspiration for the macro and implementation is from // // // MIT License // // Copyright (c) 2018 Guillaume Gomez macro_rules! insert_as_doc { { $content:expr } => { #[allow(unused_doc_comments)] #[doc = $content] extern { } } } // Provides the README.md as doc, to ensure the example works! insert_as_doc!(include_str!("../README.md")); image-0.24.7/src/math/mod.rs000064400000000000000000000002061046102023000136430ustar 00000000000000//! Mathematical helper functions and types. mod rect; mod utils; pub use self::rect::Rect; pub(super) use utils::resize_dimensions; image-0.24.7/src/math/rect.rs000064400000000000000000000005631046102023000140270ustar 00000000000000/// A Rectangle defined by its top left corner, width and height. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct Rect { /// The x coordinate of the top left corner. pub x: u32, /// The y coordinate of the top left corner. pub y: u32, /// The rectangle's width. pub width: u32, /// The rectangle's height. pub height: u32, } image-0.24.7/src/math/utils.rs000064400000000000000000000105661046102023000142360ustar 00000000000000//! Shared mathematical utility functions. use std::cmp::max; /// Calculates the width and height an image should be resized to. /// This preserves aspect ratio, and based on the `fill` parameter /// will either fill the dimensions to fit inside the smaller constraint /// (will overflow the specified bounds on one axis to preserve /// aspect ratio), or will shrink so that both dimensions are /// completely contained within the given `width` and `height`, /// with empty space on one axis. pub(crate) fn resize_dimensions( width: u32, height: u32, nwidth: u32, nheight: u32, fill: bool, ) -> (u32, u32) { let wratio = nwidth as f64 / width as f64; let hratio = nheight as f64 / height as f64; let ratio = if fill { f64::max(wratio, hratio) } else { f64::min(wratio, hratio) }; let nw = max((width as f64 * ratio).round() as u64, 1); let nh = max((height as f64 * ratio).round() as u64, 1); if nw > u64::from(u32::MAX) { let ratio = u32::MAX as f64 / width as f64; (u32::MAX, max((height as f64 * ratio).round() as u32, 1)) } else if nh > u64::from(u32::MAX) { let ratio = u32::MAX as f64 / height as f64; (max((width as f64 * ratio).round() as u32, 1), u32::MAX) } else { (nw as u32, nh as u32) } } #[cfg(test)] mod test { quickcheck! { fn resize_bounds_correctly_width(old_w: u32, new_w: u32) -> bool { if old_w == 0 || new_w == 0 { return true; } // In this case, the scaling is limited by scaling of height. // We could check that case separately but it does not conform to the same expectation. if new_w as u64 * 400u64 >= old_w as u64 * u64::from(u32::MAX) { return true; } let result = super::resize_dimensions(old_w, 400, new_w, ::std::u32::MAX, false); let exact = (400 as f64 * new_w as f64 / old_w as f64).round() as u32; result.0 == new_w && result.1 == exact.max(1) } } quickcheck! { fn resize_bounds_correctly_height(old_h: u32, new_h: u32) -> bool { if old_h == 0 || new_h == 0 { return true; } // In this case, the scaling is limited by scaling of width. // We could check that case separately but it does not conform to the same expectation. if 400u64 * new_h as u64 >= old_h as u64 * u64::from(u32::MAX) { return true; } let result = super::resize_dimensions(400, old_h, ::std::u32::MAX, new_h, false); let exact = (400 as f64 * new_h as f64 / old_h as f64).round() as u32; result.1 == new_h && result.0 == exact.max(1) } } #[test] fn resize_handles_fill() { let result = super::resize_dimensions(100, 200, 200, 500, true); assert!(result.0 == 250); assert!(result.1 == 500); let result = super::resize_dimensions(200, 100, 500, 200, true); assert!(result.0 == 500); assert!(result.1 == 250); } #[test] fn resize_never_rounds_to_zero() { let result = super::resize_dimensions(1, 150, 128, 128, false); assert!(result.0 > 0); assert!(result.1 > 0); } #[test] fn resize_handles_overflow() { let result = super::resize_dimensions(100, ::std::u32::MAX, 200, ::std::u32::MAX, true); assert!(result.0 == 100); assert!(result.1 == ::std::u32::MAX); let result = super::resize_dimensions(::std::u32::MAX, 100, ::std::u32::MAX, 200, true); assert!(result.0 == ::std::u32::MAX); assert!(result.1 == 100); } #[test] fn resize_rounds() { // Only truncation will result in (3840, 2229) and (2160, 3719) let result = super::resize_dimensions(4264, 2476, 3840, 2160, true); assert_eq!(result, (3840, 2230)); let result = super::resize_dimensions(2476, 4264, 2160, 3840, false); assert_eq!(result, (2160, 3720)); } #[test] fn resize_handles_zero() { let result = super::resize_dimensions(0, 100, 100, 100, false); assert_eq!(result, (1, 100)); let result = super::resize_dimensions(100, 0, 100, 100, false); assert_eq!(result, (100, 1)); let result = super::resize_dimensions(100, 100, 0, 100, false); assert_eq!(result, (1, 1)); let result = super::resize_dimensions(100, 100, 100, 0, false); assert_eq!(result, (1, 1)); } } image-0.24.7/src/traits.rs000064400000000000000000000262241046102023000134510ustar 00000000000000//! This module provides useful traits that were deprecated in rust // Note copied from the stdlib under MIT license use num_traits::{Bounded, Num, NumCast}; use std::ops::AddAssign; use crate::color::{ColorType, Luma, LumaA, Rgb, Rgba}; /// Types which are safe to treat as an immutable byte slice in a pixel layout /// for image encoding. pub trait EncodableLayout: seals::EncodableLayout { /// Get the bytes of this value. fn as_bytes(&self) -> &[u8]; } impl EncodableLayout for [u8] { fn as_bytes(&self) -> &[u8] { bytemuck::cast_slice(self) } } impl EncodableLayout for [u16] { fn as_bytes(&self) -> &[u8] { bytemuck::cast_slice(self) } } impl EncodableLayout for [f32] { fn as_bytes(&self) -> &[u8] { bytemuck::cast_slice(self) } } /// The type of each channel in a pixel. For example, this can be `u8`, `u16`, `f32`. // TODO rename to `PixelComponent`? Split up into separate traits? Seal? pub trait Primitive: Copy + NumCast + Num + PartialOrd + Clone + Bounded { /// The maximum value for this type of primitive within the context of color. /// For floats, the maximum is `1.0`, whereas the integer types inherit their usual maximum values. const DEFAULT_MAX_VALUE: Self; /// The minimum value for this type of primitive within the context of color. /// For floats, the minimum is `0.0`, whereas the integer types inherit their usual minimum values. const DEFAULT_MIN_VALUE: Self; } macro_rules! declare_primitive { ($base:ty: ($from:expr)..$to:expr) => { impl Primitive for $base { const DEFAULT_MAX_VALUE: Self = $to; const DEFAULT_MIN_VALUE: Self = $from; } }; } declare_primitive!(usize: (0)..Self::MAX); declare_primitive!(u8: (0)..Self::MAX); declare_primitive!(u16: (0)..Self::MAX); declare_primitive!(u32: (0)..Self::MAX); declare_primitive!(u64: (0)..Self::MAX); declare_primitive!(isize: (Self::MIN)..Self::MAX); declare_primitive!(i8: (Self::MIN)..Self::MAX); declare_primitive!(i16: (Self::MIN)..Self::MAX); declare_primitive!(i32: (Self::MIN)..Self::MAX); declare_primitive!(i64: (Self::MIN)..Self::MAX); declare_primitive!(f32: (0.0)..1.0); declare_primitive!(f64: (0.0)..1.0); /// An Enlargable::Larger value should be enough to calculate /// the sum (average) of a few hundred or thousand Enlargeable values. pub trait Enlargeable: Sized + Bounded + NumCast { type Larger: Copy + NumCast + Num + PartialOrd + Clone + Bounded + AddAssign; fn clamp_from(n: Self::Larger) -> Self { if n > Self::max_value().to_larger() { Self::max_value() } else if n < Self::min_value().to_larger() { Self::min_value() } else { NumCast::from(n).unwrap() } } fn to_larger(self) -> Self::Larger { NumCast::from(self).unwrap() } } impl Enlargeable for u8 { type Larger = u32; } impl Enlargeable for u16 { type Larger = u32; } impl Enlargeable for u32 { type Larger = u64; } impl Enlargeable for u64 { type Larger = u128; } impl Enlargeable for usize { // Note: On 32-bit architectures, u64 should be enough here. type Larger = u128; } impl Enlargeable for i8 { type Larger = i32; } impl Enlargeable for i16 { type Larger = i32; } impl Enlargeable for i32 { type Larger = i64; } impl Enlargeable for i64 { type Larger = i128; } impl Enlargeable for isize { // Note: On 32-bit architectures, i64 should be enough here. type Larger = i128; } impl Enlargeable for f32 { type Larger = f64; } impl Enlargeable for f64 { type Larger = f64; } /// Linear interpolation without involving floating numbers. pub trait Lerp: Bounded + NumCast { type Ratio: Primitive; fn lerp(a: Self, b: Self, ratio: Self::Ratio) -> Self { let a = ::from(a).unwrap(); let b = ::from(b).unwrap(); let res = a + (b - a) * ratio; if res > NumCast::from(Self::max_value()).unwrap() { Self::max_value() } else if res < NumCast::from(0).unwrap() { NumCast::from(0).unwrap() } else { NumCast::from(res).unwrap() } } } impl Lerp for u8 { type Ratio = f32; } impl Lerp for u16 { type Ratio = f32; } impl Lerp for u32 { type Ratio = f64; } impl Lerp for f32 { type Ratio = f32; fn lerp(a: Self, b: Self, ratio: Self::Ratio) -> Self { a + (b - a) * ratio } } /// The pixel with an associated `ColorType`. /// Not all possible pixels represent one of the predefined `ColorType`s. pub trait PixelWithColorType: Pixel + self::private::SealedPixelWithColorType { /// This pixel has the format of one of the predefined `ColorType`s, /// such as `Rgb8`, `La16` or `Rgba32F`. /// This is needed for automatically detecting /// a color format when saving an image as a file. const COLOR_TYPE: ColorType; } impl PixelWithColorType for Rgb { const COLOR_TYPE: ColorType = ColorType::Rgb8; } impl PixelWithColorType for Rgb { const COLOR_TYPE: ColorType = ColorType::Rgb16; } impl PixelWithColorType for Rgb { const COLOR_TYPE: ColorType = ColorType::Rgb32F; } impl PixelWithColorType for Rgba { const COLOR_TYPE: ColorType = ColorType::Rgba8; } impl PixelWithColorType for Rgba { const COLOR_TYPE: ColorType = ColorType::Rgba16; } impl PixelWithColorType for Rgba { const COLOR_TYPE: ColorType = ColorType::Rgba32F; } impl PixelWithColorType for Luma { const COLOR_TYPE: ColorType = ColorType::L8; } impl PixelWithColorType for Luma { const COLOR_TYPE: ColorType = ColorType::L16; } impl PixelWithColorType for LumaA { const COLOR_TYPE: ColorType = ColorType::La8; } impl PixelWithColorType for LumaA { const COLOR_TYPE: ColorType = ColorType::La16; } /// Prevents down-stream users from implementing the `Primitive` trait mod private { use crate::color::*; pub trait SealedPixelWithColorType {} impl SealedPixelWithColorType for Rgb {} impl SealedPixelWithColorType for Rgb {} impl SealedPixelWithColorType for Rgb {} impl SealedPixelWithColorType for Rgba {} impl SealedPixelWithColorType for Rgba {} impl SealedPixelWithColorType for Rgba {} impl SealedPixelWithColorType for Luma {} impl SealedPixelWithColorType for LumaA {} impl SealedPixelWithColorType for Luma {} impl SealedPixelWithColorType for LumaA {} } /// A generalized pixel. /// /// A pixel object is usually not used standalone but as a view into an image buffer. pub trait Pixel: Copy + Clone { /// The scalar type that is used to store each channel in this pixel. type Subpixel: Primitive; /// The number of channels of this pixel type. const CHANNEL_COUNT: u8; /// Returns the components as a slice. fn channels(&self) -> &[Self::Subpixel]; /// Returns the components as a mutable slice fn channels_mut(&mut self) -> &mut [Self::Subpixel]; /// A string that can help to interpret the meaning each channel /// See [gimp babl](http://gegl.org/babl/). const COLOR_MODEL: &'static str; /// Returns the channels of this pixel as a 4 tuple. If the pixel /// has less than 4 channels the remainder is filled with the maximum value #[deprecated(since = "0.24.0", note = "Use `channels()` or `channels_mut()`")] fn channels4( &self, ) -> ( Self::Subpixel, Self::Subpixel, Self::Subpixel, Self::Subpixel, ); /// Construct a pixel from the 4 channels a, b, c and d. /// If the pixel does not contain 4 channels the extra are ignored. #[deprecated( since = "0.24.0", note = "Use the constructor of the pixel, for example `Rgba([r,g,b,a])` or `Pixel::from_slice`" )] fn from_channels( a: Self::Subpixel, b: Self::Subpixel, c: Self::Subpixel, d: Self::Subpixel, ) -> Self; /// Returns a view into a slice. /// /// Note: The slice length is not checked on creation. Thus the caller has to ensure /// that the slice is long enough to prevent panics if the pixel is used later on. fn from_slice(slice: &[Self::Subpixel]) -> &Self; /// Returns mutable view into a mutable slice. /// /// Note: The slice length is not checked on creation. Thus the caller has to ensure /// that the slice is long enough to prevent panics if the pixel is used later on. fn from_slice_mut(slice: &mut [Self::Subpixel]) -> &mut Self; /// Convert this pixel to RGB fn to_rgb(&self) -> Rgb; /// Convert this pixel to RGB with an alpha channel fn to_rgba(&self) -> Rgba; /// Convert this pixel to luma fn to_luma(&self) -> Luma; /// Convert this pixel to luma with an alpha channel fn to_luma_alpha(&self) -> LumaA; /// Apply the function ```f``` to each channel of this pixel. fn map(&self, f: F) -> Self where F: FnMut(Self::Subpixel) -> Self::Subpixel; /// Apply the function ```f``` to each channel of this pixel. fn apply(&mut self, f: F) where F: FnMut(Self::Subpixel) -> Self::Subpixel; /// Apply the function ```f``` to each channel except the alpha channel. /// Apply the function ```g``` to the alpha channel. fn map_with_alpha(&self, f: F, g: G) -> Self where F: FnMut(Self::Subpixel) -> Self::Subpixel, G: FnMut(Self::Subpixel) -> Self::Subpixel; /// Apply the function ```f``` to each channel except the alpha channel. /// Apply the function ```g``` to the alpha channel. Works in-place. fn apply_with_alpha(&mut self, f: F, g: G) where F: FnMut(Self::Subpixel) -> Self::Subpixel, G: FnMut(Self::Subpixel) -> Self::Subpixel; /// Apply the function ```f``` to each channel except the alpha channel. fn map_without_alpha(&self, f: F) -> Self where F: FnMut(Self::Subpixel) -> Self::Subpixel, { let mut this = *self; this.apply_with_alpha(f, |x| x); this } /// Apply the function ```f``` to each channel except the alpha channel. /// Works in place. fn apply_without_alpha(&mut self, f: F) where F: FnMut(Self::Subpixel) -> Self::Subpixel, { self.apply_with_alpha(f, |x| x); } /// Apply the function ```f``` to each channel of this pixel and /// ```other``` pairwise. fn map2(&self, other: &Self, f: F) -> Self where F: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel; /// Apply the function ```f``` to each channel of this pixel and /// ```other``` pairwise. Works in-place. fn apply2(&mut self, other: &Self, f: F) where F: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel; /// Invert this pixel fn invert(&mut self); /// Blend the color of a given pixel into ourself, taking into account alpha channels fn blend(&mut self, other: &Self); } /// Private module for supertraits of sealed traits. mod seals { pub trait EncodableLayout {} impl EncodableLayout for [u8] {} impl EncodableLayout for [u16] {} impl EncodableLayout for [f32] {} } image-0.24.7/src/utils/mod.rs000064400000000000000000000074341046102023000140640ustar 00000000000000//! Utilities use std::iter::repeat; #[inline(always)] pub(crate) fn expand_packed(buf: &mut [u8], channels: usize, bit_depth: u8, mut func: F) where F: FnMut(u8, &mut [u8]), { let pixels = buf.len() / channels * bit_depth as usize; let extra = pixels % 8; let entries = pixels / 8 + match extra { 0 => 0, _ => 1, }; let mask = ((1u16 << bit_depth) - 1) as u8; let i = (0..entries) .rev() // Reverse iterator .flat_map(|idx| // This has to be reversed to (0..8/bit_depth).map(|i| i*bit_depth).zip(repeat(idx))) .skip(extra); let buf_len = buf.len(); let j_inv = (channels..buf_len).step_by(channels); for ((shift, i), j_inv) in i.zip(j_inv) { let j = buf_len - j_inv; let pixel = (buf[i] & (mask << shift)) >> shift; func(pixel, &mut buf[j..(j + channels)]) } } /// Expand a buffer of packed 1, 2, or 4 bits integers into u8's. Assumes that /// every `row_size` entries there are padding bits up to the next byte boundary. #[allow(dead_code)] // When no image formats that use it are enabled pub(crate) fn expand_bits(bit_depth: u8, row_size: u32, buf: &[u8]) -> Vec { // Note: this conversion assumes that the scanlines begin on byte boundaries let mask = (1u8 << bit_depth as usize) - 1; let scaling_factor = 255 / ((1 << bit_depth as usize) - 1); let bit_width = row_size * u32::from(bit_depth); let skip = if bit_width % 8 == 0 { 0 } else { (8 - bit_width % 8) / u32::from(bit_depth) }; let row_len = row_size + skip; let mut p = Vec::new(); let mut i = 0; for v in buf { for shift_inv in 1..=8 / bit_depth { let shift = 8 - bit_depth * shift_inv; // skip the pixels that can be neglected because scanlines should // start at byte boundaries if i % (row_len as usize) < (row_size as usize) { let pixel = (v & mask << shift as usize) >> shift as usize; p.push(pixel * scaling_factor); } i += 1; } } p } /// Checks if the provided dimensions would cause an overflow. #[allow(dead_code)] // When no image formats that use it are enabled pub(crate) fn check_dimension_overflow(width: u32, height: u32, bytes_per_pixel: u8) -> bool { width as u64 * height as u64 > std::u64::MAX / bytes_per_pixel as u64 } #[allow(dead_code)] // When no image formats that use it are enabled pub(crate) fn vec_copy_to_u8(vec: &[T]) -> Vec where T: bytemuck::Pod, { bytemuck::cast_slice(vec).to_owned() } #[inline] pub(crate) fn clamp(a: N, min: N, max: N) -> N where N: PartialOrd, { if a < min { min } else if a > max { max } else { a } } #[cfg(test)] mod test { #[test] fn gray_to_luma8_skip() { let check = |bit_depth, w, from, to| { assert_eq!(super::expand_bits(bit_depth, w, from), to); }; // Bit depth 1, skip is more than half a byte check( 1, 10, &[0b11110000, 0b11000000, 0b00001111, 0b11000000], vec![ 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, ], ); // Bit depth 2, skip is more than half a byte check( 2, 5, &[0b11110000, 0b11000000, 0b00001111, 0b11000000], vec![255, 255, 0, 0, 255, 0, 0, 255, 255, 255], ); // Bit depth 2, skip is 0 check( 2, 4, &[0b11110000, 0b00001111], vec![255, 255, 0, 0, 0, 0, 255, 255], ); // Bit depth 4, skip is half a byte check(4, 1, &[0b11110011, 0b00001100], vec![255, 0]); } }