diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e9865f408e..927618b517 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,9 +2,9 @@ name: Rust CI on: push: - branches: [ master, main, next ] + branches: [ master, main, next-version-0.25 ] pull_request: - branches: [ master, main, next ] + branches: [ master, main, next-version-0.25 ] schedule: - cron: '5 16 * * 6' @@ -14,7 +14,7 @@ jobs: 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] + features: ['', default, rayon, avif, bmp, dds, exr, ff, gif, hdr, ico, jpeg, png, pnm, qoi, tga, tiff, webp] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -38,14 +38,14 @@ jobs: strategy: fail-fast: false matrix: - rust: ["1.63.0", nightly, beta] + rust: ["1.67.1", nightly, beta] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - if: ${{ matrix.rust == '1.63.0' }} + if: ${{ matrix.rust == '1.67.1' }} - name: Generate Cargo.lock with minimal-version dependencies - if: ${{ matrix.rust == '1.63.0' }} + if: ${{ matrix.rust == '1.67.1' }} run: cargo -Zminimal-versions generate-lockfile - uses: dtolnay/rust-toolchain@v1 @@ -56,12 +56,10 @@ jobs: with: cache-on-failure: true - name: build - run: cargo build -v --features webp,webp-encoder + run: cargo build -v - name: test - if: ${{ matrix.rust != '1.63.0' }} - run: > - cargo test -v --features webp,webp-encoder && - cargo doc -v --features webp,webp-encoder + if: ${{ matrix.rust != '1.67.1' }} + run: cargo test -v && cargo doc -v test_other_archs: # github actions does not support 32-bit or big endian systems directly, but @@ -73,7 +71,6 @@ jobs: fail-fast: false matrix: arch: [powerpc-unknown-linux-gnu, i686-unknown-linux-gnu] - features: [default, webp-encoder] steps: - uses: actions/checkout@v4 - name: Install or use cached cross-rs/cross @@ -84,23 +81,11 @@ jobs: uses: Swatinem/rust-cache@v2 with: cache-on-failure: true + key: ${{ matrix.arch }} - name: Start Docker (required for cross-rs) run: sudo systemctl start docker - name: Cross-Run Tests using QEMU - run: | - cross test --target ${{ matrix.arch }} --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@v4 - - uses: dtolnay/rust-toolchain@stable - - name: build - run: cargo build -v --no-default-features --features="avif" + run: cross test --target ${{ matrix.arch }} test_avif_decoding: runs-on: ubuntu-latest @@ -110,7 +95,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - name: build - run: cargo build -v --no-default-features --features="avif,avif-decoder" + run: cargo build -v --no-default-features --features="avif,avif-native" env: SYSTEM_DEPS_DAV1D_BUILD_INTERNAL: always @@ -135,9 +120,16 @@ jobs: - uses: dtolnay/rust-toolchain@nightly - name: install-deps run: sudo apt-get -y install clang llvm + - name: Cache Cargo Registry + uses: Swatinem/rust-cache@v2 + with: + cache-targets: false + cache-all-crates: true + cache-on-failure: true + - name: Install cargo-afl + run: cargo install --locked -f cargo-afl - name: build run: | - cargo install cargo-afl cd fuzz-afl cargo check --bin reproduce_webp cargo check --bin reproduce_pnm @@ -152,15 +144,14 @@ jobs: steps: - uses: actions/checkout@v4 - 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 + - name: Install cargo-fuzz + uses: baptiste0928/cargo-install@v3 + with: + crate: cargo-fuzz + - name: check + run: cargo fuzz check + env: + CARGO_INCREMENTAL: 0 public_private_dependencies: runs-on: ubuntu-latest @@ -169,9 +160,10 @@ jobs: - 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 + echo 'cargo-features = ["public-dependency"]' | cat - Cargo.toml > Cargo.toml.0 + mv Cargo.toml.0 Cargo.toml cargo check build_benchmarks: @@ -197,19 +189,3 @@ jobs: steps: - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v1 - - verify_msrv: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install or use cached `cargo-msrv` - uses: baptiste0928/cargo-install@v2 - with: - crate: cargo-msrv - - - uses: dtolnay/rust-toolchain@nightly - - name: Generate Cargo.lock with minimal-version dependencies - run: cargo -Zminimal-versions generate-lockfile - - - name: Verify Minimum Rust Version against `-Zminimal-versions` dependencies - run: cargo-msrv verify diff --git a/CHANGES.md b/CHANGES.md index 82810acedd..7f01b45a5f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,27 @@ ## Changes +### Version 0.25.0 + +Breaking changes: +- Added `BufRead` + `Seek` bound on many decoders. +- Use `ExtendedColorType` instead of `ColorType` when encoding. +- Removed `ImageOutputFormat`, `GenericImageView::bounds`, and several other + deprecated items. +- Removed incremental decoding support and changed `ImageDecoder` so the trait + is object safe. +- Pixel types are now `repr(transparent)` rather than `repr(C)`. +- Made color_quant dependency optional. +- Renamed some feature flags. + +Structural changes: +- Increased MSRV to 1.67.1 + +Codec changes: +- Switched to image-webp for WebP encoding. +- Switched to zune-jpeg for JPEG decoding. +- Made the HDR decoder produce f32 images. + ### Version 0.24.9 Structural changes: diff --git a/Cargo.toml b/Cargo.toml index a78bdf74ba..0075ed1c4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "image" -version = "0.24.9" +version = "0.25.0" edition = "2021" resolver = "2" # note: when changed, also update test runner in `.github/workflows/rust.yml` -rust-version = "1.63.0" +rust-version = "1.67.1" license = "MIT OR Apache-2.0" description = "Imaging library. Provides basic image processing and encoders/decoders for common image formats." @@ -31,24 +31,30 @@ include = [ "/benches/", ] +# Crate build related +exclude = ["src/png/testdata/*", "examples/*", "tests/*"] + [dependencies] bytemuck = { version = "1.8.0", features = ["extern_crate_alloc"] } # includes cast_vec byteorder = "1.3.2" -num-traits = "0.2.0" -gif = { version = "0.13", 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 } +num-traits = { version = "0.2.0" } + +# Optional dependencies +color_quant = { version = "1.1", optional = true } dav1d = { version = "0.10.2", optional = true } dcv-color-primitives = { version = "0.6.1", optional = true } -color_quant = "1.1" exr = { version = "1.5.0", optional = true } +gif = { version = "0.13", optional = true } +image-webp = { version = "0.1.0", optional = true } +mp4parse = { version = "0.17.0", optional = true } +png = { version = "0.17.6", optional = true } qoi = { version = "0.4", optional = true } -libwebp = { package = "webp", version = "0.2.2", default-features = false, optional = true } +ravif = { version = "0.11.2", default-features = false, optional = true } rayon = { version = "1.7.0", optional = true } +rgb = { version = "0.8.25", optional = true } +tiff = { version = "0.9.0", optional = true } +zune-core = { version = "0.4.11", default-features = false, optional = true } +zune-jpeg = { version = "0.4.11", optional = true } [dev-dependencies] crc32fast = "1.2.0" @@ -56,44 +62,32 @@ num-complex = "0.4" glob = "0.3" quickcheck = "1" criterion = "0.5.0" -# 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"] +default = ["rayon", "default-formats"] -ico = ["bmp", "png"] -pnm = [] -tga = [] +# Format features +default-formats = ["avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "tiff", "webp"] +avif = ["dep:ravif", "dep:rgb"] bmp = [] +dds = [] +exr = ["dep:exr"] +ff = [] # Farbfeld image format +gif = ["dep:gif", "dep:color_quant"] hdr = [] -dxt = [] -dds = ["dxt"] -farbfeld = [] -openexr = ["exr"] +ico = ["bmp", "png"] +jpeg = ["dep:zune-core", "dep:zune-jpeg"] +png = ["dep:png"] +pnm = [] qoi = ["dep:qoi"] +tga = [] +tiff = ["dep:tiff"] +webp = ["dep:image-webp"] -# Enables WebP decoder support. -webp = [] -# Non-default, not included in `webp`. Requires native dependency libwebp. -webp-encoder = ["libwebp", "webp"] - -# 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 = [] +# Other features +rayon = ["dep:rayon"] # Enables multi-threading +avif-native = ["dep:mp4parse", "dep:dcv-color-primitives", "dep:dav1d"] # Enable native dependency libdav1d +benchmarks = [] # Build some inline benchmarks. Useful only during development (requires nightly Rust) [[bench]] path = "benches/decode.rs" diff --git a/Cargo.toml.public-private-dependencies b/Cargo.toml.public-private-dependencies deleted file mode 100644 index 9925be68b9..0000000000 --- a/Cargo.toml.public-private-dependencies +++ /dev/null @@ -1,98 +0,0 @@ -cargo-features = ["public-dependency"] - -[package] -name = "image" -version = "0.24.0-alpha" -edition = "2021" -rust-version = "1.63.0" - -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 diff --git a/README.md b/README.md index b81c16f112..924a165bcf 100644 --- a/README.md +++ b/README.md @@ -14,34 +14,81 @@ This crate provides basic image processing functions and methods for converting All image processing functions provided operate on types that implement the `GenericImageView` and `GenericImage` traits and return an `ImageBuffer`. +## High level API + +Load images using [`io::Reader`]: + +```rust,ignore +use std::io::Cursor; +use image::io::Reader as ImageReader; + +let img = ImageReader::open("myimage.png")?.decode()?; +let img2 = ImageReader::new(Cursor::new(bytes)).with_guessed_format()?.decode()?; +``` + +And save them using [`save`] or [`write_to`] methods: + +```rust,ignore +img.save("empty.jpg")?; + +let mut bytes: Vec = Vec::new(); +img2.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)?; +``` + ## Supported Image Formats -`image` provides implementations of common image format encoders and decoders. +With default features enabled, `image` provides implementations of many 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 | +| AVIF | Yes (8-bit only) \* | Yes (lossy only) | +| BMP | Yes | Yes | +| DDS | Yes | --- | | Farbfeld | Yes | Yes | | GIF | Yes | Yes | | HDR | 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 | +| JPEG | Yes | Yes | +| EXR | Yes | Yes | +| PNG | Yes | Yes | +| PNM | Yes | 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 \* | +| TGA | Yes | Yes | +| TIFF | Yes | Yes | +| WebP | Yes | Yes (lossless only) | + +- \* Requires the `avif-native` feature, uses the libdav1d C library. + +## Image Types -- \* Requires the `webp-encoder` feature, uses the libwebp C library. -- \*\* Requires the `avif-decoder` feature, uses the libdav1d C library. +This crate provides a number of different types for representing images. +Individual pixels within images are indexed with (0,0) at the top left corner. -### The [`ImageDecoder`](https://docs.rs/image/*/image/trait.ImageDecoder.html) and [`ImageDecoderRect`](https://docs.rs/image/*/image/trait.ImageDecoderRect.html) Traits +### [`ImageBuffer`](https://docs.rs/image/*/image/struct.ImageBuffer.html) +An image parameterised by its Pixel type, 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. + +### [`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. + +### 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. + +### [`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. + + +## 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 @@ -63,81 +110,6 @@ The most important methods for decoders are... 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. @@ -246,6 +218,6 @@ 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::save_buffer("image.png", buffer, 800, 600, image::ExtendedColorType::Rgb8).unwrap() } ``` diff --git a/benches/encode.rs b/benches/encode.rs index f64e7701a5..8c1fd955db 100644 --- a/benches/encode.rs +++ b/benches/encode.rs @@ -1,15 +1,16 @@ extern crate criterion; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use image::ExtendedColorType; 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); + fn encode_raw(&self, into: &mut Vec, im: &[u8], dims: u32, color: ExtendedColorType); + fn encode_bufvec(&self, into: &mut Vec, im: &[u8], dims: u32, color: ExtendedColorType); + fn encode_file(&self, file: &File, im: &[u8], dims: u32, color: ExtendedColorType); } #[derive(Clone, Copy)] @@ -50,9 +51,8 @@ type BenchGroup<'a> = criterion::BenchmarkGroup<'a, criterion::measurement::Wall /// /// 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]; +fn encode_zeroed(group: &mut BenchGroup, with: &dyn Encoder, size: u32, color: ExtendedColorType) { + let im = vec![0; (color.bits_per_pixel() as usize * size as usize + 7) / 8 * size as usize]; group.bench_with_input( BenchmarkId::new(format!("zero-{:?}-rawvec", color), size), @@ -87,7 +87,7 @@ fn encode_definition(criterion: &mut Criterion, def: &BenchDef) { for &color in def.colors { for &size in def.sizes { - encode_zeroed(&mut group, def.with, size, color); + encode_zeroed(&mut group, def.with, size, color.into()); } } } @@ -97,22 +97,22 @@ struct Bmp; struct Jpeg; trait EncoderBase { - fn encode(&self, into: impl Write, im: &[u8], dims: u32, color: ColorType); + fn encode(&self, into: impl Write, im: &[u8], dims: u32, color: ExtendedColorType); } impl Encoder for T { - fn encode_raw(&self, into: &mut Vec, im: &[u8], dims: u32, color: ColorType) { + fn encode_raw(&self, into: &mut Vec, im: &[u8], dims: u32, color: ExtendedColorType) { into.clear(); self.encode(into, im, dims, color); } - fn encode_bufvec(&self, into: &mut Vec, im: &[u8], dims: u32, color: ColorType) { + fn encode_bufvec(&self, into: &mut Vec, im: &[u8], dims: u32, color: ExtendedColorType) { 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) { + fn encode_file(&self, mut file: &File, im: &[u8], dims: u32, color: ExtendedColorType) { file.seek(SeekFrom::Start(0)).unwrap(); let buf = BufWriter::new(file); self.encode(buf, im, dims, color); @@ -120,14 +120,14 @@ impl Encoder for T { } impl EncoderBase for Bmp { - fn encode(&self, mut into: impl Write, im: &[u8], size: u32, color: ColorType) { + fn encode(&self, mut into: impl Write, im: &[u8], size: u32, color: ExtendedColorType) { 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) { + fn encode(&self, mut into: impl Write, im: &[u8], size: u32, color: ExtendedColorType) { let mut x = JpegEncoder::new(&mut into); x.encode(im, size, size, color).unwrap(); } diff --git a/fuzz-afl/Cargo.toml b/fuzz-afl/Cargo.toml index 311e4a1a86..940403af28 100644 --- a/fuzz-afl/Cargo.toml +++ b/fuzz-afl/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "image-fuzz-afl" version = "0.0.1" -authors = ["HeroicKatora"] +edition = "2021" publish = false [dependencies.image] diff --git a/fuzz-afl/fuzzers/fuzz_webp.rs b/fuzz-afl/fuzzers/fuzz_webp.rs index 98e0437d32..ee51dd3920 100644 --- a/fuzz-afl/fuzzers/fuzz_webp.rs +++ b/fuzz-afl/fuzzers/fuzz_webp.rs @@ -1,12 +1,14 @@ extern crate afl; extern crate image; +use std::io::Cursor; + use image::{DynamicImage, ImageDecoder}; use image::error::{ImageError, ImageResult, LimitError, LimitErrorKind}; #[inline(always)] fn webp_decode(data: &[u8]) -> ImageResult { - let decoder = image::codecs::webp::WebPDecoder::new(data)?; + let decoder = image::codecs::webp::WebPDecoder::new(Cursor::new(data))?; let (width, height) = decoder.dimensions(); if width.saturating_mul(height) > 4_000_000 { diff --git a/fuzz-afl/reproducers/reproduce_webp.rs b/fuzz-afl/reproducers/reproduce_webp.rs index 99c860dd10..93a87f717f 100644 --- a/fuzz-afl/reproducers/reproduce_webp.rs +++ b/fuzz-afl/reproducers/reproduce_webp.rs @@ -1,5 +1,7 @@ extern crate image; +use std::io::Cursor; + use image::{DynamicImage, ImageDecoder}; use image::error::{ImageError, ImageResult, LimitError, LimitErrorKind}; @@ -7,7 +9,7 @@ mod utils; #[inline(always)] fn webp_decode(data: &[u8]) -> ImageResult { - let decoder = image::codecs::webp::WebPDecoder::new(data)?; + let decoder = image::codecs::webp::WebPDecoder::new(Cursor::new(data))?; let (width, height) = decoder.dimensions(); if width.saturating_mul(height) > 4_000_000 { diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index ce695c5125..81b99fbf15 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -3,6 +3,7 @@ name = "image-fuzz" version = "0.0.1" authors = ["Automatically generated"] +edition = "2021" publish = false [package.metadata] diff --git a/fuzz/fuzzers/fuzzer_script_exr.rs b/fuzz/fuzzers/fuzzer_script_exr.rs index 645150ab37..f42ffec4d6 100644 --- a/fuzz/fuzzers/fuzzer_script_exr.rs +++ b/fuzz/fuzzers/fuzzer_script_exr.rs @@ -5,21 +5,17 @@ extern crate image; use image::codecs::openexr::*; use image::io::Limits; -use image::ColorType; +use image::ExtendedColorType; use image::ImageDecoder; use image::ImageEncoder; use image::ImageResult; -use std::convert::TryFrom; -use std::io::Cursor; -use std::io::Read; -use std::io::Seek; -use std::io::Write; +use std::io::{BufRead, Cursor, Seek, Write}; // "just dont panic" fn roundtrip(bytes: &[u8]) -> ImageResult<()> { /// Read the file from the specified path into an `Rgba32FImage`. // TODO this method should probably already exist in the main image crate - fn read_as_rgba_byte_image(read: impl Read + Seek) -> ImageResult<(u32, u32, Vec)> { + fn read_as_rgba_byte_image(read: impl BufRead + Seek) -> ImageResult<(u32, u32, Vec)> { let mut decoder = OpenExrDecoder::with_alpha_preference(read, Some(true))?; match usize::try_from(decoder.total_bytes()) { Ok(decoded_size) if decoded_size <= 256 * 1024 * 1024 => { @@ -45,7 +41,12 @@ fn roundtrip(bytes: &[u8]) -> ImageResult<()> { write: impl Write + Seek, (width, height, data): &(u32, u32, Vec), ) -> ImageResult<()> { - OpenExrEncoder::new(write).write_image(data.as_slice(), *width, *height, ColorType::Rgba32F) + OpenExrEncoder::new(write).write_image( + data.as_slice(), + *width, + *height, + ExtendedColorType::Rgba32F, + ) } let decoded_image = read_as_rgba_byte_image(Cursor::new(bytes))?; diff --git a/src/animation.rs b/src/animation.rs index 0f3b4cafb1..f598eeeacc 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -292,7 +292,7 @@ impl Ratio { } #[inline] - pub(crate) fn to_integer(&self) -> u32 { + pub(crate) fn to_integer(self) -> u32 { self.numer / self.denom } } diff --git a/src/buffer.rs b/src/buffer.rs index bfb6e66c14..480536a7dc 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -10,7 +10,7 @@ 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::image::{GenericImage, GenericImageView, ImageEncoder, ImageFormat}; use crate::math::Rect; use crate::traits::{EncodableLayout, Pixel, PixelWithColorType}; use crate::utils::expand_packed; @@ -1049,10 +1049,9 @@ where /// /// See [`ImageOutputFormat`](enum.ImageOutputFormat.html) for /// supported types. - pub fn write_to(&self, writer: &mut W, format: F) -> ImageResult<()> + pub fn write_to(&self, writer: &mut W, format: ImageFormat) -> ImageResult<()> where W: std::io::Write + std::io::Seek, - F: Into, P: PixelWithColorType, { // This is valid as the subpixel is u8. @@ -1174,10 +1173,6 @@ where 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) } @@ -1484,9 +1479,10 @@ impl From for Rgba32FImage { #[cfg(test)] mod test { - use super::{GrayImage, ImageBuffer, ImageOutputFormat, RgbImage}; + use super::{GrayImage, ImageBuffer, RgbImage}; use crate::math::Rect; use crate::GenericImage as _; + use crate::ImageFormat; use crate::{color, Rgb}; #[test] @@ -1676,9 +1672,10 @@ mod 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()); + assert!(img.write_to(&mut buffer, ImageFormat::Png).is_ok()); } #[test] diff --git a/src/codecs/avif/decoder.rs b/src/codecs/avif/decoder.rs index 4c84a2fd0e..6a25313199 100644 --- a/src/codecs/avif/decoder.rs +++ b/src/codecs/avif/decoder.rs @@ -4,9 +4,8 @@ /// /// [AVIF]: https://aomediacodec.github.io/av1-avif/ use std::error::Error; -use std::io::{self, Cursor, Read}; +use std::io::Read; use std::marker::PhantomData; -use std::mem; use crate::error::{DecodingError, ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; use crate::{ColorType, ImageDecoder, ImageError, ImageFormat, ImageResult}; @@ -87,26 +86,7 @@ impl AvifDecoder { } } -/// 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; - +impl ImageDecoder for AvifDecoder { fn dimensions(&self) -> (u32, u32) { (self.picture.width(), self.picture.height()) } @@ -115,16 +95,8 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for AvifDecoder { 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 icc_profile(&mut self) -> ImageResult>> { + Ok(self.icc_profile.clone()) } fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { @@ -203,6 +175,10 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for AvifDecoder { Ok(()) } + + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) + } } /// `get_picture` and `send_pending_data` yield `Again` as a non-fatal error requesting more data is sent to the decoder diff --git a/src/codecs/avif/encoder.rs b/src/codecs/avif/encoder.rs index 900a10e648..58cbe4c506 100644 --- a/src/codecs/avif/encoder.rs +++ b/src/codecs/avif/encoder.rs @@ -12,7 +12,7 @@ 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::{ExtendedColorType, ImageBuffer, ImageEncoder, ImageFormat, Pixel}; use crate::{ImageError, ImageResult}; use bytemuck::{try_cast_slice, try_cast_slice_mut, Pod, PodCastError}; @@ -104,10 +104,9 @@ impl ImageEncoder for AvifEncoder { data: &[u8], width: u32, height: u32, - color: ColorType, + color: ExtendedColorType, ) -> ImageResult<()> { - let expected_buffer_len = - (width as u64 * height as u64).saturating_mul(color.bytes_per_pixel() as u64); + let expected_buffer_len = color.buffer_size(width, height); assert_eq!( expected_buffer_len, data.len() as u64, @@ -134,7 +133,7 @@ impl ImageEncoder for AvifEncoder { impl AvifEncoder { // Does not currently do anything. Mirrors behaviour of old config function. - fn set_color(&mut self, _color: ColorType) { + fn set_color(&mut self, _color: ExtendedColorType) { // self.config.color_space = ColorSpace::RGB; } @@ -143,7 +142,7 @@ impl AvifEncoder { data: &'buf [u8], width: u32, height: u32, - color: ColorType, + color: ExtendedColorType, ) -> ImageResult> { // Error wrapping utility for color dependent buffer dimensions. fn try_from_raw( @@ -210,7 +209,7 @@ impl AvifEncoder { } match color { - ColorType::Rgb8 => { + ExtendedColorType::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 @@ -222,12 +221,12 @@ impl AvifEncoder { } Ok(RgbColor::Rgb8(Img::new( - rgb::AsPixels::as_pixels(data), + AsPixels::as_pixels(data), width as usize, height as usize, ))) } - ColorType::Rgba8 => { + ExtendedColorType::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 @@ -239,37 +238,37 @@ impl AvifEncoder { } Ok(RgbColor::Rgba8(Img::new( - rgb::AsPixels::as_pixels(data), + AsPixels::as_pixels(data), width as usize, height as usize, ))) } // we need a separate buffer.. - ColorType::L8 => { + ExtendedColorType::L8 => { let image = try_from_raw::>(data, width, height)?; Ok(RgbColor::Rgba8(convert_into(fallback, image))) } - ColorType::La8 => { + ExtendedColorType::La8 => { let image = try_from_raw::>(data, width, height)?; Ok(RgbColor::Rgba8(convert_into(fallback, image))) } // we need to really convert data.. - ColorType::L16 => { + ExtendedColorType::L16 => { let buffer = cast_buffer(data)?; let image = try_from_raw::>(&buffer, width, height)?; Ok(RgbColor::Rgba8(convert_into(fallback, image))) } - ColorType::La16 => { + ExtendedColorType::La16 => { let buffer = cast_buffer(data)?; let image = try_from_raw::>(&buffer, width, height)?; Ok(RgbColor::Rgba8(convert_into(fallback, image))) } - ColorType::Rgb16 => { + ExtendedColorType::Rgb16 => { let buffer = cast_buffer(data)?; let image = try_from_raw::>(&buffer, width, height)?; Ok(RgbColor::Rgba8(convert_into(fallback, image))) } - ColorType::Rgba16 => { + ExtendedColorType::Rgba16 => { let buffer = cast_buffer(data)?; let image = try_from_raw::>(&buffer, width, height)?; Ok(RgbColor::Rgba8(convert_into(fallback, image))) @@ -278,7 +277,7 @@ impl AvifEncoder { _ => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Avif.into(), - UnsupportedErrorKind::Color(color.into()), + UnsupportedErrorKind::Color(color), ), )), } diff --git a/src/codecs/avif/mod.rs b/src/codecs/avif/mod.rs index f74217cf3b..89edfc2c97 100644 --- a/src/codecs/avif/mod.rs +++ b/src/codecs/avif/mod.rs @@ -3,12 +3,12 @@ /// 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")] +#[cfg(feature = "avif-native")] pub use self::decoder::AvifDecoder; -#[cfg(feature = "avif-encoder")] +#[cfg(feature = "avif")] pub use self::encoder::{AvifEncoder, ColorSpace}; -#[cfg(feature = "avif-decoder")] +#[cfg(feature = "avif-native")] mod decoder; -#[cfg(feature = "avif-encoder")] +#[cfg(feature = "avif")] mod encoder; diff --git a/src/codecs/bmp/decoder.rs b/src/codecs/bmp/decoder.rs index ab8fa1b0d8..77a7712232 100644 --- a/src/codecs/bmp/decoder.rs +++ b/src/codecs/bmp/decoder.rs @@ -1,9 +1,8 @@ use std::cmp::{self, Ordering}; -use std::io::{self, Cursor, Read, Seek, SeekFrom}; +use std::io::{self, BufRead, Seek, SeekFrom}; use std::iter::{repeat, Rev}; -use std::marker::PhantomData; use std::slice::ChunksMut; -use std::{error, fmt, mem}; +use std::{error, fmt}; use byteorder::{LittleEndian, ReadBytesExt}; @@ -11,7 +10,8 @@ use crate::color::ColorType; use crate::error::{ DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, }; -use crate::image::{self, ImageDecoder, ImageDecoderRect, ImageFormat, Progress}; +use crate::image::{self, ImageDecoder, ImageFormat}; +use crate::ImageDecoderRect; const BITMAPCOREHEADER_SIZE: u32 = 12; const BITMAPINFOHEADER_SIZE: u32 = 40; @@ -502,7 +502,7 @@ enum RLEInsn { PixelRun(u8, u8), } -impl BmpDecoder { +impl BmpDecoder { fn new_decoder(reader: R) -> BmpDecoder { BmpDecoder { reader, @@ -1329,25 +1329,7 @@ impl BmpDecoder { } } -/// 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; - +impl ImageDecoder for BmpDecoder { fn dimensions(&self) -> (u32, u32) { (self.width as u32, self.height as u32) } @@ -1362,28 +1344,25 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for BmpDecoder { } } - 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) } + + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) + } } -impl<'a, R: 'a + Read + Seek> ImageDecoderRect<'a> for BmpDecoder { - fn read_rect_with_progress( +impl ImageDecoderRect for BmpDecoder { + fn read_rect( &mut self, x: u32, y: u32, width: u32, height: u32, buf: &mut [u8], - progress_callback: F, + row_pitch: usize, ) -> ImageResult<()> { let start = self.reader.stream_position()?; image::load_rect( @@ -1392,8 +1371,9 @@ impl<'a, R: 'a + Read + Seek> ImageDecoderRect<'a> for BmpDecoder { width, height, buf, - progress_callback, + row_pitch, self, + self.total_bytes() as usize, |_, _| Ok(()), |s, buf| s.read_image_data(buf), )?; @@ -1404,6 +1384,8 @@ impl<'a, R: 'a + Read + Seek> ImageDecoderRect<'a> for BmpDecoder { #[cfg(test)] mod test { + use std::io::{BufReader, Cursor}; + use super::*; #[test] @@ -1423,11 +1405,12 @@ mod test { #[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 f = + BufReader::new(std::fs::File::open("tests/images/bmp/images/Core_8_Bit.bmp").unwrap()); + let mut decoder = BmpDecoder::new(f).unwrap(); let mut buf: Vec = vec![0; 8 * 8 * 3]; - decoder.read_rect(0, 0, 8, 8, &mut buf).unwrap(); + decoder.read_rect(0, 0, 8, 8, &mut buf, 8 * 3).unwrap(); } #[test] diff --git a/src/codecs/bmp/encoder.rs b/src/codecs/bmp/encoder.rs index c3e1ae08f4..512d25ace5 100644 --- a/src/codecs/bmp/encoder.rs +++ b/src/codecs/bmp/encoder.rs @@ -5,7 +5,7 @@ use crate::error::{ EncodingError, ImageError, ImageFormatHint, ImageResult, ParameterError, ParameterErrorKind, }; use crate::image::ImageEncoder; -use crate::{color, ImageFormat}; +use crate::{ExtendedColorType, ImageFormat}; const BITMAPFILEHEADER_SIZE: u32 = 14; const BITMAPINFOHEADER_SIZE: u32 = 40; @@ -22,7 +22,7 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> { BmpEncoder { writer: w } } - /// Encodes the image `image` that has dimensions `width` and `height` and `ColorType` `c`. + /// Encodes the image `image` that has dimensions `width` and `height` and `ExtendedColorType` `c`. /// /// # Panics /// @@ -33,7 +33,7 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> { image: &[u8], width: u32, height: u32, - c: color::ColorType, + c: ExtendedColorType, ) -> ImageResult<()> { self.encode_with_palette(image, width, height, c, None) } @@ -50,10 +50,10 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> { image: &[u8], width: u32, height: u32, - c: color::ColorType, + c: ExtendedColorType, palette: Option<&[[u8; 3]]>, ) -> ImageResult<()> { - if palette.is_some() && c != color::ColorType::L8 && c != color::ColorType::La8 { + if palette.is_some() && c != ExtendedColorType::L8 && c != ExtendedColorType::La8 { return Err(ImageError::IoError(io::Error::new( io::ErrorKind::InvalidInput, format!( @@ -63,8 +63,7 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> { ))); } - let expected_buffer_len = - (width as u64 * height as u64).saturating_mul(c.bytes_per_pixel() as u64); + let expected_buffer_len = c.buffer_size(width, height); assert_eq!( expected_buffer_len, image.len() as u64, @@ -141,12 +140,12 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> { // 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 => { + ExtendedColorType::Rgb8 => self.encode_rgb(image, width, height, row_pad_size, 3)?, + ExtendedColorType::Rgba8 => self.encode_rgba(image, width, height, row_pad_size, 4)?, + ExtendedColorType::L8 => { self.encode_gray(image, width, height, row_pad_size, 1, palette)? } - color::ColorType::La8 => { + ExtendedColorType::La8 => { self.encode_gray(image, width, height, row_pad_size, 2, palette)? } _ => { @@ -274,13 +273,13 @@ impl<'a, W: Write> ImageEncoder for BmpEncoder<'a, W> { buf: &[u8], width: u32, height: u32, - color_type: color::ColorType, + color_type: ExtendedColorType, ) -> ImageResult<()> { self.encode(buf, width, height, color_type) } } -fn get_unsupported_error_message(c: color::ColorType) -> String { +fn get_unsupported_error_message(c: ExtendedColorType) -> String { format!( "Unsupported color type {:?}. Supported types: RGB(8), RGBA(8), Gray(8), GrayA(8).", c @@ -288,16 +287,19 @@ fn get_unsupported_error_message(c: color::ColorType) -> String { } /// 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)> { +fn get_pixel_info( + c: ExtendedColorType, + 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 => ( + ExtendedColorType::Rgb8 => (BITMAPINFOHEADER_SIZE, 3, 0), + ExtendedColorType::Rgba8 => (BITMAPV4HEADER_SIZE, 4, 0), + ExtendedColorType::L8 => ( BITMAPINFOHEADER_SIZE, 1, palette.map(|p| p.len()).unwrap_or(256) as u32, ), - color::ColorType::La8 => ( + ExtendedColorType::La8 => ( BITMAPINFOHEADER_SIZE, 1, palette.map(|p| p.len()).unwrap_or(256) as u32, @@ -317,11 +319,12 @@ fn get_pixel_info(c: color::ColorType, palette: Option<&[[u8; 3]]>) -> io::Resul mod tests { use super::super::BmpDecoder; use super::BmpEncoder; - use crate::color::ColorType; + use crate::image::ImageDecoder; + use crate::ExtendedColorType; use std::io::Cursor; - fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec { + fn round_trip_image(image: &[u8], width: u32, height: u32, c: ExtendedColorType) -> Vec { let mut encoded_data = Vec::new(); { let mut encoder = BmpEncoder::new(&mut encoded_data); @@ -340,7 +343,7 @@ mod tests { #[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); + let decoded = round_trip_image(&image, 1, 1, ExtendedColorType::Rgb8); assert_eq!(3, decoded.len()); assert_eq!(255, decoded[0]); assert_eq!(0, decoded[1]); @@ -353,27 +356,27 @@ mod tests { 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); + let result = encoder.encode(&image, 40_000, 40_000, ExtendedColorType::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); + let decoded = round_trip_image(&image, 1, 1, ExtendedColorType::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); + let _decoded = round_trip_image(&image, 3, 3, ExtendedColorType::Rgb8); } #[test] fn round_trip_gray() { let image = [0u8, 1, 2]; // 3 pixels - let decoded = round_trip_image(&image, 3, 1, ColorType::L8); + let decoded = round_trip_image(&image, 3, 1, ExtendedColorType::L8); // should be read back as 3 RGB pixels assert_eq!(9, decoded.len()); assert_eq!(0, decoded[0]); @@ -390,7 +393,7 @@ mod tests { #[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); + let decoded = round_trip_image(&image, 1, 3, ExtendedColorType::La8); // should be read back as 3 RGB pixels assert_eq!(9, decoded.len()); assert_eq!(0, decoded[0]); diff --git a/src/codecs/dds.rs b/src/codecs/dds.rs index 408d786cec..88c94c2486 100644 --- a/src/codecs/dds.rs +++ b/src/codecs/dds.rs @@ -11,7 +11,7 @@ use std::{error, fmt}; use byteorder::{LittleEndian, ReadBytesExt}; #[allow(deprecated)] -use crate::codecs::dxt::{DxtDecoder, DxtReader, DxtVariant}; +use crate::codecs::dxt::{DxtDecoder, DxtVariant}; use crate::color::ColorType; use crate::error::{ DecodingError, ImageError, ImageFormatHint, ImageResult, UnsupportedError, UnsupportedErrorKind, @@ -20,6 +20,7 @@ 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)] +#[allow(clippy::enum_variant_names)] enum DecoderError { /// Wrong DDS channel width PixelFormatSizeInvalid(u32), @@ -327,10 +328,7 @@ impl DdsDecoder { } } -impl<'a, R: 'a + Read> ImageDecoder<'a> for DdsDecoder { - #[allow(deprecated)] - type Reader = DxtReader; - +impl ImageDecoder for DdsDecoder { fn dimensions(&self) -> (u32, u32) { self.inner.dimensions() } @@ -339,19 +337,13 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for DdsDecoder { self.inner.color_type() } - fn scanline_bytes(&self) -> u64 { - #[allow(deprecated)] - self.inner.scanline_bytes() - } - - fn into_reader(self) -> ImageResult { - #[allow(deprecated)] - self.inner.into_reader() - } - fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { self.inner.read_image(buf) } + + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) + } } #[cfg(test)] diff --git a/src/codecs/dxt.rs b/src/codecs/dxt.rs index 63f6bf3300..108f6e1a64 100644 --- a/src/codecs/dxt.rs +++ b/src/codecs/dxt.rs @@ -7,17 +7,17 @@ //! //! Note: this module only implements bare DXT encoding/decoding, it does not parse formats that can contain DXT files like .dds -use std::io::{self, Read, Seek, SeekFrom, Write}; +use std::io::{self, Read}; use crate::color::ColorType; use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; -use crate::image::{self, ImageDecoder, ImageDecoderRect, ImageReadBuffer, Progress}; +use crate::image::ImageDecoder; /// 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 { +pub(crate) 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, @@ -48,7 +48,7 @@ impl DxtVariant { } /// Returns the color type that is stored in this DXT variant - pub fn color_type(self) -> ColorType { + pub(crate) fn color_type(self) -> ColorType { match self { DxtVariant::DXT1 => ColorType::Rgb8, DxtVariant::DXT3 | DxtVariant::DXT5 => ColorType::Rgba8, @@ -57,7 +57,7 @@ impl DxtVariant { } /// DXT decoder -pub struct DxtDecoder { +pub(crate) struct DxtDecoder { inner: R, width_blocks: u32, height_blocks: u32, @@ -73,7 +73,7 @@ impl DxtDecoder { /// 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( + pub(crate) fn new( r: R, width: u32, height: u32, @@ -98,6 +98,10 @@ impl DxtDecoder { }) } + fn scanline_bytes(&self) -> u64 { + self.variant.decoded_bytes_per_block() as u64 * u64::from(self.width_blocks) + } + fn read_scanline(&mut self, buf: &mut [u8]) -> io::Result { assert_eq!( u64::try_from(buf.len()), @@ -122,9 +126,7 @@ impl DxtDecoder { // 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; - +impl ImageDecoder for DxtDecoder { fn dimensions(&self) -> (u32, u32) { (self.width_blocks * 4, self.height_blocks * 4) } @@ -133,21 +135,6 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for DxtDecoder { 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( - #[allow(deprecated)] - 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())); @@ -157,106 +144,15 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for DxtDecoder { } 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(()) + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) } } /** * Actual encoding/decoding logic below. */ -use std::mem::swap; type Rgb = [u8; 3]; @@ -274,27 +170,6 @@ fn enc565_decode(value: u16) -> Rgb { ] } -/// 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 */ @@ -471,409 +346,3 @@ fn decode_dxt5_row(source: &[u8], dest: &mut [u8]) { } } } - -/* - * 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 -} diff --git a/src/codecs/farbfeld.rs b/src/codecs/farbfeld.rs index bcfeee4b27..10950ce45b 100644 --- a/src/codecs/farbfeld.rs +++ b/src/codecs/farbfeld.rs @@ -19,11 +19,12 @@ use std::i64; use std::io::{self, Read, Seek, SeekFrom, Write}; -use crate::color::ColorType; +use crate::color::ExtendedColorType; use crate::error::{ DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, }; -use crate::image::{self, ImageDecoder, ImageDecoderRect, ImageEncoder, ImageFormat, Progress}; +use crate::image::{self, ImageDecoder, ImageDecoderRect, ImageEncoder, ImageFormat}; +use crate::ColorType; /// farbfeld Reader pub struct FarbfeldReader { @@ -67,8 +68,8 @@ impl FarbfeldReader { if crate::utils::check_dimension_overflow( reader.width, reader.height, - // ColorType is always rgba16 - ColorType::Rgba16.bytes_per_pixel(), + // ExtendedColorType is always rgba16 + 8, ) { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( @@ -195,9 +196,7 @@ impl FarbfeldDecoder { } } -impl<'a, R: 'a + Read> ImageDecoder<'a> for FarbfeldDecoder { - type Reader = FarbfeldReader; - +impl ImageDecoder for FarbfeldDecoder { fn dimensions(&self) -> (u32, u32) { (self.reader.width, self.reader.height) } @@ -206,24 +205,26 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for FarbfeldDecoder { ColorType::Rgba16 } - fn into_reader(self) -> ImageResult { - Ok(self.reader) + fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); + self.reader.read_exact(buf)?; + Ok(()) } - fn scanline_bytes(&self) -> u64 { - 2 + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) } } -impl<'a, R: 'a + Read + Seek> ImageDecoderRect<'a> for FarbfeldDecoder { - fn read_rect_with_progress( +impl ImageDecoderRect for FarbfeldDecoder { + fn read_rect( &mut self, x: u32, y: u32, width: u32, height: u32, buf: &mut [u8], - progress_callback: F, + row_pitch: usize, ) -> ImageResult<()> { // A "scanline" (defined as "shortest non-caching read" in the doc) is just one channel in this case @@ -234,8 +235,9 @@ impl<'a, R: 'a + Read + Seek> ImageDecoderRect<'a> for FarbfeldDecoder { width, height, buf, - progress_callback, + row_pitch, self, + 2, |s, scanline| s.reader.seek(SeekFrom::Start(scanline * 2)).map(|_| ()), |s, buf| s.reader.read_exact(buf), )?; @@ -295,13 +297,13 @@ impl ImageEncoder for FarbfeldEncoder { buf: &[u8], width: u32, height: u32, - color_type: ColorType, + color_type: ExtendedColorType, ) -> ImageResult<()> { - if color_type != ColorType::Rgba16 { + if color_type != ExtendedColorType::Rgba16 { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Farbfeld.into(), - UnsupportedErrorKind::Color(color_type.into()), + UnsupportedErrorKind::Color(color_type), ), )); } @@ -375,7 +377,7 @@ mod tests { let mut out_buf = [0u8; 64]; FarbfeldDecoder::new(input_cur) .unwrap() - .read_rect(0, 2, 1, 1, &mut out_buf) + .read_rect(0, 2, 1, 1, &mut out_buf, 8) .unwrap(); let exp = degenerate_pixels(RECTANGLE_OUT); assert_eq!(&out_buf[..exp.len()], &exp[..]); @@ -392,7 +394,7 @@ mod tests { let mut out_buf = [0u8; 64]; FarbfeldDecoder::new(Cursor::new(RECTANGLE_IN)) .unwrap() - .read_rect(x, y, width, height, &mut out_buf) + .read_rect(x, y, width, height, &mut out_buf, width as usize * 8) .unwrap(); let exp = degenerate_pixels(exp_wide); assert_eq!(&out_buf[..exp.len()], &exp[..]); diff --git a/src/codecs/gif.rs b/src/codecs/gif.rs index dffd985bbf..618e3b605e 100644 --- a/src/codecs/gif.rs +++ b/src/codecs/gif.rs @@ -10,9 +10,10 @@ //! use image::codecs::gif::{GifDecoder, GifEncoder}; //! use image::{ImageDecoder, AnimationDecoder}; //! use std::fs::File; +//! use std::io::BufReader; //! # fn main() -> std::io::Result<()> { //! // Decode a gif into frames -//! let file_in = File::open("foo.gif")?; +//! let file_in = BufReader::new(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"); @@ -26,7 +27,7 @@ //! ``` #![allow(clippy::while_let_loop)] -use std::io::{self, Cursor, Read, Write}; +use std::io::{self, BufRead, Cursor, Read, Seek, Write}; use std::marker::PhantomData; use std::mem; @@ -41,9 +42,10 @@ use crate::error::{ DecodingError, EncodingError, ImageError, ImageResult, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, }; -use crate::image::{self, AnimationDecoder, ImageDecoder, ImageFormat}; +use crate::image::{AnimationDecoder, ImageDecoder, ImageFormat}; use crate::io::Limits; use crate::traits::Pixel; +use crate::ExtendedColorType; use crate::ImageBuffer; /// GIF decoder @@ -63,16 +65,6 @@ impl GifDecoder { limits: Limits::no_limits(), }) } - - /// Creates a new decoder that decodes the input steam `r`, using limits `limits` - #[deprecated(since = "0.24.8", note = "Use `new` followed by `set_limits` instead")] - pub fn with_limits(r: R, limits: Limits) -> ImageResult> { - let mut decoder = Self::new(r)?; - // call `.set_limits()` instead of just setting the field directly - // so that we raise an error in case they are exceeded - decoder.set_limits(limits)?; - Ok(decoder) - } } /// Wrapper struct around a `Cursor>` @@ -91,9 +83,7 @@ impl Read for GifReader { } } -impl<'a, R: 'a + Read> ImageDecoder<'a> for GifDecoder { - type Reader = GifReader; - +impl ImageDecoder for GifDecoder { fn dimensions(&self) -> (u32, u32) { ( u32::from(self.reader.width()), @@ -105,13 +95,6 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for GifDecoder { ColorType::Rgba8 } - fn into_reader(self) -> ImageResult { - Ok(GifReader( - Cursor::new(image::decoder_to_vec(self)?), - PhantomData, - )) - } - fn set_limits(&mut self, limits: Limits) -> ImageResult<()> { limits.check_support(&crate::io::LimitSupport::default())?; @@ -228,6 +211,10 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for GifDecoder { Ok(()) } + + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) + } } struct GifFrameIterator { @@ -240,7 +227,7 @@ struct GifFrameIterator { limits: Limits, } -impl GifFrameIterator { +impl GifFrameIterator { fn new(decoder: GifDecoder) -> GifFrameIterator { let (width, height) = decoder.dimensions(); let limits = decoder.limits.clone(); @@ -406,7 +393,7 @@ impl Iterator for GifFrameIterator { } } -impl<'a, R: Read + 'a> AnimationDecoder<'a> for GifDecoder { +impl<'a, R: BufRead + Seek + 'a> AnimationDecoder<'a> for GifDecoder { fn into_frames(self) -> animation::Frames<'a> { animation::Frames::new(Box::new(GifFrameIterator::new(self))) } @@ -445,9 +432,9 @@ pub enum Repeat { } impl Repeat { - pub(crate) fn to_gif_enum(&self) -> gif::Repeat { + pub(crate) fn to_gif_enum(self) -> gif::Repeat { match self { - Repeat::Finite(n) => gif::Repeat::Finite(*n), + Repeat::Finite(n) => gif::Repeat::Finite(n), Repeat::Infinite => gif::Repeat::Infinite, } } @@ -500,18 +487,18 @@ impl GifEncoder { data: &[u8], width: u32, height: u32, - color: ColorType, + color: ExtendedColorType, ) -> ImageResult<()> { let (width, height) = self.gif_dimensions(width, height)?; match color { - ColorType::Rgb8 => self.encode_gif(Frame::from_rgb(width, height, data)), - ColorType::Rgba8 => { + ExtendedColorType::Rgb8 => self.encode_gif(Frame::from_rgb(width, height, data)), + ExtendedColorType::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()), + UnsupportedErrorKind::Color(color), ), )), } @@ -601,7 +588,7 @@ impl GifEncoder { gif_encoder = self.gif_encoder.as_mut().unwrap() } - frame.dispose = gif::DisposalMethod::Background; + frame.dispose = DisposalMethod::Background; gif_encoder .write_frame(&frame) diff --git a/src/codecs/hdr/decoder.rs b/src/codecs/hdr/decoder.rs index 6413e05942..c704f1ab8d 100644 --- a/src/codecs/hdr/decoder.rs +++ b/src/codecs/hdr/decoder.rs @@ -1,19 +1,13 @@ -use crate::Primitive; -use num_traits::identities::Zero; -#[cfg(test)] -use std::borrow::Cow; -use std::io::{self, BufRead, Cursor, Read, Seek}; -use std::marker::PhantomData; +use std::io::{self, Read}; + use std::num::{ParseFloatError, ParseIntError}; -use std::path::Path; -use std::{error, fmt, mem}; +use std::{error, fmt}; use crate::color::{ColorType, Rgb}; use crate::error::{ - DecodingError, ImageError, ImageFormatHint, ImageResult, ParameterError, ParameterErrorKind, - UnsupportedError, UnsupportedErrorKind, + DecodingError, ImageError, ImageFormatHint, ImageResult, UnsupportedError, UnsupportedErrorKind, }; -use crate::image::{self, ImageDecoder, ImageDecoderRect, ImageFormat, Progress}; +use crate::image::{ImageDecoder, ImageFormat}; /// Errors that can occur during decoding and parsing of a HDR image #[derive(Debug, Clone, PartialEq, Eq)] @@ -121,117 +115,6 @@ impl fmt::Display for LineType { } } -/// 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; @@ -248,22 +131,22 @@ pub struct HdrDecoder { /// Refer to [wikipedia](https://en.wikipedia.org/wiki/RGBE_image_format) #[repr(C)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub struct Rgbe8Pixel { +pub(crate) struct Rgbe8Pixel { /// Color components - pub c: [u8; 3], + pub(crate) c: [u8; 3], /// Exponent - pub e: u8, + pub(crate) e: u8, } /// Creates `Rgbe8Pixel` from components -pub fn rgbe8(r: u8, g: u8, b: u8, e: u8) -> Rgbe8Pixel { +pub(crate) 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 { + pub(crate) fn to_hdr(self) -> Rgb { if self.e == 0 { Rgb([0.0, 0.0, 0.0]) } else { @@ -276,63 +159,21 @@ impl Rgbe8Pixel { ]) } } - - /// 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` +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> { + pub fn new(reader: R) -> ImageResult { HdrDecoder::with_strictness(reader, true) } + /// Allows reading old Radiance HDR images + pub fn new_nonstrict(reader: R) -> ImageResult { + Self::with_strictness(reader, false) + } + /// Reads Radiance HDR image header from stream `reader`, /// if the header is valid, creates `HdrDecoder`. /// @@ -425,23 +266,8 @@ impl HdrDecoder { 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>( + fn read_image_transform T>( mut self, f: F, output_slice: &mut [T], @@ -469,118 +295,35 @@ impl HdrDecoder { } 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) +impl ImageDecoder for HdrDecoder { + fn dimensions(&self) -> (u32, u32) { + (self.meta.width, self.meta.height) } - /// 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) + fn color_type(&self) -> ColorType { + ColorType::Rgb32F } -} - -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, - } - } -} + fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); -/// 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, -} + let mut img = 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 img[..])?; -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; + for (i, Rgb(data)) in img.into_iter().enumerate() { + buf[(i * 12)..][..12].copy_from_slice(bytemuck::cast_slice(&data)); } - } -} - -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)) - } + Ok(()) } - 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)) + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) } } -impl ExactSizeIterator for HdrImageDecoderIterator {} - // Precondition: buf.len() > 0 fn read_scanline(r: &mut R, buf: &mut [Rgbe8Pixel]) -> ImageResult<()> { assert!(!buf.is_empty()); @@ -940,91 +683,72 @@ fn split_at_first<'a>(s: &'a str, separator: &str) -> Option<(&'a str, &'a str)> } } -#[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>> { +fn read_line_u8(r: &mut R) -> 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(); + loop { + let mut byte = [0]; + if r.read(&mut byte)? == 0 || byte[0] == b'\n' { + if ret.is_empty() && byte[0] != b'\n' { + return Ok(None); + } else { + return Ok(Some(ret)); } - Ok(Some(ret)) } - Err(err) => Err(err), + ret.push(byte[0]); } } -#[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); -} +#[cfg(test)] +mod tests { + use std::{borrow::Cow, io::Cursor}; -/// 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; + use super::*; - 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])); + #[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); } - Ok(ret) -} -#[cfg(test)] -mod test { - use super::*; + #[test] + fn read_line_u8_test() { + let buf: Vec<_> = (&b"One\nTwo\nThree\nFour\n\n\n"[..]).into(); + let input = &mut 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); + } #[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()); + assert!(HdrDecoder::new(Cursor::new(data)).is_err()); + assert!(HdrDecoder::new_nonstrict(Cursor::new(data)).is_err()); } } diff --git a/src/codecs/hdr/encoder.rs b/src/codecs/hdr/encoder.rs index aa97186cf3..ddda38ad69 100644 --- a/src/codecs/hdr/encoder.rs +++ b/src/codecs/hdr/encoder.rs @@ -227,7 +227,7 @@ fn write_rgbe8(w: &mut W, v: Rgbe8Pixel) -> Result<()> { } /// Converts ```Rgb``` into ```Rgbe8Pixel``` -pub fn to_rgbe8(pix: Rgb) -> Rgbe8Pixel { +pub(crate) 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 { @@ -421,7 +421,7 @@ fn noruncombine_test() { assert_eq!(rsi.next(), Some(Norun(129, 7))); assert_eq!(rsi.next(), None); - let v: Vec<_> = ::std::iter::repeat(()) + let v: Vec<_> = std::iter::repeat(()) .flat_map(|_| (0..2)) .take(257) .collect(); diff --git a/src/codecs/ico/decoder.rs b/src/codecs/ico/decoder.rs index 699e6a992b..0ad4cf613d 100644 --- a/src/codecs/ico/decoder.rs +++ b/src/codecs/ico/decoder.rs @@ -1,13 +1,12 @@ use byteorder::{LittleEndian, ReadBytesExt}; -use std::io::{self, Cursor, Read, Seek, SeekFrom}; -use std::marker::PhantomData; -use std::{error, fmt, mem}; +use std::io::{BufRead, Read, Seek, SeekFrom}; +use std::{error, fmt}; use crate::color::ColorType; use crate::error::{ DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, }; -use crate::image::{self, ImageDecoder, ImageFormat}; +use crate::image::{ImageDecoder, ImageFormat}; use self::InnerDecoder::*; use crate::codecs::bmp::BmpDecoder; @@ -107,12 +106,12 @@ impl From for ImageFormat { } /// An ico decoder -pub struct IcoDecoder { +pub struct IcoDecoder { selected_entry: DirEntry, inner_decoder: InnerDecoder, } -enum InnerDecoder { +enum InnerDecoder { Bmp(BmpDecoder), Png(Box>), } @@ -142,7 +141,7 @@ struct DirEntry { image_offset: u32, } -impl IcoDecoder { +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)?; @@ -249,7 +248,7 @@ impl DirEntry { Ok(signature == PNG_SIGNATURE) } - fn decoder(&self, mut r: R) -> ImageResult> { + fn decoder(&self, mut r: R) -> ImageResult> { let is_png = self.is_png(&mut r)?; self.seek_to_start(&mut r)?; @@ -261,25 +260,7 @@ impl DirEntry { } } -/// 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; - +impl ImageDecoder for IcoDecoder { fn dimensions(&self) -> (u32, u32) { match self.inner_decoder { Bmp(ref decoder) => decoder.dimensions(), @@ -294,13 +275,6 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for IcoDecoder { } } - 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 { @@ -402,6 +376,10 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for IcoDecoder { } } } + + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) + } } #[cfg(test)] @@ -462,7 +440,7 @@ mod test { 0x50, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x37, 0x61, ]; - let decoder = IcoDecoder::new(Cursor::new(&data)).unwrap(); + let decoder = IcoDecoder::new(std::io::Cursor::new(&data)).unwrap(); let mut buf = vec![0; usize::try_from(decoder.total_bytes()).unwrap()]; assert!(decoder.read_image(&mut buf).is_err()); } diff --git a/src/codecs/ico/encoder.rs b/src/codecs/ico/encoder.rs index 75d3116590..88facebff8 100644 --- a/src/codecs/ico/encoder.rs +++ b/src/codecs/ico/encoder.rs @@ -2,11 +2,11 @@ use 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; +use crate::ExtendedColorType; // Enum value indicating an ICO image (as opposed to a CUR image): const ICO_IMAGE_TYPE: u16 = 1; @@ -28,7 +28,7 @@ pub struct IcoFrame<'a> { width: u8, // Stored as `0 => 256, n => n` height: u8, - color_type: ColorType, + color_type: ExtendedColorType, } impl<'a> IcoFrame<'a> { @@ -39,7 +39,7 @@ impl<'a> IcoFrame<'a> { encoded_image: impl Into>, width: u32, height: u32, - color_type: ColorType, + color_type: ExtendedColorType, ) -> ImageResult { let encoded_image = encoded_image.into(); @@ -72,7 +72,12 @@ impl<'a> IcoFrame<'a> { /// 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 { + pub fn as_png( + buf: &[u8], + width: u32, + height: u32, + color_type: ExtendedColorType, + ) -> ImageResult { let mut image_data: Vec = Vec::new(); PngEncoder::new(&mut image_data).write_image(buf, width, height, color_type)?; @@ -87,21 +92,6 @@ impl 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 @@ -151,10 +141,9 @@ impl ImageEncoder for IcoEncoder { buf: &[u8], width: u32, height: u32, - color_type: ColorType, + color_type: ExtendedColorType, ) -> ImageResult<()> { - let expected_buffer_len = - (width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64); + let expected_buffer_len = color_type.buffer_size(width, height); assert_eq!( expected_buffer_len, buf.len() as u64, @@ -181,7 +170,7 @@ fn write_direntry( w: &mut W, width: u8, height: u8, - color: ColorType, + color: ExtendedColorType, data_start: u32, data_size: u32, ) -> io::Result<()> { diff --git a/src/codecs/jpeg/decoder.rs b/src/codecs/jpeg/decoder.rs index 639c6244b7..2fc51c1780 100644 --- a/src/codecs/jpeg/decoder.rs +++ b/src/codecs/jpeg/decoder.rs @@ -1,1288 +1,160 @@ -use std::io::{self, Cursor, Read}; +use std::io::{BufRead, Seek}; use std::marker::PhantomData; -use std::mem; use crate::color::ColorType; use crate::error::{ - DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, + DecodingError, ImageError, ImageResult, LimitError, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{ImageDecoder, ImageFormat}; +use crate::io::Limits; + +type ZuneColorSpace = zune_core::colorspace::ColorSpace; /// JPEG decoder pub struct JpegDecoder { - decoder: jpeg::Decoder, - metadata: jpeg::ImageInfo, + input: Vec, + orig_color_space: ZuneColorSpace, + width: u16, + height: u16, + limits: Limits, + // For API compatibility with the previous jpeg_decoder wrapper. + // Can be removed later, which would be an API break. + phantom: PhantomData, } -impl JpegDecoder { +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) - } + let mut input = Vec::new(); + let mut r = r; + r.read_to_end(&mut input)?; + let mut decoder = zune_jpeg::JpegDecoder::new(input.as_slice()); + decoder.decode_headers().map_err(ImageError::from_jpeg)?; + // now that we've decoded the headers we can `.unwrap()` + // all these functions that only fail if called before decoding the headers + let (width, height) = decoder.dimensions().unwrap(); + // JPEG can only express dimensions up to 65535x65535, so this conversion cannot fail + let width: u16 = width.try_into().unwrap(); + let height: u16 = height.try_into().unwrap(); + let orig_color_space = decoder.get_output_colorspace().unwrap(); + // Limits are disabled by default in the constructor for all decoders + let limits = Limits::no_limits(); + Ok(JpegDecoder { + input, + orig_color_space, + width, + height, + limits, + phantom: PhantomData, + }) } } -impl<'a, R: 'a + Read> ImageDecoder<'a> for JpegDecoder { - type Reader = JpegReader; - +impl ImageDecoder for JpegDecoder { fn dimensions(&self) -> (u32, u32) { - ( - u32::from(self.metadata.width), - u32::from(self.metadata.height), - ) + (u32::from(self.width), u32::from(self.height)) } fn color_type(&self) -> ColorType { - ColorType::from_jpeg(self.metadata.pixel_format) + ColorType::from_jpeg(self.orig_color_space) } - fn icc_profile(&mut self) -> Option> { - self.decoder.icc_profile() + fn icc_profile(&mut self) -> ImageResult>> { + let mut decoder = zune_jpeg::JpegDecoder::new(&self.input); + decoder.decode_headers().map_err(ImageError::from_jpeg)?; + Ok(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(self, buf: &mut [u8]) -> ImageResult<()> { + let advertised_len = self.total_bytes(); + let actual_len = buf.len() as u64; - 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, - }; + if actual_len != advertised_len { + return Err(ImageError::Decoding(DecodingError::new( + ImageFormat::Jpeg.into(), + format!( + "Length of the decoded data {actual_len}\ + doesn't match the advertised dimensions of the image\ + that imply length {advertised_len}" + ), + ))); + } - buf.copy_from_slice(&data); + let mut decoder = new_zune_decoder(&self.input, self.orig_color_space, self.limits); + decoder.decode_into(buf).map_err(ImageError::from_jpeg)?; 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; + fn set_limits(&mut self, limits: Limits) -> ImageResult<()> { + limits.check_support(&crate::io::LimitSupport::default())?; + let (width, height) = self.dimensions(); + limits.check_dimensions(width, height)?; + self.limits = limits; + Ok(()) } - output + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) + } } 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!(), + fn from_jpeg(colorspace: ZuneColorSpace) -> ColorType { + let colorspace = to_supported_color_space(colorspace); + use zune_core::colorspace::ColorSpace::*; + match colorspace { + // As of zune-jpeg 0.3.13 the output is always 8-bit, + // but support for 16-bit JPEG might be added in the future. + RGB => ColorType::Rgb8, + RGBA => ColorType::Rgba8, + Luma => ColorType::L8, + LumaA => ColorType::La8, + // to_supported_color_space() doesn't return any of the other variants + _ => unreachable!(), } } } +fn to_supported_color_space(orig: ZuneColorSpace) -> ZuneColorSpace { + use zune_core::colorspace::ColorSpace::*; + match orig { + RGB | RGBA | Luma | LumaA => orig, + // the rest is not supported by `image` so it will be converted to RGB during decoding + _ => RGB, + } +} + +fn new_zune_decoder( + input: &[u8], + orig_color_space: ZuneColorSpace, + limits: Limits, +) -> zune_jpeg::JpegDecoder<&[u8]> { + let target_color_space = to_supported_color_space(orig_color_space); + let mut options = + zune_core::options::DecoderOptions::default().jpeg_set_out_colorspace(target_color_space); + options = options.set_max_width(match limits.max_image_width { + Some(max_width) => max_width as usize, // u32 to usize never truncates + None => usize::MAX, + }); + options = options.set_max_height(match limits.max_image_height { + Some(max_height) => max_height as usize, // u32 to usize never truncates + None => usize::MAX, + }); + zune_jpeg::JpegDecoder::new_with_options(input, options) +} + impl ImageError { - fn from_jpeg(err: jpeg::Error) -> ImageError { - use jpeg::Error::*; + fn from_jpeg(err: zune_jpeg::errors::DecodeErrors) -> ImageError { + use zune_jpeg::errors::DecodeErrors::*; 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); - 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); - } + LargeDimensions(_) => ImageError::Limits(LimitError::from_kind( + crate::error::LimitErrorKind::DimensionError, + )), + err => ImageError::Decoding(DecodingError::new(ImageFormat::Jpeg.into(), err)), } - - 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]); - }); } } diff --git a/src/codecs/jpeg/encoder.rs b/src/codecs/jpeg/encoder.rs index b350595f6f..ebf2532524 100644 --- a/src/codecs/jpeg/encoder.rs +++ b/src/codecs/jpeg/encoder.rs @@ -9,7 +9,7 @@ use crate::error::{ }; use crate::image::{ImageEncoder, ImageFormat}; use crate::utils::clamp; -use crate::{ColorType, GenericImageView, ImageBuffer, Luma, LumaA, Pixel, Rgb, Rgba}; +use crate::{ExtendedColorType, GenericImageView, ImageBuffer, Luma, LumaA, Pixel, Rgb, Rgba}; use super::entropy::build_huff_lut_const; use super::transform; @@ -445,10 +445,9 @@ impl JpegEncoder { image: &[u8], width: u32, height: u32, - color_type: ColorType, + color_type: ExtendedColorType, ) -> ImageResult<()> { - let expected_buffer_len = - (width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64); + let expected_buffer_len = color_type.buffer_size(width, height); assert_eq!( expected_buffer_len, image.len() as u64, @@ -457,22 +456,22 @@ impl JpegEncoder { ); match color_type { - ColorType::L8 => { + ExtendedColorType::L8 => { let image: ImageBuffer, _> = ImageBuffer::from_raw(width, height, image).unwrap(); self.encode_image(&image) } - ColorType::La8 => { + ExtendedColorType::La8 => { let image: ImageBuffer, _> = ImageBuffer::from_raw(width, height, image).unwrap(); self.encode_image(&image) } - ColorType::Rgb8 => { + ExtendedColorType::Rgb8 => { let image: ImageBuffer, _> = ImageBuffer::from_raw(width, height, image).unwrap(); self.encode_image(&image) } - ColorType::Rgba8 => { + ExtendedColorType::Rgba8 => { let image: ImageBuffer, _> = ImageBuffer::from_raw(width, height, image).unwrap(); self.encode_image(&image) @@ -480,7 +479,7 @@ impl JpegEncoder { _ => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Jpeg.into(), - UnsupportedErrorKind::Color(color_type.into()), + UnsupportedErrorKind::Color(color_type), ), )), } @@ -578,7 +577,7 @@ impl JpegEncoder { build_scan_header(&mut buf, &self.components[..num_components]); self.writer.write_segment(SOS, &buf)?; - if color_type.has_color() { + if let ExtendedColorType::Rgb8 = color_type { self.encode_rgb(image) } else { self.encode_gray(image) @@ -673,7 +672,7 @@ impl ImageEncoder for JpegEncoder { buf: &[u8], width: u32, height: u32, - color_type: ColorType, + color_type: ExtendedColorType, ) -> ImageResult<()> { self.encode(buf, width, height, color_type) } @@ -855,10 +854,9 @@ mod tests { #[cfg(feature = "benchmarks")] use test::Bencher; - use crate::color::ColorType; use crate::error::ParameterErrorKind::DimensionMismatch; use crate::image::ImageDecoder; - use crate::{ImageEncoder, ImageError}; + use crate::{ExtendedColorType, ImageEncoder, ImageError}; use super::super::JpegDecoder; use super::{ @@ -887,7 +885,7 @@ mod tests { { let encoder = JpegEncoder::new_with_quality(&mut encoded_img, 100); encoder - .write_image(&img, 1, 1, ColorType::Rgb8) + .write_image(&img, 1, 1, ExtendedColorType::Rgb8) .expect("Could not encode image"); } @@ -913,7 +911,7 @@ mod tests { { let encoder = JpegEncoder::new_with_quality(&mut encoded_img, 100); encoder - .write_image(&img[..], 2, 2, ColorType::L8) + .write_image(&img[..], 2, 2, ExtendedColorType::L8) .expect("Could not encode image"); } @@ -963,7 +961,7 @@ mod tests { // 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); + let result = encoder.write_image(&img, 65_536, 1, ExtendedColorType::L8); match result { Err(ImageError::Parameter(err)) => { assert_eq!(err.kind(), DimensionMismatch) diff --git a/src/codecs/openexr.rs b/src/codecs/openexr.rs index b28955e63c..4ddffe3a41 100644 --- a/src/codecs/openexr.rs +++ b/src/codecs/openexr.rs @@ -23,12 +23,11 @@ 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::io::{Cursor, Read, Seek, Write}; + +use std::io::{BufRead, Seek, Write}; /// An OpenEXR decoder. Immediately reads the meta data from the file. #[derive(Debug)] @@ -46,7 +45,7 @@ pub struct OpenExrDecoder { alpha_present_in_file: bool, } -impl OpenExrDecoder { +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. @@ -105,9 +104,7 @@ impl OpenExrDecoder { } } -impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for OpenExrDecoder { - type Reader = Cursor>; - +impl ImageDecoder for OpenExrDecoder { fn dimensions(&self) -> (u32, u32) { let size = self .selected_exr_header() @@ -134,27 +131,9 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for OpenExrDecoder { } } - /// 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; + fn read_image(self, unaligned_bytes: &mut [u8]) -> 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; @@ -212,14 +191,6 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for OpenExrDecoder { ) .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)?; @@ -232,6 +203,10 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for OpenExrDecoder { )); Ok(()) } + + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) + } } /// Write a raw byte buffer of pixels, @@ -245,37 +220,15 @@ fn write_buffer( unaligned_bytes: &[u8], width: u32, height: u32, - color_type: ColorType, + color_type: ExtendedColorType, ) -> 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", - ))); - } - } - - let bytes_per_pixel = color_type.bytes_per_pixel() as usize; + let bytes_per_pixel = color_type.bits_per_pixel() as usize / 8; match color_type { - ColorType::Rgb32F => { - exr::prelude::Image // TODO compression method zip?? + ExtendedColorType::Rgb32F => { + Image // TODO compression method zip?? ::from_channels( (width, height), SpecificChannels::rgb(|pixel: Vec2| { @@ -295,8 +248,8 @@ fn write_buffer( .map_err(to_image_err)?; } - ColorType::Rgba32F => { - exr::prelude::Image // TODO compression method zip?? + ExtendedColorType::Rgba32F => { + Image // TODO compression method zip?? ::from_channels( (width, height), SpecificChannels::rgba(|pixel: Vec2| { @@ -358,10 +311,9 @@ where buf: &[u8], width: u32, height: u32, - color_type: ColorType, + color_type: ExtendedColorType, ) -> ImageResult<()> { - let expected_buffer_len = - (width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64); + let expected_buffer_len = color_type.buffer_size(width, height); assert_eq!( expected_buffer_len, buf.len() as u64, @@ -384,12 +336,13 @@ fn to_image_err(exr_error: Error) -> ImageError { mod test { use super::*; - use std::io::BufReader; + use std::fs::File; + use std::io::{BufReader, Cursor}; use std::path::{Path, PathBuf}; use crate::buffer_::{Rgb32FImage, Rgba32FImage}; use crate::error::{LimitError, LimitErrorKind}; - use crate::{ImageBuffer, Rgb, Rgba}; + use crate::{DynamicImage, ImageBuffer, Rgb, Rgba}; const BASE_PATH: &[&str] = &[".", "tests", "images", "exr"]; @@ -402,7 +355,7 @@ mod test { bytemuck::cast_slice(image.as_raw().as_slice()), image.width(), image.height(), - ColorType::Rgb32F, + ExtendedColorType::Rgb32F, ) } @@ -415,25 +368,25 @@ mod test { bytemuck::cast_slice(image.as_raw().as_slice()), image.width(), image.height(), - ColorType::Rgba32F, + ExtendedColorType::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_as_rgba_image(BufReader::new(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_as_rgb_image(BufReader::new(File::open(path)?)) } /// Read the file from the specified path into an `Rgb32FImage`. - fn read_as_rgb_image(read: impl Read + Seek) -> ImageResult { + fn read_as_rgb_image(read: impl BufRead + Seek) -> ImageResult { let decoder = OpenExrDecoder::with_alpha_preference(read, Some(false))?; let (width, height) = decoder.dimensions(); - let buffer: Vec = decoder_to_vec(decoder)?; + let buffer: Vec = crate::image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(width, height, buffer) // this should be the only reason for the "from raw" call to fail, @@ -444,10 +397,10 @@ mod test { } /// Read the file from the specified path into an `Rgba32FImage`. - fn read_as_rgba_image(read: impl Read + Seek) -> ImageResult { + fn read_as_rgba_image(read: impl BufRead + Seek) -> ImageResult { let decoder = OpenExrDecoder::with_alpha_preference(read, Some(true))?; let (width, height) = decoder.dimensions(); - let buffer: Vec = decoder_to_vec(decoder)?; + let buffer: Vec = crate::image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(width, height, buffer) // this should be the only reason for the "from raw" call to fail, @@ -465,26 +418,25 @@ mod test { #[cfg(feature = "hdr")] { + use crate::codecs::hdr::HdrDecoder; + 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 hdr_decoder = + HdrDecoder::new(BufReader::new(File::open(reference_path).unwrap())).unwrap(); + let hdr: Rgb32FImage = match DynamicImage::from_decoder(hdr_decoder).unwrap() { + DynamicImage::ImageRgb32F(image) => image, + _ => panic!("expected rgb32f image"), + }; 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 - ); + assert_eq!(exr_pixels.dimensions(), hdr.dimensions()); - for (expected, found) in hdr.iter().zip(exr_pixels.pixels()) { + for (expected, found) in hdr.pixels().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 diff --git a/src/codecs/png.rs b/src/codecs/png.rs index 0e60f35252..bdaa6b3aac 100644 --- a/src/codecs/png.rs +++ b/src/codecs/png.rs @@ -7,7 +7,7 @@ //! use std::fmt; -use std::io::{self, Read, Write}; +use std::io::{BufRead, Seek, Write}; use png::{BlendOp, DisposeOp}; @@ -25,96 +25,14 @@ use crate::{DynamicImage, GenericImage, ImageBuffer, Luma, LumaA, Rgb, Rgba, Rgb // 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 { +pub struct PngDecoder { color_type: ColorType, reader: png::Reader, limits: Limits, } -impl PngDecoder { +impl PngDecoder { /// Creates a new decoder that decodes from the stream ```r``` pub fn new(r: R) -> ImageResult> { Self::with_limits(r, Limits::no_limits()) @@ -224,8 +142,8 @@ impl PngDecoder { /// 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) + pub fn apng(self) -> ImageResult> { + Ok(ApngDecoder::new(self)) } /// Returns if the image contains an animation. @@ -234,8 +152,8 @@ impl PngDecoder { /// 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() + pub fn is_apng(&self) -> ImageResult { + Ok(self.reader.info().animation_control.is_some()) } } @@ -246,9 +164,7 @@ fn unsupported_color(ect: ExtendedColorType) -> ImageError { )) } -impl<'a, R: 'a + Read> ImageDecoder<'a> for PngDecoder { - type Reader = PngReader; - +impl ImageDecoder for PngDecoder { fn dimensions(&self) -> (u32, u32) { self.reader.info().size() } @@ -257,12 +173,8 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for PngDecoder { 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 icc_profile(&mut self) -> ImageResult>> { + Ok(self.reader.info().icc_profile.as_ref().map(|x| x.to_vec())) } fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { @@ -287,9 +199,8 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for PngDecoder { Ok(()) } - fn scanline_bytes(&self) -> u64 { - let width = self.reader.info().width; - self.reader.output_line_size(width) as u64 + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) } fn set_limits(&mut self, limits: Limits) -> ImageResult<()> { @@ -310,7 +221,7 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for PngDecoder { /// [`AnimationDecoder`]: ../trait.AnimationDecoder.html /// [`PngDecoder`]: struct.PngDecoder.html /// [`PngDecoder::apng`]: struct.PngDecoder.html#method.apng -pub struct ApngDecoder { +pub struct ApngDecoder { inner: PngDecoder, /// The current output buffer. current: Option, @@ -324,7 +235,7 @@ pub struct ApngDecoder { has_thumbnail: bool, } -impl ApngDecoder { +impl ApngDecoder { fn new(inner: PngDecoder) -> Self { let info = inner.reader.info(); let remaining = match info.animation_control() { @@ -508,11 +419,11 @@ impl ApngDecoder { } } -impl<'a, R: Read + 'a> AnimationDecoder<'a> for ApngDecoder { +impl<'a, R: BufRead + Seek + 'a> AnimationDecoder<'a> for ApngDecoder { fn into_frames(self) -> Frames<'a> { - struct FrameIterator(ApngDecoder); + struct FrameIterator(ApngDecoder); - impl Iterator for FrameIterator { + impl Iterator for FrameIterator { type Item = ImageResult; fn next(&mut self) -> Option { @@ -559,12 +470,6 @@ pub enum CompressionType { 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. @@ -594,7 +499,7 @@ pub enum FilterType { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[non_exhaustive] enum BadPngRepresentation { - ColorType(ColorType), + ColorType(ExtendedColorType), } impl PngEncoder { @@ -631,35 +536,27 @@ impl PngEncoder { } } - /// 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, + color: ExtendedColorType, ) -> 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), + ExtendedColorType::L8 => (png::ColorType::Grayscale, png::BitDepth::Eight), + ExtendedColorType::L16 => (png::ColorType::Grayscale, png::BitDepth::Sixteen), + ExtendedColorType::La8 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight), + ExtendedColorType::La16 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen), + ExtendedColorType::Rgb8 => (png::ColorType::Rgb, png::BitDepth::Eight), + ExtendedColorType::Rgb16 => (png::ColorType::Rgb, png::BitDepth::Sixteen), + ExtendedColorType::Rgba8 => (png::ColorType::Rgba, png::BitDepth::Eight), + ExtendedColorType::Rgba16 => (png::ColorType::Rgba, png::BitDepth::Sixteen), _ => { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Png.into(), - UnsupportedErrorKind::Color(color.into()), + UnsupportedErrorKind::Color(color), ), )) } @@ -708,17 +605,16 @@ impl ImageEncoder for PngEncoder { buf: &[u8], width: u32, height: u32, - color_type: ColorType, + color_type: ExtendedColorType, ) -> ImageResult<()> { use byteorder::{BigEndian, ByteOrder, NativeEndian}; - use ColorType::*; + use ExtendedColorType::*; - let expected_bufffer_len = - (width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64); + let expected_buffer_len = color_type.buffer_size(width, height); assert_eq!( - expected_bufffer_len, + expected_buffer_len, buf.len() as u64, - "Invalid buffer length: expected {expected_bufffer_len} got {} for {width}x{height} image", + "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image", buf.len(), ); @@ -789,16 +685,14 @@ impl std::error::Error for BadPngRepresentation {} #[cfg(test)] mod tests { use super::*; - use crate::ImageOutputFormat; - - use std::io::Cursor; + use std::io::{BufReader, Cursor, Read}; #[test] fn ensure_no_decoder_off_by_one() { - let dec = PngDecoder::new( + let dec = PngDecoder::new(BufReader::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()]; @@ -809,9 +703,7 @@ mod tests { "Image MUST have the Rgb8 format" ]; - #[allow(deprecated)] - let correct_bytes = dec - .into_reader() + let correct_bytes = crate::image::decoder_to_vec(dec) .expect("Unable to read file") .bytes() .map(|x| x.expect("Unable to read byte")) @@ -829,7 +721,7 @@ mod tests { .unwrap(); not_png[0] = 0; - let error = PngDecoder::new(¬_png[..]).err().unwrap(); + let error = PngDecoder::new(Cursor::new(¬_png)).err().unwrap(); let _ = error .source() .unwrap() @@ -842,6 +734,6 @@ mod tests { // 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); + let _ = image.write_to(&mut target, ImageFormat::Png); } } diff --git a/src/codecs/pnm/decoder.rs b/src/codecs/pnm/decoder.rs index 470bc43d67..1c619c6fb1 100644 --- a/src/codecs/pnm/decoder.rs +++ b/src/codecs/pnm/decoder.rs @@ -1,8 +1,6 @@ use std::error; use std::fmt::{self, Display}; -use std::io::{self, BufRead, Cursor, Read}; -use std::marker::PhantomData; -use std::mem; +use std::io::{self, Read}; use std::num::ParseIntError; use std::str::{self, FromStr}; @@ -12,7 +10,7 @@ use crate::color::{ColorType, ExtendedColorType}; use crate::error::{ DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, }; -use crate::image::{self, ImageDecoder, ImageFormat}; +use crate::image::{ImageDecoder, ImageFormat}; use crate::utils; use byteorder::{BigEndian, ByteOrder, NativeEndian}; @@ -262,7 +260,7 @@ pub struct PnmDecoder { tuple: TupleType, } -impl PnmDecoder { +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()?; @@ -362,7 +360,7 @@ impl PnmDecoder { } } -trait HeaderReader: BufRead { +trait HeaderReader: Read { /// Reads the two magic constant bytes fn read_magic_constant(&mut self) -> ImageResult<[u8; 2]> { let mut magic: [u8; 2] = [0, 0]; @@ -419,6 +417,20 @@ trait HeaderReader: BufRead { Ok(string) } + fn read_next_line(&mut self) -> ImageResult { + let mut buffer = Vec::new(); + loop { + let mut byte = [0]; + if self.read(&mut byte)? == 0 || byte[0] == b'\n' { + break; + } + buffer.push(byte[0]); + } + + String::from_utf8(buffer) + .map_err(|e| ImageError::Decoding(DecodingError::new(ImageFormat::Pnm.into(), e))) + } + fn read_next_u32(&mut self) -> ImageResult { let s = self.read_next_string()?; s.parse::() @@ -487,16 +499,15 @@ trait HeaderReader: BufRead { Some(Ok(c)) => return Err(DecoderError::NotNewlineAfterP7Magic(c).into()), } - let mut line = String::new(); + let mut line; 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 { + line = self.read_next_line()?; + if line.is_empty() { return Err(DecoderError::UnexpectedPnmHeaderEnd.into()); } if line.as_bytes()[0] == b'#' { @@ -568,27 +579,9 @@ trait HeaderReader: BufRead { } } -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; +impl HeaderReader for R where R: Read {} +impl ImageDecoder for PnmDecoder { fn dimensions(&self) -> (u32, u32) { (self.header.width(), self.header.height()) } @@ -615,13 +608,6 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for PnmDecoder { } } - 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 { @@ -633,6 +619,10 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for PnmDecoder { TupleType::GrayU16 => self.read_samples::(1, buf), } } + + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) + } } impl PnmDecoder { diff --git a/src/codecs/pnm/encoder.rs b/src/codecs/pnm/encoder.rs index 87c62f730a..bd86cdb52a 100644 --- a/src/codecs/pnm/encoder.rs +++ b/src/codecs/pnm/encoder.rs @@ -7,7 +7,7 @@ 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::color::ExtendedColorType; use crate::error::{ ImageError, ImageResult, ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, @@ -144,27 +144,20 @@ impl PnmEncoder { image: S, width: u32, height: u32, - color: ColorType, + color: ExtendedColorType, ) -> ImageResult<()> where S: Into>, { let image = image.into(); match self.header { - HeaderStrategy::Dynamic => { - self.write_dynamic_header(image, width, height, color.into()) - } + HeaderStrategy::Dynamic => self.write_dynamic_header(image, width, height, color), HeaderStrategy::Subtype(subtype) => { - self.write_subtyped_header(subtype, image, width, height, color.into()) + self.write_subtyped_header(subtype, image, width, height, color) + } + HeaderStrategy::Chosen(ref header) => { + Self::write_with_header(&mut self.writer, header, image, width, height, color) } - HeaderStrategy::Chosen(ref header) => Self::write_with_header( - &mut self.writer, - header, - image, - width, - height, - color.into(), - ), } } @@ -295,10 +288,9 @@ impl ImageEncoder for PnmEncoder { buf: &[u8], width: u32, height: u32, - color_type: ColorType, + color_type: ExtendedColorType, ) -> ImageResult<()> { - let expected_buffer_len = - (width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64); + let expected_buffer_len = color_type.buffer_size(width, height); assert_eq!( expected_buffer_len, buf.len() as u64, diff --git a/src/codecs/pnm/mod.rs b/src/codecs/pnm/mod.rs index de8612d3eb..ac9d390fa9 100644 --- a/src/codecs/pnm/mod.rs +++ b/src/codecs/pnm/mod.rs @@ -20,11 +20,11 @@ mod header; #[cfg(test)] mod tests { use super::*; - use crate::color::ColorType; use crate::image::ImageDecoder; + use crate::ExtendedColorType; use byteorder::{ByteOrder, NativeEndian}; - fn execute_roundtrip_default(buffer: &[u8], width: u32, height: u32, color: ColorType) { + fn execute_roundtrip_default(buffer: &[u8], width: u32, height: u32, color: ExtendedColorType) { let mut encoded_buffer = Vec::new(); { @@ -47,7 +47,7 @@ mod tests { assert_eq!(header.width(), width); assert_eq!(header.height(), height); - assert_eq!(loaded_color, color); + assert_eq!(ExtendedColorType::from(loaded_color), color); assert_eq!(loaded_image.as_slice(), buffer); } @@ -55,7 +55,7 @@ mod tests { buffer: &[u8], width: u32, height: u32, - color: ColorType, + color: ExtendedColorType, subtype: PnmSubtype, ) { let mut encoded_buffer = Vec::new(); @@ -81,11 +81,11 @@ mod tests { assert_eq!(header.width(), width); assert_eq!(header.height(), height); assert_eq!(header.subtype(), subtype); - assert_eq!(loaded_color, color); + assert_eq!(ExtendedColorType::from(loaded_color), color); assert_eq!(loaded_image.as_slice(), buffer); } - fn execute_roundtrip_u16(buffer: &[u16], width: u32, height: u32, color: ColorType) { + fn execute_roundtrip_u16(buffer: &[u16], width: u32, height: u32, color: ExtendedColorType) { let mut encoded_buffer = Vec::new(); { @@ -111,7 +111,7 @@ mod tests { assert_eq!(header.width(), width); assert_eq!(header.height(), height); - assert_eq!(loaded_color, color); + assert_eq!(ExtendedColorType::from(loaded_color), color); assert_eq!(loaded_image, buffer_u8); } @@ -125,20 +125,20 @@ mod tests { 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_default(&buf, 4, 4, ExtendedColorType::L8); + execute_roundtrip_with_subtype(&buf, 4, 4, ExtendedColorType::L8, PnmSubtype::ArbitraryMap); execute_roundtrip_with_subtype( &buf, 4, 4, - ColorType::L8, + ExtendedColorType::L8, PnmSubtype::Graymap(SampleEncoding::Ascii), ); execute_roundtrip_with_subtype( &buf, 4, 4, - ColorType::L8, + ExtendedColorType::L8, PnmSubtype::Graymap(SampleEncoding::Binary), ); } @@ -157,20 +157,26 @@ mod tests { 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_default(&buf, 3, 3, ExtendedColorType::Rgb8); execute_roundtrip_with_subtype( &buf, 3, 3, - ColorType::Rgb8, + ExtendedColorType::Rgb8, + PnmSubtype::ArbitraryMap, + ); + execute_roundtrip_with_subtype( + &buf, + 3, + 3, + ExtendedColorType::Rgb8, PnmSubtype::Pixmap(SampleEncoding::Binary), ); execute_roundtrip_with_subtype( &buf, 3, 3, - ColorType::Rgb8, + ExtendedColorType::Rgb8, PnmSubtype::Pixmap(SampleEncoding::Ascii), ); } @@ -179,6 +185,6 @@ mod tests { fn roundtrip_u16() { let buf: [u16; 6] = [0, 1, 0xFFFF, 0x1234, 0x3412, 0xBEAF]; - execute_roundtrip_u16(&buf, 6, 1, ColorType::L16); + execute_roundtrip_u16(&buf, 6, 1, ExtendedColorType::L16); } } diff --git a/src/codecs/qoi.rs b/src/codecs/qoi.rs index 2a1627e486..809b9f9131 100644 --- a/src/codecs/qoi.rs +++ b/src/codecs/qoi.rs @@ -2,9 +2,9 @@ use crate::{ error::{DecodingError, EncodingError}, - ColorType, ImageDecoder, ImageEncoder, ImageError, ImageFormat, ImageResult, + ColorType, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageFormat, ImageResult, }; -use std::io::{Cursor, Read, Write}; +use std::io::{Read, Write}; /// QOI decoder pub struct QoiDecoder { @@ -22,9 +22,7 @@ where } } -impl<'a, R: Read + 'a> ImageDecoder<'a> for QoiDecoder { - type Reader = Cursor>; - +impl ImageDecoder for QoiDecoder { fn dimensions(&self) -> (u32, u32) { (self.decoder.header().width, self.decoder.header().height) } @@ -36,9 +34,13 @@ impl<'a, R: Read + 'a> ImageDecoder<'a> for QoiDecoder { } } - fn into_reader(mut self) -> ImageResult { - let buffer = self.decoder.decode_to_vec().map_err(decoding_error)?; - Ok(Cursor::new(buffer)) + fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { + self.decoder.decode_to_buf(buf).map_err(decoding_error)?; + Ok(()) + } + + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) } } @@ -69,17 +71,19 @@ impl ImageEncoder for QoiEncoder { buf: &[u8], width: u32, height: u32, - color_type: ColorType, + color_type: ExtendedColorType, ) -> ImageResult<()> { - if !matches!(color_type, ColorType::Rgba8 | ColorType::Rgb8) { + if !matches!( + color_type, + ExtendedColorType::Rgba8 | ExtendedColorType::Rgb8 + ) { return Err(ImageError::Encoding(EncodingError::new( ImageFormat::Qoi.into(), format!("unsupported color type {color_type:?}. Supported are Rgba8 and Rgb8."), ))); } - let expected_buffer_len = - (width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64); + let expected_buffer_len = color_type.buffer_size(width, height); assert_eq!( expected_buffer_len, buf.len() as u64, diff --git a/src/codecs/tga/decoder.rs b/src/codecs/tga/decoder.rs index 4bc16c2c8e..f3046edd7f 100644 --- a/src/codecs/tga/decoder.rs +++ b/src/codecs/tga/decoder.rs @@ -4,13 +4,10 @@ use crate::{ error::{ ImageError, ImageResult, LimitError, LimitErrorKind, UnsupportedError, UnsupportedErrorKind, }, - image::{ImageDecoder, ImageFormat, ImageReadBuffer}, + image::{ImageDecoder, ImageFormat}, }; use byteorder::ReadBytesExt; -use std::{ - io::{self, Read, Seek}, - mem, -}; +use std::io::{self, Read}; struct ColorMap { /// sizes in bytes @@ -60,13 +57,9 @@ pub struct TgaDecoder { header: Header, color_map: Option, - - // Used in read_scanline - line_read: Option, - line_remain_buff: Vec, } -impl TgaDecoder { +impl TgaDecoder { /// Create a new decoder that decodes from the stream `r` pub fn new(r: R) -> ImageResult> { let mut decoder = TgaDecoder { @@ -83,9 +76,6 @@ impl TgaDecoder { header: Header::default(), color_map: None, - - line_read: None, - line_remain_buff: Vec::new(), }; decoder.read_metadata()?; Ok(decoder) @@ -182,7 +172,7 @@ impl TgaDecoder { /// is present fn read_image_id(&mut self) -> ImageResult<()> { self.r - .seek(io::SeekFrom::Current(i64::from(self.header.id_length)))?; + .read_exact(&mut vec![0; self.header.id_length as usize])?; Ok(()) } @@ -288,39 +278,6 @@ impl TgaDecoder { 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 @@ -377,42 +334,9 @@ impl TgaDecoder { 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; - +impl ImageDecoder for TgaDecoder { fn dimensions(&self) -> (u32, u32) { (self.width as u32, self.height as u32) } @@ -426,23 +350,6 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TgaDecoder { .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( - #[allow(deprecated)] - 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())); @@ -491,15 +398,8 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TgaDecoder { 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)) + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) } } diff --git a/src/codecs/tga/encoder.rs b/src/codecs/tga/encoder.rs index 0bd415b38d..ec74c905f8 100644 --- a/src/codecs/tga/encoder.rs +++ b/src/codecs/tga/encoder.rs @@ -1,7 +1,7 @@ use super::header::Header; use crate::{ - codecs::tga::header::ImageType, error::EncodingError, ColorType, ImageEncoder, ImageError, - ImageFormat, ImageResult, + codecs::tga::header::ImageType, error::EncodingError, ExtendedColorType, ImageEncoder, + ImageError, ImageFormat, ImageResult, }; use std::{error, fmt, io::Write}; @@ -85,10 +85,14 @@ impl TgaEncoder { } /// Writes the run-length encoded buffer to the writer - fn run_length_encode(&mut self, image: &[u8], color_type: ColorType) -> ImageResult<()> { + fn run_length_encode( + &mut self, + image: &[u8], + color_type: ExtendedColorType, + ) -> ImageResult<()> { use PacketType::*; - let bytes_per_pixel = color_type.bytes_per_pixel(); + let bytes_per_pixel = color_type.bits_per_pixel() / 8; let capacity_in_bytes = usize::from(MAX_RUN_LENGTH) * usize::from(bytes_per_pixel); // Buffer to temporarily store pixels @@ -162,10 +166,9 @@ impl TgaEncoder { buf: &[u8], width: u32, height: u32, - color_type: ColorType, + color_type: ExtendedColorType, ) -> ImageResult<()> { - let expected_buffer_len = - (width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64); + let expected_buffer_len = color_type.buffer_size(width, height); assert_eq!( expected_buffer_len, buf.len() as u64, @@ -192,10 +195,11 @@ impl TgaEncoder { // Write run-length encoded image data match color_type { - ColorType::Rgb8 | ColorType::Rgba8 => { + ExtendedColorType::Rgb8 | ExtendedColorType::Rgba8 => { let mut image = Vec::from(buf); - for pixel in image.chunks_mut(usize::from(color_type.bytes_per_pixel())) { + for pixel in image.chunks_mut(usize::from(color_type.bits_per_pixel() / 8)) + { pixel.swap(0, 2); } @@ -210,10 +214,11 @@ impl TgaEncoder { // Write uncompressed image data match color_type { - ColorType::Rgb8 | ColorType::Rgba8 => { + ExtendedColorType::Rgb8 | ExtendedColorType::Rgba8 => { let mut image = Vec::from(buf); - for pixel in image.chunks_mut(usize::from(color_type.bytes_per_pixel())) { + for pixel in image.chunks_mut(usize::from(color_type.bits_per_pixel() / 8)) + { pixel.swap(0, 2); } @@ -237,7 +242,7 @@ impl ImageEncoder for TgaEncoder { buf: &[u8], width: u32, height: u32, - color_type: ColorType, + color_type: ExtendedColorType, ) -> ImageResult<()> { self.encode(buf, width, height, color_type) } @@ -246,7 +251,7 @@ impl ImageEncoder for TgaEncoder { #[cfg(test)] mod tests { use super::{EncoderError, TgaEncoder}; - use crate::{codecs::tga::TgaDecoder, ColorType, ImageDecoder, ImageError}; + use crate::{codecs::tga::TgaDecoder, ExtendedColorType, ImageDecoder, ImageError}; use std::{error::Error, io::Cursor}; #[test] @@ -260,7 +265,7 @@ mod tests { // 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); + let result = encoder.encode(&img, dimension, 1, ExtendedColorType::L8); match result { Err(ImageError::Encoding(err)) => { @@ -290,7 +295,7 @@ mod tests { // 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); + let result = encoder.encode(&img, 1, dimension, ExtendedColorType::L8); match result { Err(ImageError::Encoding(err)) => { @@ -317,7 +322,7 @@ mod tests { let mut encoded_data = Vec::new(); let encoder = TgaEncoder::new(&mut encoded_data).disable_rle(); encoder - .encode(&image, 5, 1, ColorType::Rgb8) + .encode(&image, 5, 1, ExtendedColorType::Rgb8) .expect("could not encode image"); encoded_data @@ -327,7 +332,7 @@ mod tests { let mut encoded_data = Vec::new(); let encoder = TgaEncoder::new(&mut encoded_data); encoder - .encode(&image, 5, 1, ColorType::Rgb8) + .encode(&image, 5, 1, ExtendedColorType::Rgb8) .expect("could not encode image"); encoded_data @@ -339,7 +344,12 @@ mod tests { mod compressed { use super::*; - fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec { + fn round_trip_image( + image: &[u8], + width: u32, + height: u32, + c: ExtendedColorType, + ) -> Vec { let mut encoded_data = Vec::new(); { let encoder = TgaEncoder::new(&mut encoded_data); @@ -359,7 +369,7 @@ mod tests { let image = [ 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, ]; - let decoded = round_trip_image(&image, 5, 1, ColorType::Rgb8); + let decoded = round_trip_image(&image, 5, 1, ExtendedColorType::Rgb8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } @@ -367,7 +377,7 @@ mod tests { #[test] fn round_trip_gray() { let image = [0, 1, 2]; - let decoded = round_trip_image(&image, 3, 1, ColorType::L8); + let decoded = round_trip_image(&image, 3, 1, ExtendedColorType::L8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } @@ -375,7 +385,7 @@ mod tests { #[test] fn round_trip_graya() { let image = [0, 1, 2, 3, 4, 5]; - let decoded = round_trip_image(&image, 1, 3, ColorType::La8); + let decoded = round_trip_image(&image, 1, 3, ExtendedColorType::La8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } @@ -383,7 +393,7 @@ mod tests { #[test] fn round_trip_single_pixel_rgb() { let image = [0, 1, 2]; - let decoded = round_trip_image(&image, 1, 1, ColorType::Rgb8); + let decoded = round_trip_image(&image, 1, 1, ExtendedColorType::Rgb8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } @@ -391,7 +401,7 @@ mod tests { #[test] fn round_trip_three_pixel_rgb() { let image = [0, 1, 2, 0, 1, 2, 0, 1, 2]; - let decoded = round_trip_image(&image, 3, 1, ColorType::Rgb8); + let decoded = round_trip_image(&image, 3, 1, ExtendedColorType::Rgb8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } @@ -399,7 +409,7 @@ mod tests { #[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); + let decoded = round_trip_image(&image, 3, 3, ExtendedColorType::Rgb8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } @@ -407,7 +417,7 @@ mod tests { #[test] fn round_trip_different() { let image = [0, 1, 2, 0, 1, 3, 0, 1, 4]; - let decoded = round_trip_image(&image, 3, 1, ColorType::Rgb8); + let decoded = round_trip_image(&image, 3, 1, ExtendedColorType::Rgb8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } @@ -415,7 +425,7 @@ mod tests { #[test] fn round_trip_different_2() { let image = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 4]; - let decoded = round_trip_image(&image, 4, 1, ColorType::Rgb8); + let decoded = round_trip_image(&image, 4, 1, ExtendedColorType::Rgb8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } @@ -423,7 +433,7 @@ mod tests { #[test] fn round_trip_different_3() { let image = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 4, 0, 1, 2]; - let decoded = round_trip_image(&image, 5, 1, ColorType::Rgb8); + let decoded = round_trip_image(&image, 5, 1, ExtendedColorType::Rgb8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } @@ -436,7 +446,7 @@ mod tests { let (width, height) = (image.width(), image.height()); let image = image.as_rgb8().unwrap().to_vec(); - let decoded = round_trip_image(&image, width, height, ColorType::Rgb8); + let decoded = round_trip_image(&image, width, height, ExtendedColorType::Rgb8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } @@ -445,7 +455,12 @@ mod tests { mod uncompressed { use super::*; - fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec { + fn round_trip_image( + image: &[u8], + width: u32, + height: u32, + c: ExtendedColorType, + ) -> Vec { let mut encoded_data = Vec::new(); { let encoder = TgaEncoder::new(&mut encoded_data).disable_rle(); @@ -464,7 +479,7 @@ mod tests { #[test] fn round_trip_single_pixel_rgb() { let image = [0, 1, 2]; - let decoded = round_trip_image(&image, 1, 1, ColorType::Rgb8); + let decoded = round_trip_image(&image, 1, 1, ExtendedColorType::Rgb8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } @@ -472,7 +487,7 @@ mod tests { #[test] fn round_trip_single_pixel_rgba() { let image = [0, 1, 2, 3]; - let decoded = round_trip_image(&image, 1, 1, ColorType::Rgba8); + let decoded = round_trip_image(&image, 1, 1, ExtendedColorType::Rgba8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } @@ -480,7 +495,7 @@ mod tests { #[test] fn round_trip_gray() { let image = [0, 1, 2]; - let decoded = round_trip_image(&image, 3, 1, ColorType::L8); + let decoded = round_trip_image(&image, 3, 1, ExtendedColorType::L8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } @@ -488,7 +503,7 @@ mod tests { #[test] fn round_trip_graya() { let image = [0, 1, 2, 3, 4, 5]; - let decoded = round_trip_image(&image, 1, 3, ColorType::La8); + let decoded = round_trip_image(&image, 1, 3, ExtendedColorType::La8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } @@ -496,7 +511,7 @@ mod tests { #[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); + let decoded = round_trip_image(&image, 3, 3, ExtendedColorType::Rgb8); assert_eq!(decoded.len(), image.len()); assert_eq!(decoded.as_slice(), image); } diff --git a/src/codecs/tga/header.rs b/src/codecs/tga/header.rs index c73770f24e..4460610100 100644 --- a/src/codecs/tga/header.rs +++ b/src/codecs/tga/header.rs @@ -1,6 +1,6 @@ use crate::{ error::{UnsupportedError, UnsupportedErrorKind}, - ColorType, ImageError, ImageFormat, ImageResult, + ExtendedColorType, ImageError, ImageFormat, ImageResult, }; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use std::io::{Read, Write}; @@ -80,7 +80,7 @@ pub(crate) struct Header { impl Header { /// Load the header with values from pixel information. pub(crate) fn from_pixel_info( - color_type: ColorType, + color_type: ExtendedColorType, width: u16, height: u16, use_rle: bool, @@ -89,19 +89,19 @@ impl Header { if width > 0 && height > 0 { let (num_alpha_bits, other_channel_bits, image_type) = match (color_type, use_rle) { - (ColorType::Rgba8, true) => (8, 24, ImageType::RunTrueColor), - (ColorType::Rgb8, true) => (0, 24, ImageType::RunTrueColor), - (ColorType::La8, true) => (8, 8, ImageType::RunGrayScale), - (ColorType::L8, true) => (0, 8, ImageType::RunGrayScale), - (ColorType::Rgba8, false) => (8, 24, ImageType::RawTrueColor), - (ColorType::Rgb8, false) => (0, 24, ImageType::RawTrueColor), - (ColorType::La8, false) => (8, 8, ImageType::RawGrayScale), - (ColorType::L8, false) => (0, 8, ImageType::RawGrayScale), + (ExtendedColorType::Rgba8, true) => (8, 24, ImageType::RunTrueColor), + (ExtendedColorType::Rgb8, true) => (0, 24, ImageType::RunTrueColor), + (ExtendedColorType::La8, true) => (8, 8, ImageType::RunGrayScale), + (ExtendedColorType::L8, true) => (0, 8, ImageType::RunGrayScale), + (ExtendedColorType::Rgba8, false) => (8, 24, ImageType::RawTrueColor), + (ExtendedColorType::Rgb8, false) => (0, 24, ImageType::RawTrueColor), + (ExtendedColorType::La8, false) => (8, 8, ImageType::RawGrayScale), + (ExtendedColorType::L8, false) => (0, 8, ImageType::RawGrayScale), _ => { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Tga.into(), - UnsupportedErrorKind::Color(color_type.into()), + UnsupportedErrorKind::Color(color_type), ), )) } diff --git a/src/codecs/tiff.rs b/src/codecs/tiff.rs index ab7091f37a..9f4dd7358e 100644 --- a/src/codecs/tiff.rs +++ b/src/codecs/tiff.rs @@ -8,7 +8,7 @@ extern crate tiff; -use std::io::{self, Cursor, Read, Seek, Write}; +use std::io::{self, BufRead, Cursor, Read, Seek, Write}; use std::marker::PhantomData; use std::mem; @@ -18,12 +18,11 @@ use crate::error::{ ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind, }; use crate::image::{ImageDecoder, ImageEncoder, ImageFormat}; -use crate::utils; /// Decoder for TIFF images. pub struct TiffDecoder where - R: Read + Seek, + R: BufRead + Seek, { dimensions: (u32, u32), color_type: ColorType, @@ -35,7 +34,7 @@ where impl TiffDecoder where - R: Read + Seek, + R: BufRead + Seek, { /// Create a new TiffDecoder. pub fn new(r: R) -> Result, ImageError> { @@ -184,9 +183,7 @@ impl Read for TiffReader { } } -impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TiffDecoder { - type Reader = TiffReader; - +impl ImageDecoder for TiffDecoder { fn dimensions(&self) -> (u32, u32) { self.dimensions } @@ -199,11 +196,11 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TiffDecoder { self.original_color_type } - fn icc_profile(&mut self) -> Option> { + fn icc_profile(&mut self) -> ImageResult>> { if let Some(decoder) = &mut self.inner { - decoder.get_tag_u8_vec(tiff::tags::Tag::Unknown(34675)).ok() + Ok(decoder.get_tag_u8_vec(tiff::tags::Tag::Unknown(34675)).ok()) } else { - None + Ok(None) } } @@ -227,28 +224,6 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TiffDecoder { 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 @@ -298,6 +273,10 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TiffDecoder { } Ok(()) } + + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) + } } /// Encoder for tiff images @@ -345,48 +324,53 @@ impl TiffEncoder { /// /// Panics if `width * height * color_type.bytes_per_pixel() != data.len()`. #[track_caller] - pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> { - let expected_buffer_len = - (width as u64 * height as u64).saturating_mul(color.bytes_per_pixel() as u64); + pub fn encode( + self, + buf: &[u8], + width: u32, + height: u32, + color_type: ExtendedColorType, + ) -> ImageResult<()> { + let expected_buffer_len = color_type.buffer_size(width, height); assert_eq!( expected_buffer_len, - data.len() as u64, + buf.len() as u64, "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image", - data.len(), + buf.len(), ); 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) + match color_type { + ExtendedColorType::L8 => { + encoder.write_image::(width, height, buf) } - ColorType::Rgb8 => { - encoder.write_image::(width, height, data) + ExtendedColorType::Rgb8 => { + encoder.write_image::(width, height, buf) } - ColorType::Rgba8 => { - encoder.write_image::(width, height, data) + ExtendedColorType::Rgba8 => { + encoder.write_image::(width, height, buf) } - ColorType::L16 => encoder.write_image::( + ExtendedColorType::L16 => encoder.write_image::( width, height, - u8_slice_as_u16(data)?, + u8_slice_as_u16(buf)?, ), - ColorType::Rgb16 => encoder.write_image::( + ExtendedColorType::Rgb16 => encoder.write_image::( width, height, - u8_slice_as_u16(data)?, + u8_slice_as_u16(buf)?, ), - ColorType::Rgba16 => encoder.write_image::( + ExtendedColorType::Rgba16 => encoder.write_image::( width, height, - u8_slice_as_u16(data)?, + u8_slice_as_u16(buf)?, ), _ => { return Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormat::Tiff.into(), - UnsupportedErrorKind::Color(color.into()), + UnsupportedErrorKind::Color(color_type), ), )) } @@ -404,7 +388,7 @@ impl ImageEncoder for TiffEncoder { buf: &[u8], width: u32, height: u32, - color_type: ColorType, + color_type: ExtendedColorType, ) -> ImageResult<()> { self.encode(buf, width, height, color_type) } diff --git a/src/codecs/webp/decoder.rs b/src/codecs/webp/decoder.rs index e590aeeead..7dd174346a 100644 --- a/src/codecs/webp/decoder.rs +++ b/src/codecs/webp/decoder.rs @@ -1,377 +1,112 @@ -use byteorder::{LittleEndian, ReadBytesExt}; -use std::io::{self, Cursor, Error, Read}; -use std::marker::PhantomData; -use std::{error, fmt, mem}; +use std::io::{BufRead, Read, Seek}; -use crate::error::{DecodingError, ImageError, ImageResult, ParameterError, ParameterErrorKind}; +use crate::buffer::ConvertBuffer; +use crate::error::{DecodingError, ImageError, ImageResult}; 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), -} +use crate::{AnimationDecoder, ColorType, Delay, Frame, Frames, RgbImage, Rgba, RgbaImage}; /// WebP Image format decoder. Currently only supports lossy RGB images or lossless RGBA images. pub struct WebPDecoder { - r: R, - image: WebPImage, + inner: image_webp::WebPDecoder, } -impl WebPDecoder { +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(()) + pub fn new(r: R) -> ImageResult { + Ok(Self { + inner: image_webp::WebPDecoder::new(r).map_err(ImageError::from_webp_decode)?, + }) } /// 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(), - } + self.inner.is_animated() } /// 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(), - ), - ))), - } + self.inner + .set_background_color(color.0) + .map_err(ImageError::from_webp_decode) } } -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) +impl ImageDecoder for WebPDecoder { + fn dimensions(&self) -> (u32, u32) { + self.inner.dimensions() } -} -/// 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()) + fn color_type(&self) -> ColorType { + if self.inner.has_alpha() { + ColorType::Rgba8 } else { - self.0.read_to_end(buf) + ColorType::Rgb8 } } -} -impl<'a, R: 'a + Read> ImageDecoder<'a> for WebPDecoder { - type Reader = WebpReader; + fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); - 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(), - } + self.inner + .read_image(buf) + .map_err(ImageError::from_webp_decode) } - 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 read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) } - 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 icc_profile(&mut self) -> ImageResult>> { + self.inner + .icc_profile() + .map_err(ImageError::from_webp_decode) } +} - 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); +impl<'a, R: 'a + Read + Seek> AnimationDecoder<'a> for WebPDecoder { + fn into_frames(self) -> Frames<'a> { + struct FramesInner { + decoder: WebPDecoder, + } + impl Iterator for FramesInner { + type Item = ImageResult; + + fn next(&mut self) -> Option { + let (width, height) = self.decoder.inner.dimensions(); + + let (img, delay) = if self.decoder.inner.has_alpha() { + let mut img = RgbaImage::new(width, height); + match self.decoder.inner.read_frame(&mut img) { + Ok(delay) => (img, delay), + Err(e) => return Some(Err(ImageError::from_webp_decode(e))), + } + } else { + let mut img = RgbImage::new(width, height); + match self.decoder.inner.read_frame(&mut img) { + Ok(delay) => (img.convert(), delay), + Err(e) => return Some(Err(ImageError::from_webp_decode(e))), + } + }; + + Some(Ok(Frame::from_parts( + img, + 0, + 0, + Delay::from_numer_denom_ms(delay, 1), + ))) } } - Ok(()) - } - fn icc_profile(&mut self) -> Option> { - if let WebPImage::Extended(extended) = &self.image { - extended.icc_profile() - } else { - None - } + Frames::new(Box::new(FramesInner { decoder: self })) } } -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(), +impl ImageError { + fn from_webp_decode(e: image_webp::DecodingError) -> Self { + match e { + image_webp::DecodingError::IoError(e) => ImageError::IoError(e), + _ => ImageError::Decoding(DecodingError::new(ImageFormat::WebP.into(), e)), } } } diff --git a/src/codecs/webp/encoder.rs b/src/codecs/webp/encoder.rs index d641f86bf4..fbc87fddb5 100644 --- a/src/codecs/webp/encoder.rs +++ b/src/codecs/webp/encoder.rs @@ -1,700 +1,25 @@ //! Encoding of WebP images. -use std::collections::BinaryHeap; -/// -/// Uses the simple encoding API from the [libwebp] library. -/// -/// [libwebp]: https://developers.google.com/speed/webp/docs/api#simple_encoding_api -use std::io::{self, Write}; -use std::slice::ChunksExact; -#[cfg(feature = "webp-encoder")] -use libwebp::{Encoder, PixelLayout, WebPMemory}; +use std::io::Write; -use crate::error::{ParameterError, ParameterErrorKind, UnsupportedError, UnsupportedErrorKind}; -use crate::flat::SampleLayout; -use crate::{ColorType, ImageEncoder, ImageError, ImageFormat, ImageResult}; +use crate::{ + error::{EncodingError, UnsupportedError, UnsupportedErrorKind}, + ExtendedColorType, ImageEncoder, ImageError, ImageFormat, ImageResult, +}; /// WebP Encoder. pub struct WebPEncoder { - writer: W, - quality: WebPQuality, - - chunk_buffer: Vec, - buffer: u64, - nbits: u8, -} - -/// WebP encoder quality. -#[derive(Debug, Copy, Clone)] -pub struct WebPQuality(Quality); - -#[derive(Debug, Copy, Clone)] -enum Quality { - Lossless, - #[allow(unused)] - #[deprecated = "Lossy encoding will be removed in a future version. See: https://github.com/image-rs/image/issues/1984"] - 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. - #[deprecated = "Lossy encoding will be removed in a future version. See: https://github.com/image-rs/image/issues/1984"] - pub fn lossy(quality: u8) -> Self { - #[allow(deprecated)] - Self(Quality::Lossy(quality.clamp(Self::MIN, Self::MAX))) - } -} - -impl Default for WebPQuality { - fn default() -> Self { - #[allow(deprecated)] - Self::lossy(WebPQuality::DEFAULT) - } + inner: image_webp::WebPEncoder, } impl WebPEncoder { - /// Create a new encoder that writes its output to `w`. - /// - /// Defaults to lossy encoding, see [`WebPQuality::DEFAULT`]. - #[deprecated = "Use `new_lossless` instead. Lossy encoding will be removed in a future version. See: https://github.com/image-rs/image/issues/1984"] - #[cfg(feature = "webp-encoder")] - pub fn new(w: W) -> Self { - #[allow(deprecated)] - WebPEncoder::new_with_quality(w, WebPQuality::default()) - } - - /// Create a new encoder with the specified quality, that writes its output to `w`. - #[deprecated = "Use `new_lossless` instead. Lossy encoding will be removed in a future version. See: https://github.com/image-rs/image/issues/1984"] - #[cfg(feature = "webp-encoder")] - pub fn new_with_quality(w: W, quality: WebPQuality) -> Self { - Self { - writer: w, - quality, - chunk_buffer: Vec::new(), - buffer: 0, - nbits: 0, - } - } - /// Create a new encoder that writes its output to `w`. /// /// Uses "VP8L" lossless encoding. pub fn new_lossless(w: W) -> Self { Self { - writer: w, - quality: WebPQuality::lossless(), - chunk_buffer: Vec::new(), - buffer: 0, - nbits: 0, - } - } - - fn write_bits(&mut self, bits: u64, nbits: u8) -> io::Result<()> { - debug_assert!(nbits <= 64); - - self.buffer |= bits << self.nbits; - self.nbits += nbits; - - if self.nbits >= 64 { - self.chunk_buffer.write_all(&self.buffer.to_le_bytes())?; - self.nbits -= 64; - self.buffer = bits.checked_shr((nbits - self.nbits) as u32).unwrap_or(0); + inner: image_webp::WebPEncoder::new(w), } - debug_assert!(self.nbits < 64); - Ok(()) - } - - fn flush(&mut self) -> io::Result<()> { - if self.nbits % 8 != 0 { - self.write_bits(0, 8 - self.nbits % 8)?; - } - if self.nbits > 0 { - self.chunk_buffer - .write_all(&self.buffer.to_le_bytes()[..self.nbits as usize / 8]) - .unwrap(); - self.buffer = 0; - self.nbits = 0; - } - Ok(()) - } - - fn write_single_entry_huffman_tree(&mut self, symbol: u8) -> io::Result<()> { - self.write_bits(1, 2)?; - if symbol <= 1 { - self.write_bits(0, 1)?; - self.write_bits(symbol as u64, 1)?; - } else { - self.write_bits(1, 1)?; - self.write_bits(symbol as u64, 8)?; - } - Ok(()) - } - - fn build_huffman_tree( - &mut self, - frequencies: &[u32], - lengths: &mut [u8], - codes: &mut [u16], - length_limit: u8, - ) -> bool { - assert_eq!(frequencies.len(), lengths.len()); - assert_eq!(frequencies.len(), codes.len()); - - if frequencies.iter().filter(|&&f| f > 0).count() <= 1 { - lengths.fill(0); - codes.fill(0); - return false; - } - - #[derive(Eq, PartialEq, Copy, Clone, Debug)] - struct Item(u32, u16); - impl Ord for Item { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - other.0.cmp(&self.0) - } - } - impl PartialOrd for Item { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - - // Build a huffman tree - let mut internal_nodes = Vec::new(); - let mut nodes = BinaryHeap::from_iter( - frequencies - .iter() - .enumerate() - .filter(|(_, &frequency)| frequency > 0) - .map(|(i, &frequency)| Item(frequency, i as u16)), - ); - while nodes.len() > 1 { - let Item(frequency1, index1) = nodes.pop().unwrap(); - let mut root = nodes.peek_mut().unwrap(); - internal_nodes.push((index1, root.1)); - *root = Item( - frequency1 + root.0, - internal_nodes.len() as u16 + frequencies.len() as u16 - 1, - ); - } - - // Walk the tree to assign code lengths - lengths.fill(0); - let mut stack = Vec::new(); - stack.push((nodes.pop().unwrap().1, 0)); - while let Some((node, depth)) = stack.pop() { - let node = node as usize; - if node < frequencies.len() { - lengths[node] = depth as u8; - } else { - let (left, right) = internal_nodes[node - frequencies.len()]; - stack.push((left, depth + 1)); - stack.push((right, depth + 1)); - } - } - - // Limit the codes to length length_limit - let mut max_length = 0; - for &length in lengths.iter() { - max_length = max_length.max(length); - } - if max_length > length_limit { - let mut counts = [0u32; 16]; - for &length in lengths.iter() { - counts[length.min(length_limit) as usize] += 1; - } - - let mut total = 0; - for (i, count) in counts - .iter() - .enumerate() - .skip(1) - .take(length_limit as usize) - { - total += count << (length_limit as usize - i); - } - - while total > 1u32 << length_limit { - let mut i = length_limit as usize - 1; - while counts[i] == 0 { - i -= 1; - } - counts[i] -= 1; - counts[length_limit as usize] -= 1; - counts[i + 1] += 2; - total -= 1; - } - - // assign new lengths - let mut len = length_limit; - let mut indexes = frequencies.iter().copied().enumerate().collect::>(); - indexes.sort_unstable_by_key(|&(_, frequency)| frequency); - for &(i, frequency) in indexes.iter() { - if frequency > 0 { - while counts[len as usize] == 0 { - len -= 1; - } - lengths[i] = len; - counts[len as usize] -= 1; - } - } - } - - // Assign codes - codes.fill(0); - let mut code = 0u32; - for len in 1..=length_limit { - for (i, &length) in lengths.iter().enumerate() { - if length == len { - codes[i] = (code as u16).reverse_bits() >> (16 - len); - code += 1; - } - } - code <<= 1; - } - assert_eq!(code, 2 << length_limit); - - true - } - - fn write_huffman_tree( - &mut self, - frequencies: &[u32], - lengths: &mut [u8], - codes: &mut [u16], - ) -> io::Result<()> { - if !self.build_huffman_tree(frequencies, lengths, codes, 15) { - let symbol = frequencies - .iter() - .position(|&frequency| frequency > 0) - .unwrap_or(0); - return self.write_single_entry_huffman_tree(symbol as u8); - } - - let mut code_length_lengths = [0u8; 16]; - let mut code_length_codes = [0u16; 16]; - let mut code_length_frequencies = [0u32; 16]; - for &length in lengths.iter() { - code_length_frequencies[length as usize] += 1; - } - let single_code_length_length = !self.build_huffman_tree( - &code_length_frequencies, - &mut code_length_lengths, - &mut code_length_codes, - 7, - ); - - const CODE_LENGTH_ORDER: [usize; 19] = [ - 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - ]; - - // Write the huffman tree - self.write_bits(0, 1)?; // normal huffman tree - self.write_bits(19 - 4, 4)?; // num_code_lengths - 4 - - for &i in CODE_LENGTH_ORDER.iter() { - if i > 15 || code_length_frequencies[i] == 0 { - self.write_bits(0, 3)?; - } else if single_code_length_length { - self.write_bits(1, 3)?; - } else { - self.write_bits(code_length_lengths[i] as u64, 3)?; - } - } - - match lengths.len() { - 256 => { - self.write_bits(1, 1)?; // max_symbol is stored - self.write_bits(3, 3)?; // max_symbol_nbits / 2 - 2 - self.write_bits(254, 8)?; // max_symbol - 2 - } - 280 => self.write_bits(0, 1)?, - _ => unreachable!(), - } - - // Write the huffman codes - if !single_code_length_length { - for &len in lengths.iter() { - self.write_bits( - code_length_codes[len as usize] as u64, - code_length_lengths[len as usize], - )?; - } - } - - Ok(()) - } - - fn length_to_symbol(len: u16) -> (u16, u8) { - let len = len - 1; - let highest_bit = 15 - len.leading_zeros() as u16; // TODO: use ilog2 once MSRV >= 1.67 - let second_highest_bit = (len >> (highest_bit - 1)) & 1; - let extra_bits = highest_bit - 1; - let symbol = 2 * highest_bit + second_highest_bit; - (symbol, extra_bits as u8) - } - - #[inline(always)] - fn count_run( - pixel: &[u8], - it: &mut std::iter::Peekable>, - frequencies1: &mut [u32; 280], - ) { - let mut run_length = 0; - while run_length < 4096 && it.peek() == Some(&pixel) { - run_length += 1; - it.next(); - } - if run_length > 0 { - if run_length <= 4 { - let symbol = 256 + run_length - 1; - frequencies1[symbol] += 1; - } else { - let (symbol, _extra_bits) = Self::length_to_symbol(run_length as u16); - frequencies1[256 + symbol as usize] += 1; - } - } - } - - #[inline(always)] - fn write_run( - &mut self, - pixel: &[u8], - it: &mut std::iter::Peekable>, - codes1: &[u16; 280], - lengths1: &[u8; 280], - ) -> io::Result<()> { - let mut run_length = 0; - while run_length < 4096 && it.peek() == Some(&pixel) { - run_length += 1; - it.next(); - } - if run_length > 0 { - if run_length <= 4 { - let symbol = 256 + run_length - 1; - self.write_bits(codes1[symbol] as u64, lengths1[symbol])?; - } else { - let (symbol, extra_bits) = Self::length_to_symbol(run_length as u16); - self.write_bits( - codes1[256 + symbol as usize] as u64, - lengths1[256 + symbol as usize], - )?; - self.write_bits( - (run_length as u64 - 1) & ((1 << extra_bits) - 1), - extra_bits, - )?; - } - } - Ok(()) - } - - fn encode_lossless( - mut self, - data: &[u8], - width: u32, - height: u32, - color: ColorType, - ) -> ImageResult<()> { - if width == 0 - || width > 16384 - || height == 0 - || height > 16384 - || !SampleLayout::row_major_packed(color.channel_count(), width, height) - .fits(data.len()) - { - return Err(ImageError::Parameter(ParameterError::from_kind( - ParameterErrorKind::DimensionMismatch, - ))); - } - - let (is_color, is_alpha) = match color { - ColorType::L8 => (false, false), - ColorType::La8 => (false, true), - ColorType::Rgb8 => (true, false), - ColorType::Rgba8 => (true, true), - _ => { - return Err(ImageError::Unsupported( - UnsupportedError::from_format_and_kind( - ImageFormat::WebP.into(), - UnsupportedErrorKind::Color(color.into()), - ), - )) - } - }; - - self.write_bits(0x2f, 8)?; // signature - self.write_bits(width as u64 - 1, 14)?; - self.write_bits(height as u64 - 1, 14)?; - - self.write_bits(is_alpha as u64, 1)?; // alpha used - self.write_bits(0x0, 3)?; // version - - // subtract green transform - self.write_bits(0b101, 3)?; - - // predictor transform - self.write_bits(0b111001, 6)?; - self.write_bits(0x0, 1)?; // no color cache - self.write_single_entry_huffman_tree(2)?; - for _ in 0..4 { - self.write_single_entry_huffman_tree(0)?; - } - - // transforms done - self.write_bits(0x0, 1)?; - - // color cache - self.write_bits(0x0, 1)?; - - // meta-huffman codes - self.write_bits(0x0, 1)?; - - // expand to RGBA - let mut pixels = match color { - ColorType::L8 => data.iter().flat_map(|&p| [p, p, p, 255]).collect(), - ColorType::La8 => data - .chunks_exact(2) - .flat_map(|p| [p[0], p[0], p[0], p[1]]) - .collect(), - ColorType::Rgb8 => data - .chunks_exact(3) - .flat_map(|p| [p[0], p[1], p[2], 255]) - .collect(), - ColorType::Rgba8 => data.to_vec(), - _ => unreachable!(), - }; - - // compute subtract green transform - for pixel in pixels.chunks_exact_mut(4) { - pixel[0] = pixel[0].wrapping_sub(pixel[1]); - pixel[2] = pixel[2].wrapping_sub(pixel[1]); - } - - // compute predictor transform - let row_bytes = width as usize * 4; - for y in (1..height as usize).rev() { - let (prev, current) = - pixels[(y - 1) * row_bytes..][..row_bytes * 2].split_at_mut(row_bytes); - for (c, p) in current.iter_mut().zip(prev) { - *c = c.wrapping_sub(*p); - } - } - for i in (4..row_bytes).rev() { - pixels[i] = pixels[i].wrapping_sub(pixels[i - 4]); - } - pixels[3] = pixels[3].wrapping_sub(255); - - // compute frequencies - let mut frequencies0 = [0u32; 256]; - let mut frequencies1 = [0u32; 280]; - let mut frequencies2 = [0u32; 256]; - let mut frequencies3 = [0u32; 256]; - let mut it = pixels.chunks_exact(4).peekable(); - match color { - ColorType::L8 => { - frequencies0[0] = 1; - frequencies2[0] = 1; - frequencies3[0] = 1; - while let Some(pixel) = it.next() { - frequencies1[pixel[1] as usize] += 1; - Self::count_run(pixel, &mut it, &mut frequencies1); - } - } - ColorType::La8 => { - frequencies0[0] = 1; - frequencies2[0] = 1; - while let Some(pixel) = it.next() { - frequencies1[pixel[1] as usize] += 1; - frequencies3[pixel[3] as usize] += 1; - Self::count_run(pixel, &mut it, &mut frequencies1); - } - } - ColorType::Rgb8 => { - frequencies3[0] = 1; - while let Some(pixel) = it.next() { - frequencies0[pixel[0] as usize] += 1; - frequencies1[pixel[1] as usize] += 1; - frequencies2[pixel[2] as usize] += 1; - Self::count_run(pixel, &mut it, &mut frequencies1); - } - } - ColorType::Rgba8 => { - while let Some(pixel) = it.next() { - frequencies0[pixel[0] as usize] += 1; - frequencies1[pixel[1] as usize] += 1; - frequencies2[pixel[2] as usize] += 1; - frequencies3[pixel[3] as usize] += 1; - Self::count_run(pixel, &mut it, &mut frequencies1); - } - } - _ => unreachable!(), - } - - // compute and write huffman codes - let mut lengths0 = [0u8; 256]; - let mut lengths1 = [0u8; 280]; - let mut lengths2 = [0u8; 256]; - let mut lengths3 = [0u8; 256]; - let mut codes0 = [0u16; 256]; - let mut codes1 = [0u16; 280]; - let mut codes2 = [0u16; 256]; - let mut codes3 = [0u16; 256]; - self.write_huffman_tree(&frequencies1, &mut lengths1, &mut codes1)?; - if is_color { - self.write_huffman_tree(&frequencies0, &mut lengths0, &mut codes0)?; - self.write_huffman_tree(&frequencies2, &mut lengths2, &mut codes2)?; - } else { - self.write_single_entry_huffman_tree(0)?; - self.write_single_entry_huffman_tree(0)?; - } - if is_alpha { - self.write_huffman_tree(&frequencies3, &mut lengths3, &mut codes3)?; - } else { - self.write_single_entry_huffman_tree(0)?; - } - self.write_single_entry_huffman_tree(1)?; - - // Write image data - let mut it = pixels.chunks_exact(4).peekable(); - match color { - ColorType::L8 => { - while let Some(pixel) = it.next() { - self.write_bits( - codes1[pixel[1] as usize] as u64, - lengths1[pixel[1] as usize], - )?; - self.write_run(pixel, &mut it, &codes1, &lengths1)?; - } - } - ColorType::La8 => { - while let Some(pixel) = it.next() { - let len1 = lengths1[pixel[1] as usize]; - let len3 = lengths3[pixel[3] as usize]; - - let code = codes1[pixel[1] as usize] as u64 - | (codes3[pixel[3] as usize] as u64) << len1; - - self.write_bits(code, len1 + len3)?; - self.write_run(pixel, &mut it, &codes1, &lengths1)?; - } - } - ColorType::Rgb8 => { - while let Some(pixel) = it.next() { - let len1 = lengths1[pixel[1] as usize]; - let len0 = lengths0[pixel[0] as usize]; - let len2 = lengths2[pixel[2] as usize]; - - let code = codes1[pixel[1] as usize] as u64 - | (codes0[pixel[0] as usize] as u64) << len1 - | (codes2[pixel[2] as usize] as u64) << (len1 + len0); - - self.write_bits(code, len1 + len0 + len2)?; - self.write_run(pixel, &mut it, &codes1, &lengths1)?; - } - } - ColorType::Rgba8 => { - while let Some(pixel) = it.next() { - let len1 = lengths1[pixel[1] as usize]; - let len0 = lengths0[pixel[0] as usize]; - let len2 = lengths2[pixel[2] as usize]; - let len3 = lengths3[pixel[3] as usize]; - - let code = codes1[pixel[1] as usize] as u64 - | (codes0[pixel[0] as usize] as u64) << len1 - | (codes2[pixel[2] as usize] as u64) << (len1 + len0) - | (codes3[pixel[3] as usize] as u64) << (len1 + len0 + len2); - - self.write_bits(code, len1 + len0 + len2 + len3)?; - self.write_run(pixel, &mut it, &codes1, &lengths1)?; - } - } - _ => unreachable!(), - } - - // flush writer - self.flush()?; - if self.chunk_buffer.len() % 2 == 1 { - self.chunk_buffer.push(0); - } - - // write container - self.writer.write_all(b"RIFF")?; - self.writer - .write_all(&(self.chunk_buffer.len() as u32 + 12).to_le_bytes())?; - self.writer.write_all(b"WEBP")?; - self.writer.write_all(b"VP8L")?; - self.writer - .write_all(&(self.chunk_buffer.len() as u32).to_le_bytes())?; - self.writer.write_all(&self.chunk_buffer)?; - - Ok(()) - } - - #[cfg(feature = "webp-encoder")] - fn encode_lossy( - 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(), - #[allow(deprecated)] - 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(crate::error::EncodingError::new( - ImageFormat::WebP.into(), - "encoding failed, output empty", - ))); - } - - self.writer.write_all(&encoded)?; - Ok(()) } /// Encode image data with the indicated color type. @@ -705,24 +30,39 @@ impl WebPEncoder { /// /// Panics if `width * height * color.bytes_per_pixel() != data.len()`. #[track_caller] - pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> { - let expected_buffer_len = - (width as u64 * height as u64).saturating_mul(color.bytes_per_pixel() as u64); + pub fn encode( + self, + buf: &[u8], + width: u32, + height: u32, + color_type: ExtendedColorType, + ) -> ImageResult<()> { + let expected_buffer_len = color_type.buffer_size(width, height); assert_eq!( expected_buffer_len, - data.len() as u64, + buf.len() as u64, "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image", - data.len(), + buf.len(), ); - if let WebPQuality(Quality::Lossless) = self.quality { - self.encode_lossless(data, width, height, color) - } else { - #[cfg(feature = "webp-encoder")] - return self.encode_lossy(data, width, height, color); - #[cfg(not(feature = "webp-encoder"))] - unreachable!() - } + let color_type = match color_type { + ExtendedColorType::L8 => image_webp::ColorType::L8, + ExtendedColorType::La8 => image_webp::ColorType::La8, + ExtendedColorType::Rgb8 => image_webp::ColorType::Rgb8, + ExtendedColorType::Rgba8 => image_webp::ColorType::Rgba8, + _ => { + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::WebP.into(), + UnsupportedErrorKind::Color(color_type), + ), + )) + } + }; + + self.inner + .encode(buf, width, height, color_type) + .map_err(ImageError::from_webp_encode) } } @@ -733,12 +73,21 @@ impl ImageEncoder for WebPEncoder { buf: &[u8], width: u32, height: u32, - color_type: ColorType, + color_type: ExtendedColorType, ) -> ImageResult<()> { self.encode(buf, width, height, color_type) } } +impl ImageError { + fn from_webp_encode(e: image_webp::EncodingError) -> Self { + match e { + image_webp::EncodingError::IoError(e) => ImageError::IoError(e), + _ => ImageError::Encoding(EncodingError::new(ImageFormat::WebP.into(), e)), + } + } +} + #[cfg(test)] mod tests { use crate::{ImageEncoder, RgbaImage}; @@ -753,7 +102,7 @@ mod tests { img.inner_pixels(), img.width(), img.height(), - crate::ColorType::Rgba8, + crate::ExtendedColorType::Rgba8, ) .unwrap(); @@ -764,81 +113,3 @@ mod tests { assert_eq!(img, img2); } } - -#[cfg(test)] -#[cfg(feature = "webp-encoder")] -mod native_tests { - use crate::codecs::webp::{WebPEncoder, WebPQuality}; - use crate::{ColorType, ImageEncoder}; - - #[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(), #[allow(deprecated)] WebPQuality::lossy(quality)] { - buffer.clear(); - #[allow(deprecated)] - let encoder = WebPEncoder::new_with_quality(&mut buffer, webp_quality); - if encoder - .write_image(&image.data, image.width, image.height, image.color).is_err() { - 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. - - if data.len() < width as usize * height as usize * 4 { - return true; - } - - let mut buffer = Vec::::new(); - for color in [ColorType::Rgb8, ColorType::Rgba8] { - for webp_quality in [WebPQuality::lossless(), #[allow(deprecated)] WebPQuality::lossy(quality)] { - buffer.clear(); - #[allow(deprecated)] - let encoder = WebPEncoder::new_with_quality(&mut buffer, webp_quality); - // Ignore errors. - let _ = encoder.write_image( - &data[..width as usize * height as usize * color.bytes_per_pixel() as usize], - width as u32, - height as u32, - color, - ); - } - } - true - } - } -} diff --git a/src/codecs/webp/extended.rs b/src/codecs/webp/extended.rs deleted file mode 100644 index eac5b9c052..0000000000 --- a/src/codecs/webp/extended.rs +++ /dev/null @@ -1,868 +0,0 @@ -use std::io::{self, Cursor, Error, Read, Seek}; -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>, - first_frame: AnimatedFrame, - 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 { first_frame, .. } => &first_frame.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, - first_frame, - } = &self.image.image - { - let anim_frame_data = frames.get(self.index)?; - let anim_frame; - let frame; - - if self.index == 0 { - // Use already decoded first frame - anim_frame = first_frame; - } else { - frame = read_anim_frame( - &mut Cursor::new(anim_frame_data), - self.image.info.canvas_width, - self.image.info.canvas_height, - ) - .ok()?; - anim_frame = &frame; - }; - - self.index += 1; - ExtendedImage::draw_subimage( - &mut self.canvas, - anim_frame, - anim_info.background_color, - ) - } 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 anim_first_frame: Option = None; - 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 mut frame_data = Vec::new(); - - // Store first frame decoded to avoid decoding it for certain function calls - if anim_first_frame.is_none() { - anim_first_frame = Some(read_anim_frame( - &mut cursor, - info.canvas_width, - info.canvas_height, - )?); - - cursor.rewind().unwrap(); - } - - cursor.read_to_end(&mut frame_data)?; - anim_frames.push(frame_data); - } - 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(anim_info), Some(first_frame)) = (anim_info, anim_first_frame) { - ExtendedImageData::Animation { - frames: anim_frames, - first_frame, - anim_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 { - anim_info, - first_frame, - .. - } => { - 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 { first_frame, .. } => &first_frame.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) -} diff --git a/src/codecs/webp/huffman.rs b/src/codecs/webp/huffman.rs deleted file mode 100644 index d3b1085cde..0000000000 --- a/src/codecs/webp/huffman.rs +++ /dev/null @@ -1,200 +0,0 @@ -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 = [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 = [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) - } -} diff --git a/src/codecs/webp/loop_filter.rs b/src/codecs/webp/loop_filter.rs deleted file mode 100644 index 312059f27f..0000000000 --- a/src/codecs/webp/loop_filter.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! 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); - } - } -} diff --git a/src/codecs/webp/lossless.rs b/src/codecs/webp/lossless.rs deleted file mode 100644 index 2ba5e87454..0000000000 --- a/src/codecs/webp/lossless.rs +++ /dev/null @@ -1,781 +0,0 @@ -//! Decoding of lossless WebP images -//! -//! [Lossless spec](https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification) -//! - -use std::{ - 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 [u32]) { - 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 - } -} diff --git a/src/codecs/webp/lossless_transform.rs b/src/codecs/webp/lossless_transform.rs deleted file mode 100644 index 4d1048961c..0000000000 --- a/src/codecs/webp/lossless_transform.rs +++ /dev/null @@ -1,461 +0,0 @@ -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) -} diff --git a/src/codecs/webp/mod.rs b/src/codecs/webp/mod.rs index 0407b30d4a..4be1d595fb 100644 --- a/src/codecs/webp/mod.rs +++ b/src/codecs/webp/mod.rs @@ -1,28 +1,7 @@ //! Decoding and Encoding of WebP Images -#[cfg(feature = "webp")] -pub use self::encoder::{WebPEncoder, WebPQuality}; - -#[cfg(feature = "webp")] +mod decoder; 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; +pub use self::encoder::WebPEncoder; diff --git a/src/codecs/webp/transform.rs b/src/codecs/webp/transform.rs deleted file mode 100644 index 6e61020c79..0000000000 --- a/src/codecs/webp/transform.rs +++ /dev/null @@ -1,77 +0,0 @@ -static 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: &[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; - } -} diff --git a/src/codecs/webp/vp8.rs b/src/codecs/webp/vp8.rs deleted file mode 100644 index a2a4e58b4d..0000000000 --- a/src/codecs/webp/vp8.rs +++ /dev/null @@ -1,2916 +0,0 @@ -//! 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::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, Default)] -enum LumaMode { - /// Predict DC using row above and column to the left. - #[default] - 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, Default)] -enum ChromaMode { - /// Predict DC using row above and column to the left. - #[default] - 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, Default)] -enum IntraMode { - #[default] - 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.first().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 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 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, - }) - } -} - -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); - 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(|| { - 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(|| { - 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(|| { - 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(|| { - 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); - } -} diff --git a/src/color.rs b/src/color.rs index 47f7fee3e4..2002a27f14 100644 --- a/src/color.rs +++ b/src/color.rs @@ -187,6 +187,46 @@ impl ExtendedColorType { | ExtendedColorType::Cmyk8 => 4, } } + + /// Returns the number of bits per pixel for this color type. + pub fn bits_per_pixel(&self) -> u16 { + match *self { + ExtendedColorType::A8 => 8, + ExtendedColorType::L1 => 1, + ExtendedColorType::La1 => 2, + ExtendedColorType::Rgb1 => 3, + ExtendedColorType::Rgba1 => 4, + ExtendedColorType::L2 => 2, + ExtendedColorType::La2 => 4, + ExtendedColorType::Rgb2 => 6, + ExtendedColorType::Rgba2 => 8, + ExtendedColorType::L4 => 4, + ExtendedColorType::La4 => 8, + ExtendedColorType::Rgb4 => 12, + ExtendedColorType::Rgba4 => 16, + ExtendedColorType::L8 => 8, + ExtendedColorType::La8 => 16, + ExtendedColorType::Rgb8 => 24, + ExtendedColorType::Rgba8 => 32, + ExtendedColorType::L16 => 16, + ExtendedColorType::La16 => 32, + ExtendedColorType::Rgb16 => 48, + ExtendedColorType::Rgba16 => 64, + ExtendedColorType::Rgb32F => 96, + ExtendedColorType::Rgba32F => 128, + ExtendedColorType::Bgr8 => 24, + ExtendedColorType::Bgra8 => 32, + ExtendedColorType::Cmyk8 => 32, + ExtendedColorType::Unknown(bpp) => bpp as u16, + } + } + + /// Returns the number of bytes required to hold a width x height image of this color type. + pub(crate) fn buffer_size(self, width: u32, height: u32) -> u64 { + let bpp = self.bits_per_pixel() as u64; + let row_pitch = (width as u64 * bpp + 7) / 8; + row_pitch.saturating_mul(height as u64) + } } impl From for ExtendedColorType { fn from(c: ColorType) -> Self { @@ -216,7 +256,7 @@ $( // START Structure definitions $(#[$doc])* #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)] -#[repr(C)] +#[repr(transparent)] #[allow(missing_docs)] pub struct $ident (pub [T; $channels]); @@ -444,6 +484,7 @@ impl FromPrimitive for u16 { /// Provides color conversions for the different pixel types. pub trait FromColor { /// Changes `self` to represent `Other` in the color space of `Self` + #[allow(clippy::wrong_self_convention)] fn from_color(&mut self, _: &Other); } @@ -452,6 +493,7 @@ pub trait FromColor { // rather than assuming sRGB. pub(crate) trait IntoColor { /// Constructs a pixel of the target type and converts this pixel into it. + #[allow(clippy::wrong_self_convention)] fn into_color(&self) -> Other; } @@ -459,6 +501,7 @@ impl IntoColor for S where O: Pixel + FromColor, { + #[allow(clippy::wrong_self_convention)] 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. diff --git a/src/dynimage.rs b/src/dynimage.rs index 6e15171471..5db281c139 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -1,5 +1,4 @@ -use std::io; -use std::io::{Seek, Write}; +use std::io::{self, Seek, Write}; use std::path::Path; use std::u32; @@ -7,8 +6,6 @@ use std::u32; 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, @@ -17,14 +14,12 @@ use crate::buffer_::{ 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::image::{GenericImage, GenericImageView, ImageDecoder, ImageEncoder, ImageFormat}; use crate::io::free_functions; use crate::math::resize_dimensions; use crate::traits::Pixel; use crate::{image, Luma, LumaA}; +use crate::{imageops, ExtendedColorType}; use crate::{Rgb32FImage, Rgba32FImage}; /// A Dynamic Image @@ -189,7 +184,7 @@ impl DynamicImage { } /// Decodes an encoded image into a dynamic image. - pub fn from_decoder<'a>(decoder: impl ImageDecoder<'a>) -> ImageResult { + pub fn from_decoder(decoder: impl ImageDecoder) -> ImageResult { decoder_to_image(decoder) } @@ -607,16 +602,6 @@ impl DynamicImage { }) } - /// 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 { @@ -831,36 +816,24 @@ impl DynamicImage { /// /// 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<()> { + pub fn write_to(&self, w: &mut W, format: ImageFormat) -> ImageResult<()> { let bytes = self.inner_bytes(); let (width, height) = self.dimensions(); - let color = self.color(); - let format = format.into(); + let color: ExtendedColorType = self.color().into(); // TODO do not repeat this match statement across the crate #[allow(deprecated)] match format { #[cfg(feature = "png")] - image::ImageOutputFormat::Png => { + ImageFormat::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 => { + ImageFormat::Gif => { let mut g = gif::GifEncoder::new(w); g.encode_frame(crate::animation::Frame::new(self.to_rgba8()))?; Ok(()) @@ -978,10 +951,6 @@ impl GenericImageView for DynamicImage { 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()) } @@ -1040,7 +1009,7 @@ impl Default for DynamicImage { } /// Decodes an image and stores it into a dynamic image -fn decoder_to_image<'a, I: ImageDecoder<'a>>(decoder: I) -> ImageResult { +fn decoder_to_image(decoder: I) -> ImageResult { let (w, h) = decoder.dimensions(); let color_type = decoder.color_type(); @@ -1115,8 +1084,7 @@ 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()) + crate::io::Reader::open(path)?.decode() } /// Read a tuple containing the (width, height) of the image located at the specified path. @@ -1130,8 +1098,7 @@ 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()) + crate::io::Reader::open(path)?.into_dimensions() } /// Saves the supplied buffer to a file at the path specified. @@ -1141,18 +1108,15 @@ where /// /// 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, +pub fn save_buffer( + path: impl AsRef, buf: &[u8], width: u32, height: u32, - color: color::ColorType, -) -> ImageResult<()> -where - P: AsRef, -{ + color: impl Into, +) -> ImageResult<()> { // thin wrapper function to strip generics before calling save_buffer_impl - free_functions::save_buffer_impl(path.as_ref(), buf, width, height, color) + free_functions::save_buffer_impl(path.as_ref(), buf, width, height, color.into()) } /// Saves the supplied buffer to a file at the path specified @@ -1163,19 +1127,23 @@ where /// 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, +pub fn save_buffer_with_format( + path: impl AsRef, buf: &[u8], width: u32, height: u32, - color: color::ColorType, + color: impl Into, format: ImageFormat, -) -> ImageResult<()> -where - P: AsRef, -{ +) -> ImageResult<()> { // thin wrapper function to strip generics - free_functions::save_buffer_with_format_impl(path.as_ref(), buf, width, height, color, format) + free_functions::save_buffer_with_format_impl( + path.as_ref(), + buf, + width, + height, + color.into(), + format, + ) } /// Writes the supplied buffer to a writer in the specified format. @@ -1190,20 +1158,16 @@ where /// /// 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( +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, -{ + color: impl Into, + format: ImageFormat, +) -> ImageResult<()> { // thin wrapper function to strip generics - free_functions::write_buffer_impl(buffered_writer, buf, width, height, color, format.into()) + free_functions::write_buffer_impl(buffered_writer, buf, width, height, color.into(), format) } /// Create a new image from a byte slice @@ -1267,7 +1231,7 @@ mod 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); + assert_eq!(image.color(), ColorType::Rgba16); } fn test_grayscale(mut img: super::DynamicImage, alpha_discarded: bool) { diff --git a/src/flat.rs b/src/flat.rs index d21a4f5eb0..5ef9ba56e3 100644 --- a/src/flat.rs +++ b/src/flat.rs @@ -1390,16 +1390,6 @@ where (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()) @@ -1433,16 +1423,6 @@ where (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()) diff --git a/src/image.rs b/src/image.rs index 3fa81092d5..49077c26bf 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,7 +1,6 @@ #![allow(clippy::too_many_arguments)] use std::ffi::OsStr; -use std::io; -use std::io::Read; +use std::io::{self, Write}; use std::ops::{Deref, DerefMut}; use std::path::Path; use std::usize; @@ -17,9 +16,6 @@ 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)] @@ -226,7 +222,7 @@ impl ImageFormat { // 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 + // farbfeld's MIME type taken from https://www.wikidata.org/wiki/Q28206109 ImageFormat::Farbfeld => "application/octet-stream", } } @@ -375,105 +371,6 @@ impl ImageFormat { } } -/// 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")] - /// 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")] - 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)] @@ -553,24 +450,27 @@ impl ImageReadBuffer { /// 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>( +pub(crate) fn load_rect( x: u32, y: u32, width: u32, height: u32, buf: &mut [u8], - progress_callback: F, + row_pitch: usize, decoder: &mut D, + scanline_bytes: usize, mut seek_scanline: F1, mut read_scanline: F2, ) -> ImageResult<()> where - D: ImageDecoder<'a>, - F: Fn(Progress), + D: ImageDecoder, F1: FnMut(&mut D, u64) -> io::Result<()>, F2: FnMut(&mut D, &mut [u8]) -> Result<(), E>, ImageError: From, { + let scanline_bytes = u64::try_from(scanline_bytes).unwrap(); + let row_pitch = u64::try_from(row_pitch).unwrap(); + let (x, y, width, height) = ( u64::from(x), u64::from(y), @@ -580,8 +480,6 @@ where 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); - #[allow(deprecated)] - 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()) { @@ -592,7 +490,6 @@ where ); } - let mut bytes_read = 0u64; let mut current_scanline = 0; let mut tmp = Vec::new(); let mut tmp_scanline = None; @@ -600,70 +497,59 @@ where { // 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 mut read_image_range = + |mut start: u64, end: u64, mut output: &mut [u8]| -> 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; + output + .write_all(&tmp[offset as usize..][..len as usize]) + .unwrap(); + start += len; + + 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; } - current_scanline += 1; - position += scanline_bytes; - progress_callback(Progress { - current: bytes_read, - total: total_bytes, - }); - } - Ok(()) - }; + let mut position = current_scanline * scanline_bytes; + while position < end { + if position >= start && end - position >= scanline_bytes { + read_scanline(decoder, &mut output[..(scanline_bytes as usize)])?; + output = &mut output[scanline_bytes as usize..]; + } 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); + + output + .write_all(&tmp[offset as usize..][..len as usize]) + .unwrap(); + } + + current_scanline += 1; + position += scanline_bytes; + } + Ok(()) + }; if x + width > u64::from(dimensions.0) || y + height > u64::from(dimensions.1) @@ -680,19 +566,15 @@ where ))); } - progress_callback(Progress { - current: 0, - total: total_bytes, - }); - if x == 0 && width == u64::from(dimensions.0) { + if x == 0 && width == u64::from(dimensions.0) && row_pitch == row_bytes { 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)?; + read_image_range(start, end, buf)?; } else { - for row in y..(y + height) { + for (output_slice, row) in buf.chunks_mut(row_pitch as usize).zip(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)?; + read_image_range(start, end, output_slice)?; } } } @@ -705,7 +587,7 @@ where /// 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> +pub(crate) fn decoder_to_vec(decoder: impl ImageDecoder) -> ImageResult> where T: crate::traits::Primitive + bytemuck::Pod, { @@ -721,46 +603,8 @@ where 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; - +pub trait ImageDecoder { /// Returns a tuple containing the width and height of the image fn dimensions(&self) -> (u32, u32); @@ -772,20 +616,13 @@ pub trait ImageDecoder<'a>: Sized { self.color_type().into() } - /// Returns the ICC color profile embedded in the image + /// Returns the ICC color profile embedded in the image, or `Ok(None)` if the image does not have one. /// - /// 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 + /// For formats that don't support embedded profiles this function should always return `Ok(None)`. + fn icc_profile(&mut self) -> ImageResult>> { + Ok(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. - #[deprecated = "Planned for removal. See https://github.com/image-rs/image/issues/1989"] - 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 @@ -799,13 +636,6 @@ pub trait ImageDecoder<'a>: Sized { 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()`. - #[deprecated = "Planned for removal. See https://github.com/image-rs/image/issues/1989"] - 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. @@ -827,49 +657,11 @@ pub trait ImageDecoder<'a>: Sized { /// buf /// } /// ``` - fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { - #[allow(deprecated)] - self.read_image_with_progress(buf, |_| {}) - } - - /// Same as `read_image` but periodically calls the provided callback to give updates on loading - /// progress. - #[deprecated = "Use read_image instead. See https://github.com/image-rs/image/issues/1989"] - 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; - #[allow(deprecated)] - let scanline_bytes = self.scanline_bytes() as usize; - let target_read_size = if scanline_bytes < 4096 { - (4096 / scanline_bytes) * scanline_bytes - } else { - scanline_bytes - }; - - #[allow(deprecated)] - 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(()) - } + fn read_image(self, buf: &mut [u8]) -> ImageResult<()> + where + Self: Sized; - /// Set decoding limits for this decoder. See [`Limits`] for the different kinds of + /// Set the decoder to have the specified limits. 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 @@ -882,50 +674,71 @@ pub trait ImageDecoder<'a>: Sized { /// [`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(()) } + + /// Use `read_image` instead; this method is an implementation detail needed so the trait can + /// be object safe. + /// + /// Note to implementors: This method should be implemented by calling `read_image` on + /// the boxed decoder... + /// ```no_build + /// fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + /// (*self).read_image(buf) + /// } + /// ``` + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()>; } -/// 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<()> { - #[allow(deprecated)] - self.read_rect_with_progress(x, y, width, height, buf, |_| {}) +impl ImageDecoder for Box { + fn dimensions(&self) -> (u32, u32) { + (**self).dimensions() + } + fn color_type(&self) -> ColorType { + (**self).color_type() + } + fn original_color_type(&self) -> ExtendedColorType { + (**self).original_color_type() + } + fn icc_profile(&mut self) -> ImageResult>> { + (**self).icc_profile() + } + fn total_bytes(&self) -> u64 { + (**self).total_bytes() + } + fn read_image(self, buf: &mut [u8]) -> ImageResult<()> + where + Self: Sized, + { + T::read_image_boxed(self, buf) + } + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + T::read_image_boxed(*self, buf) + } + fn set_limits(&mut self, limits: crate::io::Limits) -> ImageResult<()> { + (**self).set_limits(limits) } +} - /// 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. +/// Specialized image decoding not be supported by all formats +pub trait ImageDecoderRect: ImageDecoder { + /// Decode a rectangular section of the image. /// - /// 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. - #[deprecated = "Use read_image instead. See https://github.com/image-rs/image/issues/1989"] - fn read_rect_with_progress( + /// This function takes a slice of bytes and writes the pixel data of the image into it. + /// The rectangle is specified by the x and y coordinates of the top left corner, the width + /// and height of the rectangle, and the row pitch of the buffer. The row pitch is the number + /// of bytes between the start of one row and the start of the next row. The row pitch must be + /// at least as large as the width of the rectangle in bytes. + fn read_rect( &mut self, x: u32, y: u32, width: u32, height: u32, buf: &mut [u8], - progress_callback: F, + row_pitch: usize, ) -> ImageResult<()>; } @@ -956,7 +769,7 @@ pub trait ImageEncoder { buf: &[u8], width: u32, height: u32, - color_type: ColorType, + color_type: ExtendedColorType, ) -> ImageResult<()>; } @@ -1025,15 +838,10 @@ pub trait GenericImageView { h } - /// The bounding rectangle of this image. - #[deprecated = "This method has inconsistent behavior between implementations (#1829). Use `dimensions` instead"] - 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 { - #[allow(deprecated)] - let (ix, iy, iw, ih) = self.bounds(); - x >= ix && x < ix + iw && y >= iy && y < iy + ih + let (width, height) = self.dimensions(); + x < width && y < height } /// Returns the pixel located at (x, y). Indexed from top left. @@ -1424,10 +1232,6 @@ where (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) } @@ -1601,19 +1405,18 @@ mod tests { scanline_number: u64, scanline_bytes: u64, } - impl<'a> ImageDecoder<'a> for MockDecoder { - type Reader = Box; + impl ImageDecoder for MockDecoder { fn dimensions(&self) -> (u32, u32) { (5, 5) } fn color_type(&self) -> ColorType { ColorType::L8 } - fn into_reader(self) -> ImageResult { + fn read_image(self, _buf: &mut [u8]) -> ImageResult<()> { unimplemented!() } - fn scanline_bytes(&self) -> u64 { - self.scanline_bytes + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) } } @@ -1647,11 +1450,12 @@ mod tests { 5, 5, &mut output, - |_| {}, + 5, &mut MockDecoder { scanline_number: 0, scanline_bytes, }, + scanline_bytes as usize, seek_scanline, read_scanline, ) @@ -1666,11 +1470,12 @@ mod tests { 1, 1, &mut output, - |_| {}, + 1, &mut MockDecoder { scanline_number: 0, scanline_bytes, }, + scanline_bytes as usize, seek_scanline, read_scanline, ) @@ -1684,11 +1489,12 @@ mod tests { 2, 2, &mut output, - |_| {}, + 2, &mut MockDecoder { scanline_number: 0, scanline_bytes, }, + scanline_bytes as usize, seek_scanline, read_scanline, ) @@ -1702,11 +1508,12 @@ mod tests { 2, 4, &mut output, - |_| {}, + 2, &mut MockDecoder { scanline_number: 0, scanline_bytes, }, + scanline_bytes as usize, seek_scanline, read_scanline, ) @@ -1723,19 +1530,18 @@ mod tests { ]; struct MockDecoder; - impl<'a> ImageDecoder<'a> for MockDecoder { - type Reader = Box; + impl ImageDecoder for MockDecoder { fn dimensions(&self) -> (u32, u32) { (5, 5) } fn color_type(&self) -> ColorType { ColorType::L8 } - fn into_reader(self) -> ImageResult { + fn read_image(self, _buf: &mut [u8]) -> ImageResult<()> { unimplemented!() } - fn scanline_bytes(&self) -> u64 { - 25 + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) } } @@ -1760,8 +1566,9 @@ mod tests { 2, 4, &mut output, - |_| {}, + 2, &mut MockDecoder, + DATA.len(), seek_scanline, read_scanline, ) @@ -1965,16 +1772,18 @@ mod tests { #[test] fn total_bytes_overflow() { struct D; - impl<'a> ImageDecoder<'a> for D { - type Reader = std::io::Cursor>; + impl ImageDecoder for D { fn color_type(&self) -> ColorType { ColorType::Rgb8 } fn dimensions(&self) -> (u32, u32) { (0xffffffff, 0xffffffff) } - fn into_reader(self) -> ImageResult { - unreachable!() + fn read_image(self, _buf: &mut [u8]) -> ImageResult<()> { + unimplemented!() + } + fn read_image_boxed(self: Box, buf: &mut [u8]) -> ImageResult<()> { + (*self).read_image(buf) } } assert_eq!(D.total_bytes(), u64::max_value()); diff --git a/src/imageops/colorops.rs b/src/imageops/colorops.rs index 67d969d6de..86d53ecaf5 100644 --- a/src/imageops/colorops.rs +++ b/src/imageops/colorops.rs @@ -3,7 +3,7 @@ use num_traits::NumCast; use std::f64::consts::PI; -use crate::color::{FromColor, IntoColor, Luma, LumaA, Rgba}; +use crate::color::{FromColor, IntoColor, Luma, LumaA}; use crate::image::{GenericImage, GenericImageView}; use crate::traits::{Pixel, Primitive}; use crate::utils::clamp; @@ -435,11 +435,12 @@ impl ColorMap for BiLevel { } } +#[cfg(feature = "color_quant")] impl ColorMap for color_quant::NeuQuant { - type Color = Rgba; + type Color = crate::color::Rgba; #[inline(always)] - fn index_of(&self, color: &Rgba) -> usize { + fn index_of(&self, color: &Self::Color) -> usize { self.index_of(color.channels()) } @@ -454,7 +455,7 @@ impl ColorMap for color_quant::NeuQuant { } #[inline(always)] - fn map_color(&self, color: &mut Rgba) { + fn map_color(&self, color: &mut Self::Color) { self.map_pixel(color.channels_mut()) } } diff --git a/src/io/free_functions.rs b/src/io/free_functions.rs index 54201c9ccc..ef4bac0d68 100644 --- a/src/io/free_functions.rs +++ b/src/io/free_functions.rs @@ -1,27 +1,16 @@ use std::fs::File; -use std::io::{BufRead, BufReader, BufWriter, Seek}; +use std::io::{BufRead, BufWriter, Seek}; use std::path::Path; use std::u32; -use crate::codecs::*; +use crate::{codecs::*, ExtendedColorType}; use crate::dynimage::DynamicImage; use crate::error::{ImageError, ImageFormatHint, ImageResult}; -use crate::image; +use crate::error::{UnsupportedError, UnsupportedErrorKind}; 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. /// @@ -31,110 +20,10 @@ pub(crate) fn open_impl(path: &Path) -> ImageResult { /// 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) + let mut reader = crate::io::Reader::new(r); + reader.set_format(format); + reader.decode() } #[allow(unused_variables)] @@ -144,7 +33,7 @@ pub(crate) fn save_buffer_impl( buf: &[u8], width: u32, height: u32, - color: color::ColorType, + color: ExtendedColorType, ) -> ImageResult<()> { let format = ImageFormat::from_path(path)?; save_buffer_with_format_impl(path, buf, width, height, color, format) @@ -157,35 +46,10 @@ pub(crate) fn save_buffer_with_format_impl( buf: &[u8], width: u32, height: u32, - color: color::ColorType, + color: ExtendedColorType, 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) } @@ -196,68 +60,64 @@ pub(crate) fn write_buffer_impl( buf: &[u8], width: u32, height: u32, - color: color::ColorType, - format: ImageOutputFormat, + color: ExtendedColorType, + format: ImageFormat, ) -> ImageResult<()> { match format { #[cfg(feature = "png")] - ImageOutputFormat::Png => { + ImageFormat::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) + ImageFormat::Jpeg => { + jpeg::JpegEncoder::new(buffered_write).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) + ImageFormat::Pnm => { + pnm::PnmEncoder::new(buffered_write).write_image(buf, width, height, color) } + #[cfg(feature = "gif")] + ImageFormat::Gif => gif::GifEncoder::new(buffered_write).encode(buf, width, height, color), #[cfg(feature = "ico")] - ImageOutputFormat::Ico => { + ImageFormat::Ico => { ico::IcoEncoder::new(buffered_write).write_image(buf, width, height, color) } #[cfg(feature = "bmp")] - ImageOutputFormat::Bmp => { + ImageFormat::Bmp => { bmp::BmpEncoder::new(buffered_write).write_image(buf, width, height, color) } - #[cfg(feature = "farbfeld")] - ImageOutputFormat::Farbfeld => { + #[cfg(feature = "ff")] + ImageFormat::Farbfeld => { farbfeld::FarbfeldEncoder::new(buffered_write).write_image(buf, width, height, color) } #[cfg(feature = "tga")] - ImageOutputFormat::Tga => { + ImageFormat::Tga => { tga::TgaEncoder::new(buffered_write).write_image(buf, width, height, color) } #[cfg(feature = "exr")] - ImageOutputFormat::OpenExr => { + ImageFormat::OpenExr => { openexr::OpenExrEncoder::new(buffered_write).write_image(buf, width, height, color) } #[cfg(feature = "tiff")] - ImageOutputFormat::Tiff => { + ImageFormat::Tiff => { tiff::TiffEncoder::new(buffered_write).write_image(buf, width, height, color) } - #[cfg(feature = "avif-encoder")] - ImageOutputFormat::Avif => { + #[cfg(feature = "avif")] + ImageFormat::Avif => { avif::AvifEncoder::new(buffered_write).write_image(buf, width, height, color) } #[cfg(feature = "qoi")] - ImageOutputFormat::Qoi => { + ImageFormat::Qoi => { qoi::QoiEncoder::new(buffered_write).write_image(buf, width, height, color) } #[cfg(feature = "webp")] - ImageOutputFormat::WebP => { + ImageFormat::WebP => { webp::WebPEncoder::new_lossless(buffered_write).write_image(buf, width, height, color) } - - image::ImageOutputFormat::Unsupported(msg) => Err(ImageError::Unsupported( + _ => Err(ImageError::Unsupported( UnsupportedError::from_format_and_kind( ImageFormatHint::Unknown, - UnsupportedErrorKind::Format(ImageFormatHint::Name(msg)), + UnsupportedErrorKind::Format(ImageFormatHint::Name(format!("{:?}", format))), ), )), } diff --git a/src/io/mod.rs b/src/io/mod.rs index 80c4fa3884..7f27dd26da 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -8,21 +8,10 @@ mod reader; pub use self::reader::Reader; /// Set of supported strict limits for a decoder. -#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Debug, Default, 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: (), - } - } -} +#[non_exhaustive] +pub struct LimitSupport {} /// Resource limits for decoding. /// @@ -47,7 +36,7 @@ impl Default for LimitSupport { /// [`ImageDecoder::set_limits`]: ../trait.ImageDecoder.html#method.set_limits #[derive(Clone, Debug, Eq, PartialEq, Hash)] #[allow(missing_copy_implementations)] -#[allow(clippy::manual_non_exhaustive)] +#[non_exhaustive] pub struct Limits { /// The maximum allowed image width. This limit is strict. The default is no limit. pub max_image_width: Option, @@ -55,9 +44,9 @@ pub struct Limits { 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. + /// The bytes required to store the output image count towards this value. The default is + /// 512MiB. pub max_alloc: Option, - _non_exhaustive: (), } impl Default for Limits { @@ -66,7 +55,6 @@ impl Default for Limits { max_image_width: None, max_image_height: None, max_alloc: Some(512 * 1024 * 1024), - _non_exhaustive: (), } } } @@ -78,7 +66,6 @@ impl Limits { max_image_width: None, max_image_height: None, max_alloc: None, - _non_exhaustive: (), } } diff --git a/src/io/reader.rs b/src/io/reader.rs index 660780fb8d..aeb13811f3 100644 --- a/src/io/reader.rs +++ b/src/io/reader.rs @@ -5,7 +5,7 @@ use std::path::Path; use crate::dynimage::DynamicImage; use crate::error::{ImageFormatHint, UnsupportedError, UnsupportedErrorKind}; use crate::image::ImageFormat; -use crate::{ImageError, ImageResult}; +use crate::{ImageDecoder, ImageError, ImageResult}; use super::free_functions; @@ -58,7 +58,7 @@ use super::free_functions; /// /// [`set_format`]: #method.set_format /// [`ImageDecoder`]: ../trait.ImageDecoder.html -pub struct Reader { +pub struct Reader { /// The reader. Should be buffered. inner: R, /// The format, if one has been set or deduced. @@ -67,7 +67,7 @@ pub struct Reader { limits: super::Limits, } -impl Reader { +impl<'a, R: 'a + BufRead + Seek> Reader { /// Create a new image reader without a preset format. /// /// Assumes the reader is already buffered. For optimal performance, @@ -130,34 +130,69 @@ impl Reader { pub fn into_inner(self) -> R { self.inner } -} -impl Reader> { - /// Open a file to read, format will be guessed from path. + /// Makes a decoder. /// - /// 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()) - } + /// For all formats except PNG, the limits are ignored and can be set with + /// ImageDecoder::set_limits after calling this function. PNG is handled specially because that + /// decoder has a different API which does not allow setting limits after construction. + fn make_decoder( + format: ImageFormat, + reader: R, + limits_for_png: super::Limits, + ) -> ImageResult> { + #[allow(unused)] + use crate::codecs::*; - 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(), + #[allow(unreachable_patterns)] + // Default is unreachable if all features are supported. + Ok(match format { + #[cfg(feature = "avif-native")] + ImageFormat::Avif => Box::new(avif::AvifDecoder::new(reader)?), + #[cfg(feature = "png")] + ImageFormat::Png => Box::new(png::PngDecoder::with_limits(reader, limits_for_png)?), + #[cfg(feature = "gif")] + ImageFormat::Gif => Box::new(gif::GifDecoder::new(reader)?), + #[cfg(feature = "jpeg")] + ImageFormat::Jpeg => Box::new(jpeg::JpegDecoder::new(reader)?), + #[cfg(feature = "webp")] + ImageFormat::WebP => Box::new(webp::WebPDecoder::new(reader)?), + #[cfg(feature = "tiff")] + ImageFormat::Tiff => Box::new(tiff::TiffDecoder::new(reader)?), + #[cfg(feature = "tga")] + ImageFormat::Tga => Box::new(tga::TgaDecoder::new(reader)?), + #[cfg(feature = "dds")] + ImageFormat::Dds => Box::new(dds::DdsDecoder::new(reader)?), + #[cfg(feature = "bmp")] + ImageFormat::Bmp => Box::new(bmp::BmpDecoder::new(reader)?), + #[cfg(feature = "ico")] + ImageFormat::Ico => Box::new(ico::IcoDecoder::new(reader)?), + #[cfg(feature = "hdr")] + ImageFormat::Hdr => Box::new(hdr::HdrDecoder::new(reader)?), + #[cfg(feature = "exr")] + ImageFormat::OpenExr => Box::new(openexr::OpenExrDecoder::new(reader)?), + #[cfg(feature = "pnm")] + ImageFormat::Pnm => Box::new(pnm::PnmDecoder::new(reader)?), + #[cfg(feature = "ff")] + ImageFormat::Farbfeld => Box::new(farbfeld::FarbfeldDecoder::new(reader)?), + #[cfg(feature = "qoi")] + ImageFormat::Qoi => Box::new(qoi::QoiDecoder::new(reader)?), + format => { + return Err(ImageError::Unsupported( + ImageFormatHint::Exact(format).into(), + )) + } }) } -} -impl Reader { + /// Convert the reader into a decoder. + pub fn into_decoder(mut self) -> ImageResult { + let mut decoder = + Self::make_decoder(self.require_format()?, self.inner, self.limits.clone())?; + decoder.set_limits(self.limits)?; + Ok(decoder) + } + /// 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 @@ -213,9 +248,8 @@ impl Reader { /// 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) + pub fn into_dimensions(self) -> ImageResult<(u32, u32)> { + self.into_decoder().map(|d| d.dimensions()) } /// Read the image (replaces `load`). @@ -225,7 +259,16 @@ impl Reader { /// 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) + + let mut limits = self.limits; + let mut decoder = Self::make_decoder(format, self.inner, limits.clone())?; + + // 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) } fn require_format(&mut self) -> ImageResult { @@ -237,3 +280,28 @@ impl Reader { }) } } + +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(), + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index d25d89741a..45e09572ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ //! //! ```rust,no_run //! # use std::io::{Write, Cursor}; -//! # use image::{DynamicImage, ImageOutputFormat}; +//! # use image::{DynamicImage, ImageFormat}; //! # #[cfg(feature = "png")] //! # fn main() -> Result<(), image::ImageError> { //! # let img: DynamicImage = unimplemented!(); @@ -39,7 +39,7 @@ //! img.save("empty.jpg")?; //! //! let mut bytes: Vec = Vec::new(); -//! img2.write_to(&mut Cursor::new(&mut bytes), image::ImageOutputFormat::Png)?; +//! img2.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)?; //! # Ok(()) //! # } //! # #[cfg(not(feature = "png"))] fn main() {} @@ -91,14 +91,14 @@ //! While [`ImageDecoder`] and [`ImageDecoderRect`] give access to more advanced decoding options: //! //! ```rust,no_run -//! # use std::io::Read; +//! # use std::io::{BufReader, Cursor}; //! # 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 reader: BufReader> = unimplemented!(); //! let decoder = PngDecoder::new(&mut reader)?; //! let icc = decoder.icc_profile(); //! let img = DynamicImage::from_decoder(decoder)?; @@ -117,8 +117,6 @@ #![deny(deprecated)] #![deny(missing_copy_implementations)] #![cfg_attr(all(test, feature = "benchmarks"), feature(test))] -// it's a backwards compatibility break -#![allow(clippy::wrong_self_convention, clippy::enum_variant_names)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #[cfg(all(test, feature = "benchmarks"))] @@ -142,10 +140,8 @@ pub use crate::image::{ ImageDecoderRect, ImageEncoder, ImageFormat, - ImageOutputFormat, // Iterators Pixels, - Progress, SubImage, }; @@ -211,21 +207,23 @@ pub mod flat; /// /// | Format | Decoding | Encoding | /// | -------- | ----------------------------------------- | --------------------------------------- | -/// | AVIF | Only 8-bit | Lossy | -/// | BMP | Yes | Rgb8, Rgba8, Gray8, GrayA8 | -/// | DDS | DXT1, DXT3, DXT5 | No | +/// | AVIF | Yes (8-bit only) \* | Yes (lossy only) | +/// | BMP | Yes | Yes | +/// | DDS | Yes | --- | /// | Farbfeld | Yes | Yes | /// | GIF | Yes | Yes | /// | HDR | 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 | +/// | JPEG | Yes | Yes | +/// | EXR | Yes | Yes | +/// | PNG | Yes | Yes | +/// | PNM | Yes | 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 | +/// | TGA | Yes | Yes | +/// | TIFF | Yes | Yes | +/// | WebP | Yes | Yes (lossless only) | +/// +/// - \* Requires the `avif-native` feature, uses the libdav1d C library. /// /// ## A note on format specific features /// @@ -249,16 +247,13 @@ pub mod flat; /// /// 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"))] + #[cfg(any(feature = "avif", feature = "avif-native"))] 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")] + #[cfg(feature = "ff")] pub mod farbfeld; #[cfg(feature = "gif")] pub mod gif; @@ -282,6 +277,9 @@ pub mod codecs { pub mod tiff; #[cfg(feature = "webp")] pub mod webp; + + #[cfg(feature = "dds")] + mod dxt; } mod animation; diff --git a/src/math/utils.rs b/src/math/utils.rs index 66ff776c39..d734815023 100644 --- a/src/math/utils.rs +++ b/src/math/utils.rs @@ -48,7 +48,7 @@ mod test { // 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 result = super::resize_dimensions(old_w, 400, new_w, std::u32::MAX, false); let exact = (400_f64 * new_w as f64 / old_w as f64).round() as u32; result.0 == new_w && result.1 == exact.max(1) } @@ -61,7 +61,7 @@ mod test { // 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 result = super::resize_dimensions(400, old_h, std::u32::MAX, new_h, false); let exact = (400_f64 * new_h as f64 / old_h as f64).round() as u32; result.1 == new_h && result.0 == exact.max(1) } @@ -87,12 +87,12 @@ mod test { #[test] fn resize_handles_overflow() { - let result = super::resize_dimensions(100, ::std::u32::MAX, 200, ::std::u32::MAX, true); + let result = super::resize_dimensions(100, std::u32::MAX, 200, std::u32::MAX, true); assert!(result.0 == 100); - assert!(result.1 == ::std::u32::MAX); + 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); + let result = super::resize_dimensions(std::u32::MAX, 100, std::u32::MAX, 200, true); + assert!(result.0 == std::u32::MAX); assert!(result.1 == 100); } diff --git a/src/traits.rs b/src/traits.rs index 56daaa0ddf..a993a3350c 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -5,7 +5,10 @@ use num_traits::{Bounded, Num, NumCast}; use std::ops::AddAssign; -use crate::color::{ColorType, Luma, LumaA, Rgb, Rgba}; +use crate::{ + color::{Luma, LumaA, Rgb, Rgba}, + ExtendedColorType, +}; /// Types which are safe to treat as an immutable byte slice in a pixel layout /// for image encoding. @@ -168,45 +171,45 @@ impl Lerp for f32 { /// The pixel with an associated `ColorType`. /// Not all possible pixels represent one of the predefined `ColorType`s. -pub trait PixelWithColorType: Pixel + self::private::SealedPixelWithColorType { +pub trait PixelWithColorType: Pixel + 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; + const COLOR_TYPE: ExtendedColorType; } impl PixelWithColorType for Rgb { - const COLOR_TYPE: ColorType = ColorType::Rgb8; + const COLOR_TYPE: ExtendedColorType = ExtendedColorType::Rgb8; } impl PixelWithColorType for Rgb { - const COLOR_TYPE: ColorType = ColorType::Rgb16; + const COLOR_TYPE: ExtendedColorType = ExtendedColorType::Rgb16; } impl PixelWithColorType for Rgb { - const COLOR_TYPE: ColorType = ColorType::Rgb32F; + const COLOR_TYPE: ExtendedColorType = ExtendedColorType::Rgb32F; } impl PixelWithColorType for Rgba { - const COLOR_TYPE: ColorType = ColorType::Rgba8; + const COLOR_TYPE: ExtendedColorType = ExtendedColorType::Rgba8; } impl PixelWithColorType for Rgba { - const COLOR_TYPE: ColorType = ColorType::Rgba16; + const COLOR_TYPE: ExtendedColorType = ExtendedColorType::Rgba16; } impl PixelWithColorType for Rgba { - const COLOR_TYPE: ColorType = ColorType::Rgba32F; + const COLOR_TYPE: ExtendedColorType = ExtendedColorType::Rgba32F; } impl PixelWithColorType for Luma { - const COLOR_TYPE: ColorType = ColorType::L8; + const COLOR_TYPE: ExtendedColorType = ExtendedColorType::L8; } impl PixelWithColorType for Luma { - const COLOR_TYPE: ColorType = ColorType::L16; + const COLOR_TYPE: ExtendedColorType = ExtendedColorType::L16; } impl PixelWithColorType for LumaA { - const COLOR_TYPE: ColorType = ColorType::La8; + const COLOR_TYPE: ExtendedColorType = ExtendedColorType::La8; } impl PixelWithColorType for LumaA { - const COLOR_TYPE: ColorType = ColorType::La16; + const COLOR_TYPE: ExtendedColorType = ExtendedColorType::La16; } /// Prevents down-stream users from implementing the `Primitive` trait diff --git a/tests/limits.rs b/tests/limits.rs index 4c041fc237..7380deb5c6 100644 --- a/tests/limits.rs +++ b/tests/limits.rs @@ -15,15 +15,12 @@ use std::io::Cursor; -use image::{ - io::Limits, load_from_memory_with_format, ImageDecoder, ImageFormat, ImageOutputFormat, - RgbImage, -}; +use image::{io::Limits, load_from_memory_with_format, ImageDecoder, ImageFormat, RgbImage}; const WIDTH: u32 = 256; const HEIGHT: u32 = 256; -fn test_image(format: ImageOutputFormat) -> Vec { +fn test_image(format: ImageFormat) -> Vec { let image = RgbImage::new(WIDTH, HEIGHT); let mut bytes: Vec = Vec::new(); image @@ -72,7 +69,7 @@ fn load_through_reader( fn gif() { use image::codecs::gif::GifDecoder; - let image = test_image(ImageOutputFormat::Gif); + let image = test_image(ImageFormat::Gif); // sanity check that our image loads successfully without limits assert!(load_from_memory_with_format(&image, ImageFormat::Gif).is_ok()); // check that the limits implementation is not overly restrictive @@ -89,7 +86,10 @@ fn gif() { // Custom constructor on GifDecoder #[allow(deprecated)] { - assert!(GifDecoder::with_limits(Cursor::new(&image), width_height_limits()).is_err()); + assert!(GifDecoder::new(Cursor::new(&image)) + .unwrap() + .set_limits(width_height_limits()) + .is_err()); // no tests for allocation limits because the caller is responsible for allocating the buffer in this case } } @@ -99,7 +99,7 @@ fn gif() { fn png() { use image::codecs::png::PngDecoder; - let image = test_image(ImageOutputFormat::Png); + let image = test_image(ImageFormat::Png); // sanity check that our image loads successfully without limits assert!(load_from_memory_with_format(&image, ImageFormat::Png).is_ok()); // check that the limits implementation is not overly restrictive @@ -126,7 +126,7 @@ fn png() { fn jpeg() { use image::codecs::jpeg::JpegDecoder; - let image = test_image(ImageOutputFormat::Jpeg(80)); + let image = test_image(ImageFormat::Jpeg); // sanity check that our image loads successfully without limits assert!(load_from_memory_with_format(&image, ImageFormat::Jpeg).is_ok()); // check that the limits implementation is not overly restrictive @@ -146,7 +146,7 @@ fn jpeg() { fn webp() { use image::codecs::webp::WebPDecoder; - let image = test_image(ImageOutputFormat::WebP); + let image = test_image(ImageFormat::WebP); // sanity check that our image loads successfully without limits assert!(load_from_memory_with_format(&image, ImageFormat::WebP).is_ok()); // check that the limits implementation is not overly restrictive @@ -166,7 +166,7 @@ fn webp() { fn tiff() { use image::codecs::tiff::TiffDecoder; - let image = test_image(ImageOutputFormat::Tiff); + let image = test_image(ImageFormat::Tiff); // sanity check that our image loads successfully without limits assert!(load_from_memory_with_format(&image, ImageFormat::Tiff).is_ok()); @@ -189,11 +189,11 @@ fn tiff() { } #[test] -#[cfg(all(feature = "avif", feature = "avif-decoder"))] +#[cfg(all(feature = "avif", feature = "avif-native"))] fn avif() { use image::codecs::avif::AvifDecoder; - let image = test_image(ImageOutputFormat::Avif); + let image = test_image(ImageFormat::Avif); // sanity check that our image loads successfully without limits assert!(load_from_memory_with_format(&image, ImageFormat::Avif).is_ok()); // check that the limits implementation is not overly restrictive @@ -213,7 +213,7 @@ fn avif() { fn bmp() { use image::codecs::bmp::BmpDecoder; - let image = test_image(ImageOutputFormat::Bmp); + let image = test_image(ImageFormat::Bmp); // sanity check that our image loads successfully without limits assert!(load_from_memory_with_format(&image, ImageFormat::Bmp).is_ok()); // check that the limits implementation is not overly restrictive diff --git a/tests/limits_anim.rs b/tests/limits_anim.rs index ed191800d8..d332f81b5c 100644 --- a/tests/limits_anim.rs +++ b/tests/limits_anim.rs @@ -7,7 +7,9 @@ use image::codecs::gif::GifDecoder; #[cfg(feature = "gif")] fn gif_decode(data: &[u8], limits: Limits) -> ImageResult<()> { - let mut decoder = GifDecoder::new(data).unwrap(); + use std::io::Cursor; + + let mut decoder = GifDecoder::new(Cursor::new(data)).unwrap(); decoder.set_limits(limits)?; { let frames = decoder.into_frames(); diff --git a/tests/reference/hdr/images/image1.hdr.bd02cf8f.png b/tests/reference/hdr/images/image1.hdr.bd02cf8f.png deleted file mode 100644 index a4682a0d5b..0000000000 Binary files a/tests/reference/hdr/images/image1.hdr.bd02cf8f.png and /dev/null differ diff --git a/tests/reference/hdr/images/rgbr4x4.hdr.72ffeca1.png b/tests/reference/hdr/images/rgbr4x4.hdr.72ffeca1.png deleted file mode 100644 index 0172c3a2f0..0000000000 Binary files a/tests/reference/hdr/images/rgbr4x4.hdr.72ffeca1.png and /dev/null differ diff --git a/tests/reference/webp/extended_images/advertises_rgba_but_frames_are_rgb.webp.936cb4e4.png b/tests/reference/webp/extended_images/advertises_rgba_but_frames_are_rgb.webp.936cb4e4.png new file mode 100644 index 0000000000..1a16453176 Binary files /dev/null and b/tests/reference/webp/extended_images/advertises_rgba_but_frames_are_rgb.webp.936cb4e4.png differ diff --git a/tests/reference/webp/extended_images/anim.webp.baaea4f2.png b/tests/reference/webp/extended_images/anim.webp.baaea4f2.png new file mode 100644 index 0000000000..89d43d555b Binary files /dev/null and b/tests/reference/webp/extended_images/anim.webp.baaea4f2.png differ diff --git a/tests/reference/webp/extended_images/anim.webp.f6449d24.png b/tests/reference/webp/extended_images/anim.webp.f6449d24.png deleted file mode 100644 index daf1dc0fd7..0000000000 Binary files a/tests/reference/webp/extended_images/anim.webp.f6449d24.png and /dev/null differ diff --git a/tests/reference/webp/extended_images/lossy_alpha.webp.37efcff9.png b/tests/reference/webp/extended_images/lossy_alpha.webp.37efcff9.png deleted file mode 100644 index be5d085a56..0000000000 Binary files a/tests/reference/webp/extended_images/lossy_alpha.webp.37efcff9.png and /dev/null differ diff --git a/tests/reference/webp/extended_images/lossy_alpha.webp.73837b57.png b/tests/reference/webp/extended_images/lossy_alpha.webp.73837b57.png new file mode 100644 index 0000000000..2786c5c3f4 Binary files /dev/null and b/tests/reference/webp/extended_images/lossy_alpha.webp.73837b57.png differ diff --git a/tests/reference/webp/lossless_images/2-color.webp.1c59922a.png b/tests/reference/webp/lossless_images/2-color.webp.1c59922a.png new file mode 100644 index 0000000000..14c83905ca Binary files /dev/null and b/tests/reference/webp/lossless_images/2-color.webp.1c59922a.png differ diff --git a/tests/reference/webp/lossless_images/2-color.webp.d5b9a8bd.png b/tests/reference/webp/lossless_images/2-color.webp.d5b9a8bd.png deleted file mode 100644 index 04cb063888..0000000000 Binary files a/tests/reference/webp/lossless_images/2-color.webp.d5b9a8bd.png and /dev/null differ diff --git a/tests/reference/webp/lossless_images/multi-color.webp.aeee944f.png b/tests/reference/webp/lossless_images/multi-color.webp.aeee944f.png new file mode 100644 index 0000000000..65998861d6 Binary files /dev/null and b/tests/reference/webp/lossless_images/multi-color.webp.aeee944f.png differ diff --git a/tests/reference/webp/lossless_images/multi-color.webp.d98bd812.png b/tests/reference/webp/lossless_images/multi-color.webp.d98bd812.png deleted file mode 100644 index 5da21eebe3..0000000000 Binary files a/tests/reference/webp/lossless_images/multi-color.webp.d98bd812.png and /dev/null differ diff --git a/tests/reference/webp/lossless_images/simple.webp.18d31ac3.png b/tests/reference/webp/lossless_images/simple.webp.18d31ac3.png deleted file mode 100644 index a1458a1b8f..0000000000 Binary files a/tests/reference/webp/lossless_images/simple.webp.18d31ac3.png and /dev/null differ diff --git a/tests/reference/webp/lossless_images/simple.webp.3d047de8.png b/tests/reference/webp/lossless_images/simple.webp.3d047de8.png new file mode 100644 index 0000000000..ab046665f5 Binary files /dev/null and b/tests/reference/webp/lossless_images/simple.webp.3d047de8.png differ diff --git a/tests/reference/webp/lossy_images/simple-gray.webp.6b294f50.png b/tests/reference/webp/lossy_images/simple-gray.webp.6b294f50.png deleted file mode 100644 index d87582eed0..0000000000 Binary files a/tests/reference/webp/lossy_images/simple-gray.webp.6b294f50.png and /dev/null differ diff --git a/tests/reference/webp/lossy_images/simple-gray.webp.f7c5ebfe.png b/tests/reference/webp/lossy_images/simple-gray.webp.f7c5ebfe.png new file mode 100644 index 0000000000..9eec1acbb8 Binary files /dev/null and b/tests/reference/webp/lossy_images/simple-gray.webp.f7c5ebfe.png differ diff --git a/tests/reference/webp/lossy_images/simple-rgb.webp.4bb22be5.png b/tests/reference/webp/lossy_images/simple-rgb.webp.4bb22be5.png deleted file mode 100644 index 894383144a..0000000000 Binary files a/tests/reference/webp/lossy_images/simple-rgb.webp.4bb22be5.png and /dev/null differ diff --git a/tests/reference/webp/lossy_images/simple-rgb.webp.86dd9540.png b/tests/reference/webp/lossy_images/simple-rgb.webp.86dd9540.png new file mode 100644 index 0000000000..83c61abc46 Binary files /dev/null and b/tests/reference/webp/lossy_images/simple-rgb.webp.86dd9540.png differ diff --git a/tests/reference_images.rs b/tests/reference_images.rs index 406c3e5c71..9defa31201 100644 --- a/tests/reference_images.rs +++ b/tests/reference_images.rs @@ -18,7 +18,7 @@ where { let base: PathBuf = BASE_PATH.iter().collect(); let decoders = &[ - "tga", "tiff", "png", "gif", "bmp", "ico", "jpg", "hdr", "pbm", "webp", + "tga", "tiff", "png", "gif", "bmp", "ico", "hdr", "pbm", "webp", ]; for decoder in decoders { let mut path = base.clone(); @@ -45,6 +45,10 @@ fn render_images() { process_images(IMAGE_DIR, None, |base, path, decoder| { println!("render_images {}", path.display()); let img = match image::open(&path) { + Ok(DynamicImage::ImageRgb32F(_)) | Ok(DynamicImage::ImageRgba32F(_)) => { + println!("Skipping {} - HDR codec is not enabled", path.display()); + return; + } Ok(img) => img, // Do not fail on unsupported error // This might happen because the testsuite contains unsupported images @@ -222,7 +226,7 @@ fn check_references() { use image::AnimationDecoder; let stream = io::BufReader::new(fs::File::open(&img_path).unwrap()); let decoder = match image::codecs::png::PngDecoder::new(stream) { - Ok(decoder) => decoder.apng(), + Ok(decoder) => decoder.apng().unwrap(), Err(image::ImageError::Unsupported(_)) => return, Err(err) => { panic!("decoding of {:?} failed with: {}", img_path, err) @@ -311,6 +315,28 @@ fn check_references() { #[cfg(feature = "hdr")] #[test] fn check_hdr_references() { + use byteorder::{LittleEndian as LE, ReadBytesExt}; + use std::fs::File; + use std::io::BufReader; + use std::path::Path; + + /// Helper function for reading raw 3-channel f32 images + fn read_raw_file>(path: P) -> ::std::io::Result> { + 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 { + ret.push(r.read_f32::()?); + ret.push(r.read_f32::()?); + ret.push(r.read_f32::()?); + } + Ok(ret) + } + let mut ref_path: PathBuf = BASE_PATH.iter().collect(); ref_path.push(REFERENCE_DIR); ref_path.push("hdr"); @@ -343,8 +369,12 @@ fn check_hdr_references() { let decoder = image::codecs::hdr::HdrDecoder::new(io::BufReader::new(fs::File::open(&path).unwrap())) .unwrap(); - let decoded = decoder.read_image_hdr().unwrap(); - let reference = image::codecs::hdr::read_raw_file(&ref_path).unwrap(); + let decoded = match DynamicImage::from_decoder(decoder).unwrap() { + DynamicImage::ImageRgb32F(img) => img.into_vec(), + _ => unreachable!(), + }; + + let reference = read_raw_file(&ref_path).unwrap(); assert_eq!(decoded, reference); } } diff --git a/tests/save_jpeg.rs b/tests/save_jpeg.rs index 309c763f7a..d1940c8d80 100644 --- a/tests/save_jpeg.rs +++ b/tests/save_jpeg.rs @@ -2,28 +2,27 @@ #![cfg(all(feature = "jpeg", feature = "tiff"))] extern crate image; -use image::{ImageFormat, ImageOutputFormat}; -use std::io::Cursor; +use image::codecs::jpeg::JpegEncoder; #[test] fn jqeg_qualitys() { let img = image::open("tests/images/tiff/testsuite/mandrill.tiff").unwrap(); let mut default = vec![]; - img.write_to(&mut Cursor::new(&mut default), ImageFormat::Jpeg) - .unwrap(); + let encoder = JpegEncoder::new(&mut default); + img.write_with_encoder(encoder).unwrap(); assert_eq!(&[255, 216], &default[..2]); let mut small = vec![]; - img.write_to(&mut Cursor::new(&mut small), ImageOutputFormat::Jpeg(10)) - .unwrap(); + let encoder = JpegEncoder::new_with_quality(&mut small, 10); + img.write_with_encoder(encoder).unwrap(); assert_eq!(&[255, 216], &small[..2]); assert!(small.len() < default.len()); let mut large = vec![]; - img.write_to(&mut Cursor::new(&mut large), ImageOutputFormat::Jpeg(99)) - .unwrap(); + let encoder = JpegEncoder::new_with_quality(&mut large, 99); + img.write_with_encoder(encoder).unwrap(); assert_eq!(&[255, 216], &large[..2]); assert!(large.len() > default.len()); diff --git a/tests/truncate_images.rs b/tests/truncate_images.rs index 7fc4f07afd..3347f2296c 100644 --- a/tests/truncate_images.rs +++ b/tests/truncate_images.rs @@ -44,7 +44,7 @@ fn truncate_images(decoder: &str) { let max_length = 1000; let mut buf = Vec::with_capacity(max_length); fin.take(max_length as u64).read_to_end(&mut buf).unwrap(); - for i in 0..buf.len() { + for i in (0..buf.len()).step_by(37) { image::load_from_memory(&buf[..i + 1]).ok(); } })