image-webp-0.2.4/.cargo_vcs_info.json0000644000000001360000000000100130540ustar { "git": { "sha1": "08b57d473c90148dd4b37a075df365afc3dfcc2b" }, "path_in_vcs": "" }image-webp-0.2.4/Cargo.lock0000644000000216610000000000100110350ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "bytemuck" version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" [[package]] name = "byteorder-lite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "cc" version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ "jobserver", "libc", "shlex", ] [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "crc32fast" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "fdeflate" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] [[package]] name = "flate2" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "image" version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", "num-traits", ] [[package]] name = "image-webp" version = "0.2.4" dependencies = [ "byteorder-lite", "paste", "png", "quick-error", "rand", "webp", ] [[package]] name = "jobserver" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", ] [[package]] name = "libc" version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libwebp-sys" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54cd30df7c7165ce74a456e4ca9732c603e8dc5e60784558c1c6dc047f876733" dependencies = [ "cc", "glob", ] [[package]] name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "png" version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quick-error" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.16", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "webp" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f53152f51fb5af0c08484c33d16cca96175881d1f3dec068c23b31a158c2d99" dependencies = [ "image", "libwebp-sys", ] [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.9.3", ] [[package]] name = "zerocopy" version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", "syn", ] image-webp-0.2.4/Cargo.toml0000644000000025600000000000100110550ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.80.1" name = "image-webp" version = "0.2.4" build = false include = [ "/src", "LICENSE-APACHE", "LICENSE-MIT", "README.md", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "WebP encoding and decoding in pure Rust" homepage = "https://github.com/image-rs/image-webp" readme = "README.md" categories = [ "multimedia::images", "multimedia::encoding", "encoding", ] license = "MIT OR Apache-2.0" repository = "https://github.com/image-rs/image-webp" [features] _benchmarks = [] [lib] name = "image_webp" path = "src/lib.rs" [dependencies.byteorder-lite] version = "0.1.0" [dependencies.quick-error] version = "2.0.1" [dev-dependencies.paste] version = "1.0.14" [dev-dependencies.png] version = "0.17.12" [dev-dependencies.rand] version = "0.8.5" [dev-dependencies.webp] version = "0.3.0" image-webp-0.2.4/Cargo.toml.orig000064400000000000000000000011231046102023000145300ustar 00000000000000[package] name = "image-webp" version = "0.2.4" edition = "2021" license = "MIT OR Apache-2.0" rust-version = "1.80.1" description = "WebP encoding and decoding in pure Rust" homepage = "https://github.com/image-rs/image-webp" repository = "https://github.com/image-rs/image-webp" categories = ["multimedia::images", "multimedia::encoding", "encoding"] include = ["/src", "LICENSE-APACHE", "LICENSE-MIT", "README.md"] [dependencies] byteorder-lite = "0.1.0" quick-error = "2.0.1" [dev-dependencies] paste = "1.0.14" png = "0.17.12" rand = "0.8.5" webp = "0.3.0" [features] _benchmarks = [] image-webp-0.2.4/LICENSE-APACHE000064400000000000000000000236761046102023000136060ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS image-webp-0.2.4/LICENSE-MIT000064400000000000000000000020141046102023000132750ustar 00000000000000MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. image-webp-0.2.4/README.md000064400000000000000000000052021046102023000131220ustar 00000000000000# image-webp [![crates.io](https://img.shields.io/crates/v/image-webp.svg)](https://crates.io/crates/image-webp) [![Documentation](https://docs.rs/image-webp/badge.svg)](https://docs.rs/image-webp) [![Build Status](https://github.com/image-rs/image-webp/workflows/Rust%20CI/badge.svg)](https://github.com/image-rs/image-webp/actions) This crate is an independent implementation of the WebP image format, written so that the `image` crate can have a pure-Rust WebP backend for both encoding and decoding. ## Current Status * **Decoder:** Supports all WebP format features including both lossless and lossy compression, alpha channel, and animation. Both the "simple" and "extended" formats are handled, and it exposes methods to extract ICC, EXIF, and XMP chunks. Decoding speed is generally in the range of **70-100%** of the speed of libwebp. * **Encoder:** This crate only supports lossless encoding. The encoder implementation is relatively basic which makes it very fast, but it doesn't get as good compression ratios as libwebp can. Nonetheless, it often produces smaller files than PNG, even when compared against the slowest/highest compression options of PNG encoders. ## Future possibilities * We continue to be interested in **optimizations** and **bug fixes** and hope the bring the decoder closer to parity with libwebp. * Another potential area is **animation encoding**. Much of the groundwork is in place for this, but it will require some additional work to implement. * We would like to add **lossy encoding** support, but this is a non-trivial task and would require a lot of work. If you are interested in helping with this, please get in touch! ## Unsafe code Both this crate and all of its dependencies currently contain no unsafe code. NOTE: This isn't a guarantee that unsafe code will never be added. It may prove necessary in the future to improve performance, but we will always strive to minimize the use of unsafe code and ensure that it is well-tested and documented. ``` $ cargo geiger Metric output format: x/y x = unsafe code used by the build y = total unsafe code found in the crate Symbols: 🔒 = No `unsafe` usage found, declares #![forbid(unsafe_code)] ❓ = No `unsafe` usage found, missing #![forbid(unsafe_code)] ☢️ = `unsafe` usage found Functions Expressions Impls Traits Methods Dependency 0/0 0/0 0/0 0/0 0/0 🔒 image-webp 0.2.3 0/0 0/0 0/0 0/0 0/0 🔒 ├── byteorder-lite 0.1.0 0/0 0/0 0/0 0/0 0/0 ❓ └── quick-error 2.0.1 0/0 0/0 0/0 0/0 0/0 ``` image-webp-0.2.4/src/alpha_blending.rs000064400000000000000000000124271046102023000157360ustar 00000000000000//! Optimized alpha blending routines based on libwebp //! //! const fn channel_shift(i: u32) -> u32 { i * 8 } /// Blend a single channel of `src` over `dst`, given their alpha channel values. /// `src` and `dst` are assumed to be NOT pre-multiplied by alpha. fn blend_channel_nonpremult( src: u32, src_a: u8, dst: u32, dst_a: u8, scale: u32, shift: u32, ) -> u8 { let src_channel = ((src >> shift) & 0xff) as u8; let dst_channel = ((dst >> shift) & 0xff) as u8; let blend_unscaled = (u32::from(src_channel) * u32::from(src_a)) + (u32::from(dst_channel) * u32::from(dst_a)); debug_assert!(u64::from(blend_unscaled) < (1u64 << 32) / u64::from(scale)); ((blend_unscaled * scale) >> channel_shift(3)) as u8 } /// Blend `src` over `dst` assuming they are NOT pre-multiplied by alpha. fn blend_pixel_nonpremult(src: u32, dst: u32) -> u32 { let src_a = ((src >> channel_shift(3)) & 0xff) as u8; if src_a == 0 { dst } else { let dst_a = ((dst >> channel_shift(3)) & 0xff) as u8; // Approximate integer arithmetic for: dst_factor_a = (dst_a * (255 - src_a)) / 255 // libwebp used the following formula here: //let dst_factor_a = (dst_a as u32 * (256 - src_a as u32)) >> 8; // however, we've found that we can use a more precise approximation without losing performance: let dst_factor_a = div_by_255(u32::from(dst_a) * (255 - u32::from(src_a))); let blend_a = u32::from(src_a) + dst_factor_a; let scale = (1u32 << 24) / blend_a; let blend_r = blend_channel_nonpremult(src, src_a, dst, dst_factor_a as u8, scale, channel_shift(0)); let blend_g = blend_channel_nonpremult(src, src_a, dst, dst_factor_a as u8, scale, channel_shift(1)); let blend_b = blend_channel_nonpremult(src, src_a, dst, dst_factor_a as u8, scale, channel_shift(2)); debug_assert!(u32::from(src_a) + dst_factor_a < 256); (u32::from(blend_r) << channel_shift(0)) | (u32::from(blend_g) << channel_shift(1)) | (u32::from(blend_b) << channel_shift(2)) | (blend_a << channel_shift(3)) } } pub(crate) fn do_alpha_blending(buffer: [u8; 4], canvas: [u8; 4]) -> [u8; 4] { // The original C code contained different shift functions for different endianness, // but they didn't work when ported to Rust directly (and probably didn't work in C either). // So instead we reverse the order of bytes on big-endian here, at the interface. // `from_le_bytes` is a no-op on little endian (most systems) and a cheap shuffle on big endian. blend_pixel_nonpremult(u32::from_le_bytes(buffer), u32::from_le_bytes(canvas)).to_le_bytes() } /// Divides by 255, rounding to nearest (as opposed to down, like regular integer division does). /// TODO: cannot output 256, so the output is effecitively u8. Plumb that through the code. // // Sources: // https://arxiv.org/pdf/2202.02864 // https://github.com/image-rs/image-webp/issues/119#issuecomment-2544007820 #[inline] const fn div_by_255(v: u32) -> u32 { (((v + 0x80) >> 8) + v + 0x80) >> 8 } #[cfg(test)] mod tests { use super::*; fn do_alpha_blending_reference(buffer: [u8; 4], canvas: [u8; 4]) -> [u8; 4] { 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 }; [blend_rgb[0], blend_rgb[1], blend_rgb[2], blend_alpha] } #[test] #[ignore] // takes too long to run on CI. Run this locally when changing the function. fn alpha_blending_optimization() { for r1 in 0..u8::MAX { for a1 in 11..u8::MAX { for r2 in 0..u8::MAX { for a2 in 11..u8::MAX { let opt = do_alpha_blending([r1, 0, 0, a1], [r2, 0, 0, a2]); let slow = do_alpha_blending_reference([r1, 0, 0, a1], [r2, 0, 0, a2]); // libwebp doesn't do exact blending and so we don't either for (o, s) in opt.iter().zip(slow.iter()) { assert!( o.abs_diff(*s) <= 3, "Mismatch in results! opt: {opt:?}, slow: {slow:?}, blended values: [{r1}, 0, 0, {a1}], [{r2}, 0, 0, {a2}]" ); } } } } } } } image-webp-0.2.4/src/decoder.rs000064400000000000000000001123761046102023000144200ustar 00000000000000use byteorder_lite::{LittleEndian, ReadBytesExt}; use quick_error::quick_error; use std::collections::HashMap; use std::io::{self, BufRead, Cursor, Read, Seek}; use std::num::NonZeroU16; use std::ops::Range; use crate::extended::{self, get_alpha_predictor, read_alpha_chunk, WebPExtendedInfo}; use super::lossless::LosslessDecoder; use super::vp8::Vp8Decoder; quick_error! { /// Errors that can occur when attempting to decode a WebP image #[derive(Debug)] #[non_exhaustive] pub enum DecodingError { /// An IO error occurred while reading the file IoError(err: io::Error) { from() display("IO Error: {}", err) source(err) } /// RIFF's "RIFF" signature not found or invalid RiffSignatureInvalid(err: [u8; 4]) { display("Invalid RIFF signature: {err:x?}") } /// WebP's "WEBP" signature not found or invalid WebpSignatureInvalid(err: [u8; 4]) { display("Invalid WebP signature: {err:x?}") } /// An expected chunk was missing ChunkMissing { display("An expected chunk was missing") } /// Chunk Header was incorrect or invalid in its usage ChunkHeaderInvalid(err: [u8; 4]) { display("Invalid Chunk header: {err:x?}") } #[allow(deprecated)] #[deprecated] /// Some bits were invalid ReservedBitSet { display("Reserved bits set") } /// The ALPH chunk preprocessing info flag was invalid InvalidAlphaPreprocessing { display("Alpha chunk preprocessing flag invalid") } /// Invalid compression method InvalidCompressionMethod { display("Invalid compression method") } /// Alpha chunk doesn't match the frame's size AlphaChunkSizeMismatch { display("Alpha chunk size mismatch") } /// Image is too large, either for the platform's pointer size or generally ImageTooLarge { display("Image too large") } /// Frame would go out of the canvas FrameOutsideImage { display("Frame outside image") } /// Signature of 0x2f not found LosslessSignatureInvalid(err: u8) { display("Invalid lossless signature: {err:x?}") } /// Version Number was not zero VersionNumberInvalid(err: u8) { display("Invalid lossless version number: {err}") } /// Invalid color cache bits InvalidColorCacheBits(err: u8) { display("Invalid color cache bits: {err}") } /// An invalid Huffman code was encountered HuffmanError { display("Invalid Huffman code") } /// The bitstream was somehow corrupt BitStreamError { display("Corrupt bitstream") } /// The transforms specified were invalid TransformError { display("Invalid transform") } /// VP8's `[0x9D, 0x01, 0x2A]` magic not found or invalid Vp8MagicInvalid(err: [u8; 3]) { display("Invalid VP8 magic: {err:x?}") } /// VP8 Decoder initialisation wasn't provided with enough data NotEnoughInitData { display("Not enough VP8 init data") } /// At time of writing, only the YUV colour-space encoded as `0` is specified ColorSpaceInvalid(err: u8) { display("Invalid VP8 color space: {err}") } /// LUMA prediction mode was not recognised LumaPredictionModeInvalid(err: i8) { display("Invalid VP8 luma prediction mode: {err}") } /// Intra-prediction mode was not recognised IntraPredictionModeInvalid(err: i8) { display("Invalid VP8 intra prediction mode: {err}") } /// Chroma prediction mode was not recognised ChromaPredictionModeInvalid(err: i8) { display("Invalid VP8 chroma prediction mode: {err}") } /// Inconsistent image sizes InconsistentImageSizes { display("Inconsistent image sizes") } /// The file may be valid, but this crate doesn't support decoding it. UnsupportedFeature(err: String) { display("Unsupported feature: {err}") } /// Invalid function call or parameter InvalidParameter(err: String) { display("Invalid parameter: {err}") } /// Memory limit exceeded MemoryLimitExceeded { display("Memory limit exceeded") } /// Invalid chunk size InvalidChunkSize { display("Invalid chunk size") } /// No more frames in image NoMoreFrames { display("No more frames") } } } /// All possible RIFF chunks in a WebP image file #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub(crate) enum WebPRiffChunk { RIFF, WEBP, VP8, VP8L, VP8X, ANIM, ANMF, ALPH, ICCP, EXIF, XMP, Unknown([u8; 4]), } impl WebPRiffChunk { pub(crate) const fn from_fourcc(chunk_fourcc: [u8; 4]) -> Self { match &chunk_fourcc { b"RIFF" => Self::RIFF, b"WEBP" => Self::WEBP, b"VP8 " => Self::VP8, b"VP8L" => Self::VP8L, b"VP8X" => Self::VP8X, b"ANIM" => Self::ANIM, b"ANMF" => Self::ANMF, b"ALPH" => Self::ALPH, b"ICCP" => Self::ICCP, b"EXIF" => Self::EXIF, b"XMP " => Self::XMP, _ => Self::Unknown(chunk_fourcc), } } pub(crate) const 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 ", Self::Unknown(fourcc) => fourcc, } } pub(crate) const fn is_unknown(self) -> bool { matches!(self, Self::Unknown(_)) } } // enum WebPImage { // Lossy(VP8Frame), // Lossless(LosslessFrame), // Extended(ExtendedImage), // } enum ImageKind { Lossy, Lossless, Extended(WebPExtendedInfo), } struct AnimationState { next_frame: u32, next_frame_start: u64, dispose_next_frame: bool, previous_frame_width: u32, previous_frame_height: u32, previous_frame_x_offset: u32, previous_frame_y_offset: u32, canvas: Option>, } impl Default for AnimationState { fn default() -> Self { Self { next_frame: 0, next_frame_start: 0, dispose_next_frame: true, previous_frame_width: 0, previous_frame_height: 0, previous_frame_x_offset: 0, previous_frame_y_offset: 0, canvas: None, } } } /// Number of times that an animation loops. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum LoopCount { /// The animation loops forever. Forever, /// Each frame of the animation is displayed the specified number of times. Times(NonZeroU16), } /// WebP decoder configuration options #[derive(Clone)] #[non_exhaustive] pub struct WebPDecodeOptions { /// The upsampling method used in conversion from lossy yuv to rgb /// /// Defaults to `Bilinear`. pub lossy_upsampling: UpsamplingMethod, } impl Default for WebPDecodeOptions { fn default() -> Self { Self { lossy_upsampling: UpsamplingMethod::Bilinear, } } } /// Methods for upsampling the chroma values in lossy decoding /// /// The chroma red and blue planes are encoded in VP8 as half the size of the luma plane /// Therefore we need to upsample these values up to fit each pixel in the image. #[derive(Clone, Copy, Default)] pub enum UpsamplingMethod { /// Fancy upsampling /// /// Does bilinear interpolation using the 4 values nearest to the pixel, weighting based on the distance /// from the pixel. #[default] Bilinear, /// Simple upsampling, just uses the closest u/v value to the pixel when upsampling /// /// Matches the -nofancy option in dwebp. /// Should be faster but may lead to slightly jagged edges. Simple, } /// WebP image format decoder. pub struct WebPDecoder { r: R, memory_limit: usize, width: u32, height: u32, kind: ImageKind, animation: AnimationState, is_lossy: bool, has_alpha: bool, num_frames: u32, loop_count: LoopCount, loop_duration: u64, chunks: HashMap>, webp_decode_options: WebPDecodeOptions, } impl WebPDecoder { /// Create a new `WebPDecoder` from the reader `r`. The decoder performs many small reads, so the /// reader should be buffered. pub fn new(r: R) -> Result { Self::new_with_options(r, WebPDecodeOptions::default()) } /// Create a new `WebPDecoder` from the reader `r` with the options `WebPDecodeOptions`. The decoder /// performs many small reads, so the reader should be buffered. pub fn new_with_options( r: R, webp_decode_options: WebPDecodeOptions, ) -> Result { let mut decoder = Self { r, width: 0, height: 0, num_frames: 0, kind: ImageKind::Lossy, chunks: HashMap::new(), animation: Default::default(), memory_limit: usize::MAX, is_lossy: false, has_alpha: false, loop_count: LoopCount::Times(NonZeroU16::new(1).unwrap()), loop_duration: 0, webp_decode_options, }; decoder.read_data()?; Ok(decoder) } fn read_data(&mut self) -> Result<(), DecodingError> { let (WebPRiffChunk::RIFF, riff_size, _) = read_chunk_header(&mut self.r)? else { return Err(DecodingError::ChunkHeaderInvalid(*b"RIFF")); }; match &read_fourcc(&mut self.r)? { WebPRiffChunk::WEBP => {} fourcc => return Err(DecodingError::WebpSignatureInvalid(fourcc.to_fourcc())), } let (chunk, chunk_size, chunk_size_rounded) = read_chunk_header(&mut self.r)?; let start = self.r.stream_position()?; match chunk { WebPRiffChunk::VP8 => { let tag = self.r.read_u24::()?; let keyframe = tag & 1 == 0; if !keyframe { return Err(DecodingError::UnsupportedFeature( "Non-keyframe frames".to_owned(), )); } let mut tag = [0u8; 3]; self.r.read_exact(&mut tag)?; if tag != [0x9d, 0x01, 0x2a] { return Err(DecodingError::Vp8MagicInvalid(tag)); } let w = self.r.read_u16::()?; let h = self.r.read_u16::()?; self.width = u32::from(w & 0x3FFF); self.height = u32::from(h & 0x3FFF); if self.width == 0 || self.height == 0 { return Err(DecodingError::InconsistentImageSizes); } self.chunks .insert(WebPRiffChunk::VP8, start..start + chunk_size); self.kind = ImageKind::Lossy; self.is_lossy = true; } WebPRiffChunk::VP8L => { let signature = self.r.read_u8()?; if signature != 0x2f { return Err(DecodingError::LosslessSignatureInvalid(signature)); } let header = self.r.read_u32::()?; let version = header >> 29; if version != 0 { return Err(DecodingError::VersionNumberInvalid(version as u8)); } self.width = (1 + header) & 0x3FFF; self.height = (1 + (header >> 14)) & 0x3FFF; self.chunks .insert(WebPRiffChunk::VP8L, start..start + chunk_size); self.kind = ImageKind::Lossless; self.has_alpha = (header >> 28) & 1 != 0; } WebPRiffChunk::VP8X => { let mut info = extended::read_extended_header(&mut self.r)?; self.width = info.canvas_width; self.height = info.canvas_height; let mut position = start + chunk_size_rounded; let max_position = position + riff_size.saturating_sub(12); self.r.seek(io::SeekFrom::Start(position))?; while position < max_position { match read_chunk_header(&mut self.r) { Ok((chunk, chunk_size, chunk_size_rounded)) => { let range = position + 8..position + 8 + chunk_size; position += 8 + chunk_size_rounded; if !chunk.is_unknown() { self.chunks.entry(chunk).or_insert(range); } if chunk == WebPRiffChunk::ANMF { self.num_frames += 1; if chunk_size < 24 { return Err(DecodingError::InvalidChunkSize); } self.r.seek_relative(12)?; let duration = self.r.read_u32::()? & 0xffffff; self.loop_duration = self.loop_duration.wrapping_add(u64::from(duration)); // If the image is animated, the image data chunk will be inside the // ANMF chunks, so we must inspect them to determine whether the // image contains any lossy image data. VP8 chunks store lossy data // and the spec says that lossless images SHOULD NOT contain ALPH // chunks, so we treat both as indicators of lossy images. if !self.is_lossy { let (subchunk, ..) = read_chunk_header(&mut self.r)?; if let WebPRiffChunk::VP8 | WebPRiffChunk::ALPH = subchunk { self.is_lossy = true; } self.r.seek_relative(chunk_size_rounded as i64 - 24)?; } else { self.r.seek_relative(chunk_size_rounded as i64 - 16)?; } continue; } self.r.seek_relative(chunk_size_rounded as i64)?; } Err(DecodingError::IoError(e)) if e.kind() == io::ErrorKind::UnexpectedEof => { break; } Err(e) => return Err(e), } } self.is_lossy = self.is_lossy || self.chunks.contains_key(&WebPRiffChunk::VP8); // NOTE: We allow malformed images that have `info.icc_profile` set without a ICCP chunk, // because this is relatively common. if info.animation && (!self.chunks.contains_key(&WebPRiffChunk::ANIM) || !self.chunks.contains_key(&WebPRiffChunk::ANMF)) || info.exif_metadata && !self.chunks.contains_key(&WebPRiffChunk::EXIF) || info.xmp_metadata && !self.chunks.contains_key(&WebPRiffChunk::XMP) || !info.animation && self.chunks.contains_key(&WebPRiffChunk::VP8) == self.chunks.contains_key(&WebPRiffChunk::VP8L) { return Err(DecodingError::ChunkMissing); } // Decode ANIM chunk. if info.animation { match self.read_chunk(WebPRiffChunk::ANIM, 6) { Ok(Some(chunk)) => { let mut cursor = Cursor::new(chunk); cursor.read_exact(&mut info.background_color_hint)?; self.loop_count = match cursor.read_u16::()? { 0 => LoopCount::Forever, n => LoopCount::Times(NonZeroU16::new(n).unwrap()), }; self.animation.next_frame_start = self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8; } Ok(None) => return Err(DecodingError::ChunkMissing), Err(DecodingError::MemoryLimitExceeded) => { return Err(DecodingError::InvalidChunkSize) } Err(e) => return Err(e), } } // If the image is animated, the image data chunk will be inside the ANMF chunks. We // store the ALPH, VP8, and VP8L chunks (as applicable) of the first frame in the // hashmap so that we can read them later. if let Some(range) = self.chunks.get(&WebPRiffChunk::ANMF).cloned() { let mut position = range.start + 16; self.r.seek(io::SeekFrom::Start(position))?; for _ in 0..2 { let (subchunk, subchunk_size, subchunk_size_rounded) = read_chunk_header(&mut self.r)?; let subrange = position + 8..position + 8 + subchunk_size; self.chunks.entry(subchunk).or_insert(subrange.clone()); position += 8 + subchunk_size_rounded; if position + 8 > range.end { break; } } } self.has_alpha = info.alpha; self.kind = ImageKind::Extended(info); } _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())), }; Ok(()) } /// Sets the maximum amount of memory that the decoder is allowed to allocate at once. /// /// TODO: Some allocations currently ignore this limit. pub fn set_memory_limit(&mut self, limit: usize) { self.memory_limit = limit; } /// Get the background color specified in the image file if the image is extended and animated webp. pub fn background_color_hint(&self) -> Option<[u8; 4]> { if let ImageKind::Extended(info) = &self.kind { Some(info.background_color_hint) } else { None } } /// Sets the background color if the image is an extended and animated webp. pub fn set_background_color(&mut self, color: [u8; 4]) -> Result<(), DecodingError> { if let ImageKind::Extended(info) = &mut self.kind { info.background_color = Some(color); Ok(()) } else { Err(DecodingError::InvalidParameter( "Background color can only be set on animated webp".to_owned(), )) } } /// Returns the (width, height) of the image in pixels. pub fn dimensions(&self) -> (u32, u32) { (self.width, self.height) } /// Returns whether the image has an alpha channel. If so, the pixel format is Rgba8 and /// otherwise Rgb8. pub fn has_alpha(&self) -> bool { self.has_alpha } /// Returns true if the image is animated. pub fn is_animated(&self) -> bool { match &self.kind { ImageKind::Lossy | ImageKind::Lossless => false, ImageKind::Extended(extended) => extended.animation, } } /// Returns whether the image is lossy. For animated images, this is true if any frame is lossy. pub fn is_lossy(&mut self) -> bool { self.is_lossy } /// Returns the number of frames of a single loop of the animation, or zero if the image is not /// animated. pub fn num_frames(&self) -> u32 { self.num_frames } /// Returns the number of times the animation should loop. pub fn loop_count(&self) -> LoopCount { self.loop_count } /// Returns the total duration of one loop through the animation in milliseconds, or zero if the /// image is not animated. /// /// This is the sum of the durations of all individual frames of the image. pub fn loop_duration(&self) -> u64 { self.loop_duration } fn read_chunk( &mut self, chunk: WebPRiffChunk, max_size: usize, ) -> Result>, DecodingError> { match self.chunks.get(&chunk) { Some(range) => { if range.end - range.start > max_size as u64 { return Err(DecodingError::MemoryLimitExceeded); } self.r.seek(io::SeekFrom::Start(range.start))?; let mut data = vec![0; (range.end - range.start) as usize]; self.r.read_exact(&mut data)?; Ok(Some(data)) } None => Ok(None), } } /// Returns the raw bytes of the ICC profile, or None if there is no ICC profile. pub fn icc_profile(&mut self) -> Result>, DecodingError> { self.read_chunk(WebPRiffChunk::ICCP, self.memory_limit) } /// Returns the raw bytes of the EXIF metadata, or None if there is no EXIF metadata. pub fn exif_metadata(&mut self) -> Result>, DecodingError> { self.read_chunk(WebPRiffChunk::EXIF, self.memory_limit) } /// Returns the raw bytes of the XMP metadata, or None if there is no XMP metadata. pub fn xmp_metadata(&mut self) -> Result>, DecodingError> { self.read_chunk(WebPRiffChunk::XMP, self.memory_limit) } /// Returns the number of bytes required to store the image or a single frame, or None if that /// would take more than `usize::MAX` bytes. pub fn output_buffer_size(&self) -> Option { let bytes_per_pixel = if self.has_alpha() { 4 } else { 3 }; (self.width as usize) .checked_mul(self.height as usize)? .checked_mul(bytes_per_pixel) } /// Returns the raw bytes of the image. For animated images, this is the first frame. /// /// Fails with `ImageTooLarge` if `buf` has length different than `output_buffer_size()` pub fn read_image(&mut self, buf: &mut [u8]) -> Result<(), DecodingError> { if Some(buf.len()) != self.output_buffer_size() { return Err(DecodingError::ImageTooLarge); } if self.is_animated() { let saved = std::mem::take(&mut self.animation); self.animation.next_frame_start = self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8; let result = self.read_frame(buf); self.animation = saved; result?; } else if let Some(range) = self.chunks.get(&WebPRiffChunk::VP8L) { let mut decoder = LosslessDecoder::new(range_reader(&mut self.r, range.clone())?); if self.has_alpha { decoder.decode_frame(self.width, self.height, false, buf)?; } else { let mut data = vec![0; self.width as usize * self.height as usize * 4]; decoder.decode_frame(self.width, self.height, false, &mut data)?; for (rgba_val, chunk) in data.chunks_exact(4).zip(buf.chunks_exact_mut(3)) { chunk.copy_from_slice(&rgba_val[..3]); } } } else { let range = self .chunks .get(&WebPRiffChunk::VP8) .ok_or(DecodingError::ChunkMissing)?; let reader = range_reader(&mut self.r, range.start..range.end)?; let frame = Vp8Decoder::decode_frame(reader)?; if u32::from(frame.width) != self.width || u32::from(frame.height) != self.height { return Err(DecodingError::InconsistentImageSizes); } if self.has_alpha() { frame.fill_rgba(buf, self.webp_decode_options.lossy_upsampling); let range = self .chunks .get(&WebPRiffChunk::ALPH) .ok_or(DecodingError::ChunkMissing)? .clone(); let alpha_chunk = read_alpha_chunk( &mut range_reader(&mut self.r, range)?, self.width as u16, self.height as u16, )?; for y in 0..frame.height { for x in 0..frame.width { let predictor: u8 = get_alpha_predictor( x.into(), y.into(), frame.width.into(), alpha_chunk.filtering_method, buf, ); let alpha_index = usize::from(y) * usize::from(frame.width) + usize::from(x); let buffer_index = alpha_index * 4 + 3; buf[buffer_index] = predictor.wrapping_add(alpha_chunk.data[alpha_index]); } } } else { frame.fill_rgb(buf, self.webp_decode_options.lossy_upsampling); } } Ok(()) } /// Reads the next frame of the animation. /// /// The frame contents are written into `buf` and the method returns the duration of the frame /// in milliseconds. If there are no more frames, the method returns /// `DecodingError::NoMoreFrames` and `buf` is left unchanged. /// /// # Panics /// /// Panics if the image is not animated. pub fn read_frame(&mut self, buf: &mut [u8]) -> Result { assert!(self.is_animated()); assert_eq!(Some(buf.len()), self.output_buffer_size()); if self.animation.next_frame == self.num_frames { return Err(DecodingError::NoMoreFrames); } let ImageKind::Extended(info) = &self.kind else { unreachable!() }; self.r .seek(io::SeekFrom::Start(self.animation.next_frame_start))?; let anmf_size = match read_chunk_header(&mut self.r)? { (WebPRiffChunk::ANMF, size, _) if size >= 32 => size, _ => return Err(DecodingError::ChunkHeaderInvalid(*b"ANMF")), }; // Read ANMF chunk let frame_x = extended::read_3_bytes(&mut self.r)? * 2; let frame_y = extended::read_3_bytes(&mut self.r)? * 2; let frame_width = extended::read_3_bytes(&mut self.r)? + 1; let frame_height = extended::read_3_bytes(&mut self.r)? + 1; if frame_width > 16384 || frame_height > 16384 { return Err(DecodingError::ImageTooLarge); } if frame_x + frame_width > self.width || frame_y + frame_height > self.height { return Err(DecodingError::FrameOutsideImage); } let duration = extended::read_3_bytes(&mut self.r)?; let frame_info = self.r.read_u8()?; let use_alpha_blending = frame_info & 0b00000010 == 0; let dispose = frame_info & 0b00000001 != 0; let clear_color = if self.animation.dispose_next_frame { info.background_color } else { None }; // Read normal bitstream now let (chunk, chunk_size, chunk_size_rounded) = read_chunk_header(&mut self.r)?; if chunk_size_rounded + 24 > anmf_size { return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())); } let (frame, frame_has_alpha): (Vec, bool) = match chunk { WebPRiffChunk::VP8 => { let reader = (&mut self.r).take(chunk_size); let raw_frame = Vp8Decoder::decode_frame(reader)?; if u32::from(raw_frame.width) != frame_width || u32::from(raw_frame.height) != frame_height { return Err(DecodingError::InconsistentImageSizes); } let mut rgb_frame = vec![0; frame_width as usize * frame_height as usize * 3]; raw_frame.fill_rgb(&mut rgb_frame, self.webp_decode_options.lossy_upsampling); (rgb_frame, false) } WebPRiffChunk::VP8L => { let reader = (&mut self.r).take(chunk_size); let mut lossless_decoder = LosslessDecoder::new(reader); let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4]; lossless_decoder.decode_frame(frame_width, frame_height, false, &mut rgba_frame)?; (rgba_frame, true) } WebPRiffChunk::ALPH => { if chunk_size_rounded + 32 > anmf_size { return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())); } // read alpha let next_chunk_start = self.r.stream_position()? + chunk_size_rounded; let mut reader = (&mut self.r).take(chunk_size); let alpha_chunk = read_alpha_chunk(&mut reader, frame_width as u16, frame_height as u16)?; // read opaque self.r.seek(io::SeekFrom::Start(next_chunk_start))?; let (next_chunk, next_chunk_size, _) = read_chunk_header(&mut self.r)?; if chunk_size + next_chunk_size + 32 > anmf_size { return Err(DecodingError::ChunkHeaderInvalid(next_chunk.to_fourcc())); } let frame = Vp8Decoder::decode_frame((&mut self.r).take(next_chunk_size))?; let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4]; frame.fill_rgba(&mut rgba_frame, self.webp_decode_options.lossy_upsampling); for y in 0..frame.height { for x in 0..frame.width { let predictor: u8 = get_alpha_predictor( x.into(), y.into(), frame.width.into(), alpha_chunk.filtering_method, &rgba_frame, ); let alpha_index = usize::from(y) * usize::from(frame.width) + usize::from(x); let buffer_index = alpha_index * 4 + 3; rgba_frame[buffer_index] = predictor.wrapping_add(alpha_chunk.data[alpha_index]); } } (rgba_frame, true) } _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())), }; // fill starting canvas with clear color if self.animation.canvas.is_none() { self.animation.canvas = { let mut canvas = vec![0; (self.width * self.height * 4) as usize]; if let Some(color) = info.background_color.as_ref() { canvas .chunks_exact_mut(4) .for_each(|c| c.copy_from_slice(color)) } Some(canvas) } } extended::composite_frame( self.animation.canvas.as_mut().unwrap(), self.width, self.height, clear_color, &frame, frame_x, frame_y, frame_width, frame_height, frame_has_alpha, use_alpha_blending, self.animation.previous_frame_width, self.animation.previous_frame_height, self.animation.previous_frame_x_offset, self.animation.previous_frame_y_offset, ); self.animation.previous_frame_width = frame_width; self.animation.previous_frame_height = frame_height; self.animation.previous_frame_x_offset = frame_x; self.animation.previous_frame_y_offset = frame_y; self.animation.dispose_next_frame = dispose; self.animation.next_frame_start += anmf_size + 8; self.animation.next_frame += 1; if self.has_alpha() { buf.copy_from_slice(self.animation.canvas.as_ref().unwrap()); } else { for (b, c) in buf .chunks_exact_mut(3) .zip(self.animation.canvas.as_ref().unwrap().chunks_exact(4)) { b.copy_from_slice(&c[..3]); } } Ok(duration) } /// Resets the animation to the first frame. /// /// # Panics /// /// Panics if the image is not animated. pub fn reset_animation(&mut self) { assert!(self.is_animated()); self.animation.next_frame = 0; self.animation.next_frame_start = self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8; self.animation.dispose_next_frame = true; } /// Sets the upsampling method that is used in lossy decoding pub fn set_lossy_upsampling(&mut self, upsampling_method: UpsamplingMethod) { self.webp_decode_options.lossy_upsampling = upsampling_method; } } pub(crate) fn range_reader( mut r: R, range: Range, ) -> Result { r.seek(io::SeekFrom::Start(range.start))?; Ok(r.take(range.end - range.start)) } pub(crate) fn read_fourcc(mut r: R) -> Result { let mut chunk_fourcc = [0; 4]; r.read_exact(&mut chunk_fourcc)?; Ok(WebPRiffChunk::from_fourcc(chunk_fourcc)) } pub(crate) fn read_chunk_header( mut r: R, ) -> Result<(WebPRiffChunk, u64, u64), DecodingError> { let chunk = read_fourcc(&mut r)?; let chunk_size = r.read_u32::()?; let chunk_size_rounded = chunk_size.saturating_add(chunk_size & 1); Ok((chunk, chunk_size.into(), chunk_size_rounded.into())) } #[cfg(test)] mod tests { use super::*; const RGB_BPP: usize = 3; #[test] fn add_with_overflow_size() { let bytes = vec![ 0x52, 0x49, 0x46, 0x46, 0xaf, 0x37, 0x80, 0x47, 0x57, 0x45, 0x42, 0x50, 0x6c, 0x64, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x7e, 0x73, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x40, 0xfb, 0xff, 0xff, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x49, 0x54, 0x55, 0x50, 0x4c, 0x54, 0x59, 0x50, 0x45, 0x33, 0x37, 0x44, 0x4d, 0x46, ]; let data = std::io::Cursor::new(bytes); let _ = WebPDecoder::new(data); } #[test] fn decode_2x2_single_color_image() { // Image data created from imagemagick and output of xxd: // $ convert -size 2x2 xc:#f00 red.webp // $ xxd -g 1 red.webp | head const NUM_PIXELS: usize = 2 * 2 * RGB_BPP; // 2x2 red pixel image let bytes = [ 0x52, 0x49, 0x46, 0x46, 0x3c, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50, 0x38, 0x20, 0x30, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x34, 0x25, 0xa0, 0x02, 0x74, 0xba, 0x01, 0xf8, 0x00, 0x03, 0xb0, 0x00, 0xfe, 0xf0, 0xc4, 0x0b, 0xff, 0x20, 0xb9, 0x61, 0x75, 0xc8, 0xd7, 0xff, 0x20, 0x3f, 0xe4, 0x07, 0xfc, 0x80, 0xff, 0xf8, 0xf2, 0x00, 0x00, 0x00, ]; let mut data = [0; NUM_PIXELS]; let mut decoder = WebPDecoder::new(std::io::Cursor::new(bytes)).unwrap(); decoder.read_image(&mut data).unwrap(); // All pixels are the same value let first_pixel = &data[..RGB_BPP]; assert!(data.chunks_exact(3).all(|ch| ch.iter().eq(first_pixel))); } #[test] fn decode_3x3_single_color_image() { // Test that any odd pixel "tail" is decoded properly const NUM_PIXELS: usize = 3 * 3 * RGB_BPP; // 3x3 red pixel image let bytes = [ 0x52, 0x49, 0x46, 0x46, 0x3c, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50, 0x38, 0x20, 0x30, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x34, 0x25, 0xa0, 0x02, 0x74, 0xba, 0x01, 0xf8, 0x00, 0x03, 0xb0, 0x00, 0xfe, 0xf0, 0xc4, 0x0b, 0xff, 0x20, 0xb9, 0x61, 0x75, 0xc8, 0xd7, 0xff, 0x20, 0x3f, 0xe4, 0x07, 0xfc, 0x80, 0xff, 0xf8, 0xf2, 0x00, 0x00, 0x00, ]; let mut data = [0; NUM_PIXELS]; let mut decoder = WebPDecoder::new(std::io::Cursor::new(bytes)).unwrap(); decoder.read_image(&mut data).unwrap(); // All pixels are the same value let first_pixel = &data[..RGB_BPP]; assert!(data.chunks_exact(3).all(|ch| ch.iter().eq(first_pixel))); } } image-webp-0.2.4/src/encoder.rs000064400000000000000000000635361046102023000144350ustar 00000000000000//! Encoding of WebP images. use std::collections::BinaryHeap; use std::io::{self, Write}; use std::slice::ChunksExact; use quick_error::quick_error; /// Color type of the image. /// /// Note that the WebP format doesn't have a concept of color type. All images are encoded as RGBA /// and some decoders may treat them as such. This enum is used to indicate the color type of the /// input data provided to the encoder, which can help improve compression ratio. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ColorType { /// Opaque image with a single luminance byte per pixel. L8, /// Image with a luminance and alpha byte per pixel. La8, /// Opaque image with a red, green, and blue byte per pixel. Rgb8, /// Image with a red, green, blue, and alpha byte per pixel. Rgba8, } quick_error! { /// Error that can occur during encoding. #[derive(Debug)] #[non_exhaustive] pub enum EncodingError { /// An IO error occurred. IoError(err: io::Error) { from() display("IO error: {}", err) source(err) } /// The image dimensions are not allowed by the WebP format. InvalidDimensions { display("Invalid dimensions") } } } struct BitWriter { writer: W, buffer: u64, nbits: u8, } impl BitWriter { 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.writer.write_all(&self.buffer.to_le_bytes())?; self.nbits -= 64; self.buffer = bits.checked_shr(u32::from(nbits - self.nbits)).unwrap_or(0); } 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.writer .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(w: &mut BitWriter, symbol: u8) -> io::Result<()> { w.write_bits(1, 2)?; if symbol <= 1 { w.write_bits(0, 1)?; w.write_bits(u64::from(symbol), 1)?; } else { w.write_bits(1, 1)?; w.write_bits(u64::from(symbol), 8)?; } Ok(()) } fn build_huffman_tree( 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 { 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( w: &mut BitWriter, frequencies: &[u32], lengths: &mut [u8], codes: &mut [u16], ) -> io::Result<()> { if !build_huffman_tree(frequencies, lengths, codes, 15) { let symbol = frequencies .iter() .position(|&frequency| frequency > 0) .unwrap_or(0); return write_single_entry_huffman_tree(w, 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 = !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 w.write_bits(0, 1)?; // normal huffman tree w.write_bits(19 - 4, 4)?; // num_code_lengths - 4 for i in CODE_LENGTH_ORDER { if i > 15 || code_length_frequencies[i] == 0 { w.write_bits(0, 3)?; } else if single_code_length_length { w.write_bits(1, 3)?; } else { w.write_bits(u64::from(code_length_lengths[i]), 3)?; } } match lengths.len() { 256 => { w.write_bits(1, 1)?; // max_symbol is stored w.write_bits(3, 3)?; // max_symbol_nbits / 2 - 2 w.write_bits(254, 8)?; // max_symbol - 2 } 280 => w.write_bits(0, 1)?, _ => unreachable!(), } // Write the huffman codes if !single_code_length_length { for &len in lengths.iter() { w.write_bits( u64::from(code_length_codes[len as usize]), code_length_lengths[len as usize], )?; } } Ok(()) } const fn length_to_symbol(len: u16) -> (u16, u8) { let len = len - 1; let highest_bit = len.ilog2() as u16; 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) = length_to_symbol(run_length as u16); frequencies1[256 + symbol as usize] += 1; } } } #[inline(always)] fn write_run( w: &mut BitWriter, 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; w.write_bits(u64::from(codes1[symbol]), lengths1[symbol])?; } else { let (symbol, extra_bits) = length_to_symbol(run_length as u16); w.write_bits( u64::from(codes1[256 + symbol as usize]), lengths1[256 + symbol as usize], )?; w.write_bits( (run_length as u64 - 1) & ((1 << extra_bits) - 1), extra_bits, )?; } } Ok(()) } /// Allows fine-tuning some encoder parameters. /// /// Pass to [`WebPEncoder::set_params()`]. #[non_exhaustive] #[derive(Clone, Debug)] pub struct EncoderParams { /// Use a predictor transform. Enabled by default. pub use_predictor_transform: bool, } impl Default for EncoderParams { fn default() -> Self { Self { use_predictor_transform: true, } } } /// Encode image data with the indicated color type. /// /// # Panics /// /// Panics if the image data is not of the indicated dimensions. fn encode_frame( writer: W, data: &[u8], width: u32, height: u32, color: ColorType, params: EncoderParams, ) -> Result<(), EncodingError> { let w = &mut BitWriter { writer, buffer: 0, nbits: 0, }; let (is_color, is_alpha, bytes_per_pixel) = match color { ColorType::L8 => (false, false, 1), ColorType::La8 => (false, true, 2), ColorType::Rgb8 => (true, false, 3), ColorType::Rgba8 => (true, true, 4), }; assert_eq!( (u64::from(width) * u64::from(height)).saturating_mul(bytes_per_pixel), data.len() as u64 ); if width == 0 || width > 16384 || height == 0 || height > 16384 { return Err(EncodingError::InvalidDimensions); } w.write_bits(0x2f, 8)?; // signature w.write_bits(u64::from(width) - 1, 14)?; w.write_bits(u64::from(height) - 1, 14)?; w.write_bits(u64::from(is_alpha), 1)?; // alpha used w.write_bits(0x0, 3)?; // version // subtract green transform w.write_bits(0b101, 3)?; // predictor transform if params.use_predictor_transform { w.write_bits(0b111001, 6)?; w.write_bits(0x0, 1)?; // no color cache write_single_entry_huffman_tree(w, 2)?; for _ in 0..4 { write_single_entry_huffman_tree(w, 0)?; } } // transforms done w.write_bits(0x0, 1)?; // color cache w.write_bits(0x0, 1)?; // meta-huffman codes w.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(), }; // 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 if params.use_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; 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; 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; 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; count_run(pixel, &mut it, &mut frequencies1); } } } // 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]; write_huffman_tree(w, &frequencies1, &mut lengths1, &mut codes1)?; if is_color { write_huffman_tree(w, &frequencies0, &mut lengths0, &mut codes0)?; write_huffman_tree(w, &frequencies2, &mut lengths2, &mut codes2)?; } else { write_single_entry_huffman_tree(w, 0)?; write_single_entry_huffman_tree(w, 0)?; } if is_alpha { write_huffman_tree(w, &frequencies3, &mut lengths3, &mut codes3)?; } else if params.use_predictor_transform { write_single_entry_huffman_tree(w, 0)?; } else { write_single_entry_huffman_tree(w, 255)?; } write_single_entry_huffman_tree(w, 1)?; // Write image data let mut it = pixels.chunks_exact(4).peekable(); match color { ColorType::L8 => { while let Some(pixel) = it.next() { w.write_bits( u64::from(codes1[pixel[1] as usize]), lengths1[pixel[1] as usize], )?; write_run(w, 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 = u64::from(codes1[pixel[1] as usize]) | (u64::from(codes3[pixel[3] as usize]) << len1); w.write_bits(code, len1 + len3)?; write_run(w, 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 = u64::from(codes1[pixel[1] as usize]) | (u64::from(codes0[pixel[0] as usize]) << len1) | (u64::from(codes2[pixel[2] as usize]) << (len1 + len0)); w.write_bits(code, len1 + len0 + len2)?; write_run(w, 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 = u64::from(codes1[pixel[1] as usize]) | (u64::from(codes0[pixel[0] as usize]) << len1) | (u64::from(codes2[pixel[2] as usize]) << (len1 + len0)) | (u64::from(codes3[pixel[3] as usize]) << (len1 + len0 + len2)); w.write_bits(code, len1 + len0 + len2 + len3)?; write_run(w, pixel, &mut it, &codes1, &lengths1)?; } } } w.flush()?; Ok(()) } const fn chunk_size(inner_bytes: usize) -> u32 { if inner_bytes % 2 == 1 { (inner_bytes + 1) as u32 + 8 } else { inner_bytes as u32 + 8 } } fn write_chunk(mut w: W, name: &[u8], data: &[u8]) -> io::Result<()> { debug_assert!(name.len() == 4); w.write_all(name)?; w.write_all(&(data.len() as u32).to_le_bytes())?; w.write_all(data)?; if data.len() % 2 == 1 { w.write_all(&[0])?; } Ok(()) } /// WebP Encoder. pub struct WebPEncoder { writer: W, icc_profile: Vec, exif_metadata: Vec, xmp_metadata: Vec, params: EncoderParams, } impl WebPEncoder { /// Create a new encoder that writes its output to `w`. /// /// Only supports "VP8L" lossless encoding. pub fn new(w: W) -> Self { Self { writer: w, icc_profile: Vec::new(), exif_metadata: Vec::new(), xmp_metadata: Vec::new(), params: EncoderParams::default(), } } /// Set the ICC profile to use for the image. pub fn set_icc_profile(&mut self, icc_profile: Vec) { self.icc_profile = icc_profile; } /// Set the EXIF metadata to use for the image. pub fn set_exif_metadata(&mut self, exif_metadata: Vec) { self.exif_metadata = exif_metadata; } /// Set the XMP metadata to use for the image. pub fn set_xmp_metadata(&mut self, xmp_metadata: Vec) { self.xmp_metadata = xmp_metadata; } /// Set the `EncoderParams` to use. pub fn set_params(&mut self, params: EncoderParams) { self.params = params; } /// Encode image data with the indicated color type. /// /// # Panics /// /// Panics if the image data is not of the indicated dimensions. pub fn encode( mut self, data: &[u8], width: u32, height: u32, color: ColorType, ) -> Result<(), EncodingError> { let mut frame = Vec::new(); encode_frame(&mut frame, data, width, height, color, self.params)?; // If the image has no metadata, it can be encoded with the "simple" WebP container format. if self.icc_profile.is_empty() && self.exif_metadata.is_empty() && self.xmp_metadata.is_empty() { self.writer.write_all(b"RIFF")?; self.writer .write_all(&(chunk_size(frame.len()) + 4).to_le_bytes())?; self.writer.write_all(b"WEBP")?; write_chunk(&mut self.writer, b"VP8L", &frame)?; } else { let mut total_bytes = 22 + chunk_size(frame.len()); if !self.icc_profile.is_empty() { total_bytes += chunk_size(self.icc_profile.len()); } if !self.exif_metadata.is_empty() { total_bytes += chunk_size(self.exif_metadata.len()); } if !self.xmp_metadata.is_empty() { total_bytes += chunk_size(self.xmp_metadata.len()); } let mut flags = 0; if !self.xmp_metadata.is_empty() { flags |= 1 << 2; } if !self.exif_metadata.is_empty() { flags |= 1 << 3; } if let ColorType::La8 | ColorType::Rgba8 = color { flags |= 1 << 4; } if !self.icc_profile.is_empty() { flags |= 1 << 5; } self.writer.write_all(b"RIFF")?; self.writer.write_all(&total_bytes.to_le_bytes())?; self.writer.write_all(b"WEBP")?; let mut vp8x = Vec::new(); vp8x.write_all(&[flags])?; // flags vp8x.write_all(&[0; 3])?; // reserved vp8x.write_all(&(width - 1).to_le_bytes()[..3])?; // canvas width vp8x.write_all(&(height - 1).to_le_bytes()[..3])?; // canvas height write_chunk(&mut self.writer, b"VP8X", &vp8x)?; if !self.icc_profile.is_empty() { write_chunk(&mut self.writer, b"ICCP", &self.icc_profile)?; } write_chunk(&mut self.writer, b"VP8L", &frame)?; if !self.exif_metadata.is_empty() { write_chunk(&mut self.writer, b"EXIF", &self.exif_metadata)?; } if !self.xmp_metadata.is_empty() { write_chunk(&mut self.writer, b"XMP ", &self.xmp_metadata)?; } } Ok(()) } } #[cfg(test)] mod tests { use rand::RngCore; use super::*; #[test] fn write_webp() { let mut img = vec![0; 256 * 256 * 4]; rand::thread_rng().fill_bytes(&mut img); let mut output = Vec::new(); WebPEncoder::new(&mut output) .encode(&img, 256, 256, crate::ColorType::Rgba8) .unwrap(); let mut decoder = crate::WebPDecoder::new(std::io::Cursor::new(output)).unwrap(); let mut img2 = vec![0; 256 * 256 * 4]; decoder.read_image(&mut img2).unwrap(); assert_eq!(img, img2); } #[test] fn write_webp_exif() { let mut img = vec![0; 256 * 256 * 3]; rand::thread_rng().fill_bytes(&mut img); let mut exif = vec![0; 10]; rand::thread_rng().fill_bytes(&mut exif); let mut output = Vec::new(); let mut encoder = WebPEncoder::new(&mut output); encoder.set_exif_metadata(exif.clone()); encoder .encode(&img, 256, 256, crate::ColorType::Rgb8) .unwrap(); let mut decoder = crate::WebPDecoder::new(std::io::Cursor::new(output)).unwrap(); let mut img2 = vec![0; 256 * 256 * 3]; decoder.read_image(&mut img2).unwrap(); assert_eq!(img, img2); let exif2 = decoder.exif_metadata().unwrap(); assert_eq!(Some(exif), exif2); } #[test] fn roundtrip_libwebp() { roundtrip_libwebp_params(EncoderParams::default()); roundtrip_libwebp_params(EncoderParams { use_predictor_transform: false, ..Default::default() }); } fn roundtrip_libwebp_params(params: EncoderParams) { println!("Testing {params:?}"); let mut img = vec![0; 256 * 256 * 4]; rand::thread_rng().fill_bytes(&mut img); let mut output = Vec::new(); let mut encoder = WebPEncoder::new(&mut output); encoder.set_params(params.clone()); encoder .encode(&img[..256 * 256 * 3], 256, 256, crate::ColorType::Rgb8) .unwrap(); let decoded = webp::Decoder::new(&output).decode().unwrap(); assert_eq!(img[..256 * 256 * 3], *decoded); let mut output = Vec::new(); let mut encoder = WebPEncoder::new(&mut output); encoder.set_params(params.clone()); encoder .encode(&img, 256, 256, crate::ColorType::Rgba8) .unwrap(); let decoded = webp::Decoder::new(&output).decode().unwrap(); assert_eq!(img, *decoded); let mut output = Vec::new(); let mut encoder = WebPEncoder::new(&mut output); encoder.set_params(params.clone()); encoder.set_icc_profile(vec![0; 10]); encoder .encode(&img, 256, 256, crate::ColorType::Rgba8) .unwrap(); let decoded = webp::Decoder::new(&output).decode().unwrap(); assert_eq!(img, *decoded); let mut output = Vec::new(); let mut encoder = WebPEncoder::new(&mut output); encoder.set_params(params.clone()); encoder.set_exif_metadata(vec![0; 10]); encoder .encode(&img, 256, 256, crate::ColorType::Rgba8) .unwrap(); let decoded = webp::Decoder::new(&output).decode().unwrap(); assert_eq!(img, *decoded); let mut output = Vec::new(); let mut encoder = WebPEncoder::new(&mut output); encoder.set_params(params); encoder.set_xmp_metadata(vec![0; 7]); encoder.set_icc_profile(vec![0; 8]); encoder.set_icc_profile(vec![0; 9]); encoder .encode(&img, 256, 256, crate::ColorType::Rgba8) .unwrap(); let decoded = webp::Decoder::new(&output).decode().unwrap(); assert_eq!(img, *decoded); } } image-webp-0.2.4/src/extended.rs000064400000000000000000000250031046102023000146010ustar 00000000000000use super::lossless::LosslessDecoder; use crate::decoder::DecodingError; use byteorder_lite::ReadBytesExt; use std::io::{BufRead, Read}; use crate::alpha_blending::do_alpha_blending; #[derive(Debug, Clone)] pub(crate) struct WebPExtendedInfo { pub(crate) alpha: bool, pub(crate) canvas_width: u32, pub(crate) canvas_height: u32, #[allow(unused)] pub(crate) icc_profile: bool, pub(crate) exif_metadata: bool, pub(crate) xmp_metadata: bool, pub(crate) animation: bool, pub(crate) background_color: Option<[u8; 4]>, pub(crate) background_color_hint: [u8; 4], } /// Composites a frame onto a canvas. /// /// Starts by filling the rectangle occupied by the previous frame with the background /// color, if provided. Then copies or blends the frame onto the canvas. #[allow(clippy::too_many_arguments)] pub(crate) fn composite_frame( canvas: &mut [u8], canvas_width: u32, canvas_height: u32, clear_color: Option<[u8; 4]>, frame: &[u8], frame_offset_x: u32, frame_offset_y: u32, frame_width: u32, frame_height: u32, frame_has_alpha: bool, frame_use_alpha_blending: bool, previous_frame_width: u32, previous_frame_height: u32, previous_frame_offset_x: u32, previous_frame_offset_y: u32, ) { let frame_is_full_size = frame_offset_x == 0 && frame_offset_y == 0 && frame_width == canvas_width && frame_height == canvas_height; if frame_is_full_size && !frame_use_alpha_blending { if frame_has_alpha { canvas.copy_from_slice(frame); } else { for (input, output) in frame.chunks_exact(3).zip(canvas.chunks_exact_mut(4)) { output[..3].copy_from_slice(input); output[3] = 255; } } return; } // clear rectangle occupied by previous frame if let Some(clear_color) = clear_color { match (frame_is_full_size, frame_has_alpha) { (true, true) => { for pixel in canvas.chunks_exact_mut(4) { pixel.copy_from_slice(&clear_color); } } (true, false) => { for pixel in canvas.chunks_exact_mut(3) { pixel.copy_from_slice(&clear_color[..3]); } } (false, true) => { for y in 0..previous_frame_height as usize { for x in 0..previous_frame_width as usize { let canvas_index = ((x + previous_frame_offset_x as usize) + (y + previous_frame_offset_y as usize) * canvas_width as usize) * 4; let output = &mut canvas[canvas_index..][..4]; output.copy_from_slice(&clear_color); } } } (false, false) => { for y in 0..previous_frame_height as usize { for x in 0..previous_frame_width as usize { // let frame_index = (x + y * frame_width as usize) * 4; let canvas_index = ((x + previous_frame_offset_x as usize) + (y + previous_frame_offset_y as usize) * canvas_width as usize) * 3; let output = &mut canvas[canvas_index..][..3]; output.copy_from_slice(&clear_color[..3]); } } } } } let width = frame_width.min(canvas_width.saturating_sub(frame_offset_x)) as usize; let height = frame_height.min(canvas_height.saturating_sub(frame_offset_y)) as usize; if frame_has_alpha && frame_use_alpha_blending { for y in 0..height { for x in 0..width { let frame_index = (x + y * frame_width as usize) * 4; let canvas_index = ((x + frame_offset_x as usize) + (y + frame_offset_y as usize) * canvas_width as usize) * 4; let input = &frame[frame_index..][..4]; let output = &mut canvas[canvas_index..][..4]; let blended = do_alpha_blending(input.try_into().unwrap(), output.try_into().unwrap()); output.copy_from_slice(&blended); } } } else if frame_has_alpha { for y in 0..height { let frame_index = (y * frame_width as usize) * 4; let canvas_index = (frame_offset_x as usize + (y + frame_offset_y as usize) * canvas_width as usize) * 4; canvas[canvas_index..][..width * 4].copy_from_slice(&frame[frame_index..][..width * 4]); } } else { for y in 0..height { let index = (y * frame_width as usize) * 3; let canvas_index = (frame_offset_x as usize + (y + frame_offset_y as usize) * canvas_width as usize) * 4; let input = &frame[index..][..width * 3]; let output = &mut canvas[canvas_index..][..width * 4]; for (input, output) in input.chunks_exact(3).zip(output.chunks_exact_mut(4)) { output[..3].copy_from_slice(input); output[3] = 255; } } } } pub(crate) fn get_alpha_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 read_extended_header( reader: &mut R, ) -> Result { let chunk_flags = reader.read_u8()?; 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; // reserved bytes are ignored let _reserved_bytes = read_3_bytes(reader)?; 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(DecodingError::ImageTooLarge); } let info = WebPExtendedInfo { icc_profile, alpha, exif_metadata, xmp_metadata, animation, canvas_width, canvas_height, background_color_hint: [0; 4], background_color: None, }; Ok(info) } pub(crate) fn read_3_bytes(reader: &mut R) -> Result { 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) } #[derive(Debug)] pub(crate) struct AlphaChunk { _preprocessing: bool, pub(crate) filtering_method: FilteringMethod, pub(crate) data: Vec, } #[derive(Debug, Copy, Clone)] pub(crate) enum FilteringMethod { None, Horizontal, Vertical, Gradient, } pub(crate) fn read_alpha_chunk( reader: &mut R, width: u16, height: u16, ) -> Result { let info_byte = reader.read_u8()?; let preprocessing = (info_byte & 0b00110000) >> 4; let filtering = (info_byte & 0b00001100) >> 2; let compression = info_byte & 0b00000011; let preprocessing = match preprocessing { 0 => false, 1 => true, _ => return Err(DecodingError::InvalidAlphaPreprocessing), }; 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(DecodingError::InvalidCompressionMethod), }; let data = if lossless_compression { let mut decoder = LosslessDecoder::new(reader); let mut data = vec![0; usize::from(width) * usize::from(height) * 4]; decoder.decode_frame(u32::from(width), u32::from(height), true, &mut data)?; let mut green = vec![0; usize::from(width) * usize::from(height)]; for (rgba_val, green_val) in data.chunks_exact(4).zip(green.iter_mut()) { *green_val = rgba_val[1]; } green } else { let mut framedata = vec![0; width as usize * height as usize]; reader.read_exact(&mut framedata)?; framedata }; let chunk = AlphaChunk { _preprocessing: preprocessing, filtering_method, data, }; Ok(chunk) } image-webp-0.2.4/src/huffman.rs000064400000000000000000000212241046102023000144260ustar 00000000000000//! Rudimentary utility for reading Canonical Huffman Codes. //! Based off use std::io::BufRead; use crate::decoder::DecodingError; use super::lossless::BitReader; const MAX_ALLOWED_CODE_LENGTH: usize = 15; const MAX_TABLE_BITS: u8 = 10; #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum HuffmanTreeNode { Branch(usize), //offset in vector to children Leaf(u16), //symbol stored in leaf Empty, } #[derive(Clone, Debug)] enum HuffmanTreeInner { Single(u16), Tree { tree: Vec, table: Vec, table_mask: u16, }, } /// Huffman tree #[derive(Clone, Debug)] pub(crate) struct HuffmanTree(HuffmanTreeInner); impl Default for HuffmanTree { fn default() -> Self { Self(HuffmanTreeInner::Single(0)) } } impl HuffmanTree { /// Builds a tree implicitly, just from code lengths pub(crate) fn build_implicit(code_lengths: Vec) -> Result { // Count symbols and build histogram let mut num_symbols = 0; let mut code_length_hist = [0; MAX_ALLOWED_CODE_LENGTH + 1]; for &length in code_lengths.iter().filter(|&&x| x != 0) { code_length_hist[usize::from(length)] += 1; num_symbols += 1; } // Handle special cases if num_symbols == 0 { return Err(DecodingError::HuffmanError); } else if num_symbols == 1 { let root_symbol = code_lengths.iter().position(|&x| x != 0).unwrap() as u16; return Ok(Self::build_single_node(root_symbol)); }; // Assign codes let mut curr_code = 0; let mut next_codes = [0; MAX_ALLOWED_CODE_LENGTH + 1]; let max_code_length = code_length_hist.iter().rposition(|&x| x != 0).unwrap() as u16; for code_len in 1..usize::from(max_code_length) + 1 { next_codes[code_len] = curr_code; curr_code = (curr_code + code_length_hist[code_len]) << 1; } // Confirm that the huffman tree is valid if curr_code != 2 << max_code_length { return Err(DecodingError::HuffmanError); } // Calculate table/tree parameters let table_bits = max_code_length.min(u16::from(MAX_TABLE_BITS)); let table_size = (1 << table_bits) as usize; let table_mask = table_size as u16 - 1; let tree_size = code_length_hist[table_bits as usize + 1..=max_code_length as usize] .iter() .sum::() as usize; // Populate decoding table let mut tree = Vec::with_capacity(2 * tree_size); let mut table = vec![0; table_size]; for (symbol, &length) in code_lengths.iter().enumerate() { if length == 0 { continue; } let code = next_codes[length as usize]; next_codes[length as usize] += 1; if length <= table_bits { let mut j = (u16::reverse_bits(code) >> (16 - length)) as usize; let entry = (u32::from(length) << 16) | symbol as u32; while j < table_size { table[j] = entry; j += 1 << length as usize; } } else { let table_index = ((u16::reverse_bits(code) >> (16 - length)) & table_mask) as usize; let table_value = table[table_index]; debug_assert_eq!(table_value >> 16, 0); let mut node_index = if table_value == 0 { let node_index = tree.len(); table[table_index] = (node_index + 1) as u32; tree.push(HuffmanTreeNode::Empty); node_index } else { (table_value - 1) as usize }; let code = usize::from(code); for depth in (0..length - table_bits).rev() { let node = tree[node_index]; let offset = match node { HuffmanTreeNode::Empty => { // Turns a node from empty into a branch and assigns its children let offset = tree.len() - node_index; tree[node_index] = HuffmanTreeNode::Branch(offset); tree.push(HuffmanTreeNode::Empty); tree.push(HuffmanTreeNode::Empty); offset } HuffmanTreeNode::Leaf(_) => return Err(DecodingError::HuffmanError), HuffmanTreeNode::Branch(offset) => offset, }; node_index += offset + ((code >> depth) & 1); } match tree[node_index] { HuffmanTreeNode::Empty => { tree[node_index] = HuffmanTreeNode::Leaf(symbol as u16); } HuffmanTreeNode::Leaf(_) => return Err(DecodingError::HuffmanError), HuffmanTreeNode::Branch(_offset) => return Err(DecodingError::HuffmanError), } } } Ok(Self(HuffmanTreeInner::Tree { tree, table, table_mask, })) } pub(crate) const fn build_single_node(symbol: u16) -> Self { Self(HuffmanTreeInner::Single(symbol)) } pub(crate) fn build_two_node(zero: u16, one: u16) -> Self { Self(HuffmanTreeInner::Tree { tree: vec![ HuffmanTreeNode::Leaf(zero), HuffmanTreeNode::Leaf(one), HuffmanTreeNode::Empty, ], table: vec![(1 << 16) | u32::from(zero), (1 << 16) | u32::from(one)], table_mask: 0x1, }) } pub(crate) const fn is_single_node(&self) -> bool { matches!(self.0, HuffmanTreeInner::Single(_)) } #[inline(never)] fn read_symbol_slowpath( tree: &[HuffmanTreeNode], mut v: usize, start_index: usize, bit_reader: &mut BitReader, ) -> Result { let mut depth = MAX_TABLE_BITS; let mut index = start_index; loop { match &tree[index] { HuffmanTreeNode::Branch(children_offset) => { index += children_offset + (v & 1); depth += 1; v >>= 1; } HuffmanTreeNode::Leaf(symbol) => { bit_reader.consume(depth)?; return Ok(*symbol); } HuffmanTreeNode::Empty => return Err(DecodingError::HuffmanError), } } } /// Reads a symbol using the bit reader. /// /// You must call call `bit_reader.fill()` before calling this function or it may erroroneosly /// detect the end of the stream and return a bitstream error. pub(crate) fn read_symbol( &self, bit_reader: &mut BitReader, ) -> Result { match &self.0 { HuffmanTreeInner::Tree { tree, table, table_mask, } => { let v = bit_reader.peek_full() as u16; let entry = table[(v & table_mask) as usize]; if entry >> 16 != 0 { bit_reader.consume((entry >> 16) as u8)?; return Ok(entry as u16); } Self::read_symbol_slowpath( tree, (v >> MAX_TABLE_BITS) as usize, ((entry & 0xffff) - 1) as usize, bit_reader, ) } HuffmanTreeInner::Single(symbol) => Ok(*symbol), } } /// Peek at the next symbol in the bitstream if it can be read with only a primary table lookup. /// /// Returns a tuple of the codelength and symbol value. This function may return wrong /// information if there aren't enough bits in the bit reader to read the next symbol. pub(crate) fn peek_symbol(&self, bit_reader: &BitReader) -> Option<(u8, u16)> { match &self.0 { HuffmanTreeInner::Tree { table, table_mask, .. } => { let v = bit_reader.peek_full() as u16; let entry = table[(v & table_mask) as usize]; if entry >> 16 != 0 { return Some(((entry >> 16) as u8, entry as u16)); } None } HuffmanTreeInner::Single(symbol) => Some((0, *symbol)), } } } image-webp-0.2.4/src/lib.rs000064400000000000000000000013731046102023000135530ustar 00000000000000//! Decoding and Encoding of WebP Images #![forbid(unsafe_code)] #![deny(missing_docs)] // Increase recursion limit for the `quick_error!` macro. #![recursion_limit = "256"] // Enable nightly benchmark functionality if "_benchmarks" feature is enabled. #![cfg_attr(all(test, feature = "_benchmarks"), feature(test))] #[cfg(all(test, feature = "_benchmarks"))] extern crate test; pub use self::decoder::{ DecodingError, LoopCount, UpsamplingMethod, WebPDecodeOptions, WebPDecoder, }; pub use self::encoder::{ColorType, EncoderParams, EncodingError, WebPEncoder}; mod alpha_blending; mod decoder; mod encoder; mod extended; mod huffman; mod loop_filter; mod lossless; mod lossless_transform; mod transform; mod vp8_arithmetic_decoder; mod yuv; pub mod vp8; image-webp-0.2.4/src/loop_filter.rs000064400000000000000000000320411046102023000153170ustar 00000000000000//! Does loop filtering on webp lossy images #[inline] fn c(val: i32) -> i32 { val.clamp(-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] const fn diff(val1: u8, val2: u8) -> u8 { u8::abs_diff(val1, val2) } /// Used in both the simple and normal filters described in 15.2 and 15.3 /// /// Adjusts the 2 middle pixels in a vertical loop filter fn common_adjust_vertical( 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 a = c(outer + 3 * (q0 - p0)); let b = (c(a + 3)) >> 3; let a = (c(a + 4)) >> 3; pixels[point] = s2u(q0 - a); pixels[point - stride] = s2u(p0 + b); a } /// Used in both the simple and normal filters described in 15.2 and 15.3 /// /// Adjusts the 2 middle pixels in a horizontal loop filter fn common_adjust_horizontal(use_outer_taps: bool, pixels: &mut [u8]) -> i32 { let p1 = u2s(pixels[2]); let p0 = u2s(pixels[3]); let q0 = u2s(pixels[4]); let q1 = u2s(pixels[5]); //value for the outer 2 pixels let outer = if use_outer_taps { c(p1 - q1) } else { 0 }; let a = c(outer + 3 * (q0 - p0)); let b = (c(a + 3)) >> 3; let a = (c(a + 4)) >> 3; pixels[4] = s2u(q0 - a); pixels[3] = s2u(p0 + b); a } #[inline] fn simple_threshold_vertical( 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 } #[inline] fn simple_threshold_horizontal(filter_limit: i32, pixels: &[u8]) -> bool { assert!(pixels.len() >= 6); // one bounds check up front eliminates all subsequent checks in this function i32::from(diff(pixels[3], pixels[4])) * 2 + i32::from(diff(pixels[2], pixels[5])) / 2 <= filter_limit } fn should_filter_vertical( interior_limit: u8, edge_limit: u8, pixels: &[u8], point: usize, stride: usize, ) -> bool { simple_threshold_vertical(i32::from(edge_limit), pixels, point, stride) // this looks like an erroneous way to compute differences between 8 points, but isn't: // there are actually only 6 diff comparisons required as per the spec: // https://www.rfc-editor.org/rfc/rfc6386#section-20.6 && 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 should_filter_horizontal(interior_limit: u8, edge_limit: u8, pixels: &[u8]) -> bool { assert!(pixels.len() >= 8); // one bounds check up front eliminates all subsequent checks in this function simple_threshold_horizontal(i32::from(edge_limit), pixels) // this looks like an erroneous way to compute differences between 8 points, but isn't: // there are actually only 6 diff comparisons required as per the spec: // https://www.rfc-editor.org/rfc/rfc6386#section-20.6 && diff(pixels[0], pixels[1]) <= interior_limit && diff(pixels[1], pixels[2]) <= interior_limit && diff(pixels[2], pixels[3]) <= interior_limit && diff(pixels[7], pixels[6]) <= interior_limit && diff(pixels[6], pixels[5]) <= interior_limit && diff(pixels[5], pixels[4]) <= interior_limit } #[inline] fn high_edge_variance_vertical(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 } #[inline] fn high_edge_variance_horizontal(threshold: u8, pixels: &[u8]) -> bool { diff(pixels[2], pixels[3]) > threshold || diff(pixels[5], pixels[4]) > threshold } /// Part of the simple filter described in 15.2 in the specification /// /// Affects 4 pixels on an edge(2 each side) pub(crate) fn simple_segment_vertical( edge_limit: u8, pixels: &mut [u8], point: usize, stride: usize, ) { if simple_threshold_vertical(i32::from(edge_limit), pixels, point, stride) { common_adjust_vertical(true, pixels, point, stride); } } /// Part of the simple filter described in 15.2 in the specification /// /// Affects 4 pixels on an edge(2 each side) pub(crate) fn simple_segment_horizontal(edge_limit: u8, pixels: &mut [u8]) { if simple_threshold_horizontal(i32::from(edge_limit), pixels) { common_adjust_horizontal(true, pixels); } } /// Part of the normal filter described in 15.3 in the specification /// /// Filters on the 8 pixels on the edges between subblocks inside a macroblock pub(crate) fn subblock_filter_vertical( hev_threshold: u8, interior_limit: u8, edge_limit: u8, pixels: &mut [u8], point: usize, stride: usize, ) { if should_filter_vertical(interior_limit, edge_limit, pixels, point, stride) { let hv = high_edge_variance_vertical(hev_threshold, pixels, point, stride); let a = (common_adjust_vertical(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); } } } /// Part of the normal filter described in 15.3 in the specification /// /// Filters on the 8 pixels on the edges between subblocks inside a macroblock pub(crate) fn subblock_filter_horizontal( hev_threshold: u8, interior_limit: u8, edge_limit: u8, pixels: &mut [u8], ) { if should_filter_horizontal(interior_limit, edge_limit, pixels) { let hv = high_edge_variance_horizontal(hev_threshold, pixels); let a = (common_adjust_horizontal(hv, pixels) + 1) >> 1; if !hv { pixels[5] = s2u(u2s(pixels[5]) - a); pixels[2] = s2u(u2s(pixels[2]) + a); } } } /// Part of the normal filter described in 15.3 in the specification /// /// Filters on the 8 pixels on the vertical edges between macroblocks\ /// The point passed in must be the first vertical pixel on the bottom macroblock pub(crate) fn macroblock_filter_vertical( hev_threshold: u8, interior_limit: u8, edge_limit: u8, pixels: &mut [u8], point: usize, stride: usize, ) { if should_filter_vertical(interior_limit, edge_limit, pixels, point, stride) { if !high_edge_variance_vertical(hev_threshold, pixels, point, stride) { // p0-3 are the pixels on the left macroblock from right to left let p2 = u2s(pixels[point - 3 * stride]); let p1 = u2s(pixels[point - 2 * stride]); let p0 = u2s(pixels[point - stride]); // q0-3 are the pixels on the right macroblock from left to right let q0 = u2s(pixels[point]); let q1 = u2s(pixels[point + stride]); let q2 = u2s(pixels[point + 2 * stride]); let w = c(c(p1 - q1) + 3 * (q0 - p0)); let a = c((27 * w + 63) >> 7); pixels[point] = s2u(q0 - a); pixels[point - stride] = s2u(p0 + a); let a = c((18 * w + 63) >> 7); pixels[point + stride] = s2u(q1 - a); pixels[point - 2 * stride] = s2u(p1 + a); let a = c((9 * w + 63) >> 7); pixels[point + 2 * stride] = s2u(q2 - a); pixels[point - 3 * stride] = s2u(p2 + a); } else { common_adjust_vertical(true, pixels, point, stride); } } } /// Part of the normal filter described in 15.3 in the specification /// /// Filters on the 8 pixels on the horizontal edges between macroblocks\ /// The pixels passed in must be a slice containing the 4 pixels on each macroblock pub(crate) fn macroblock_filter_horizontal( hev_threshold: u8, interior_limit: u8, edge_limit: u8, pixels: &mut [u8], ) { assert!(pixels.len() >= 8); if should_filter_horizontal(interior_limit, edge_limit, pixels) { if !high_edge_variance_horizontal(hev_threshold, pixels) { // p0-3 are the pixels on the left macroblock from right to left let p2 = u2s(pixels[1]); let p1 = u2s(pixels[2]); let p0 = u2s(pixels[3]); // q0-3 are the pixels on the right macroblock from left to right let q0 = u2s(pixels[4]); let q1 = u2s(pixels[5]); let q2 = u2s(pixels[6]); let w = c(c(p1 - q1) + 3 * (q0 - p0)); let a = c((27 * w + 63) >> 7); pixels[4] = s2u(q0 - a); pixels[3] = s2u(p0 + a); let a = c((18 * w + 63) >> 7); pixels[5] = s2u(q1 - a); pixels[2] = s2u(p1 + a); let a = c((9 * w + 63) >> 7); pixels[6] = s2u(q2 - a); pixels[1] = s2u(p2 + a); } else { common_adjust_horizontal(true, pixels); } } } #[cfg(all(test, feature = "_benchmarks"))] mod benches { use super::*; use test::{black_box, Bencher}; #[rustfmt::skip] const TEST_DATA: [u8; 8 * 8] = [ 177, 192, 179, 181, 185, 174, 186, 193, 185, 180, 175, 179, 175, 190, 189, 190, 185, 181, 177, 190, 190, 174, 176, 188, 192, 179, 186, 175, 190, 184, 190, 175, 175, 183, 183, 190, 187, 186, 176, 181, 183, 177, 182, 185, 183, 179, 178, 181, 191, 183, 188, 181, 180, 193, 185, 180, 177, 182, 177, 178, 179, 178, 191, 178, ]; #[bench] fn measure_horizontal_macroblock_filter(b: &mut Bencher) { let hev_threshold = 5; let interior_limit = 15; let edge_limit = 15; let mut data = TEST_DATA.clone(); let stride = 8; b.iter(|| { for y in 0..8 { black_box(macroblock_filter_horizontal( hev_threshold, interior_limit, edge_limit, &mut data[y * stride..][..8], )); } }); } #[bench] fn measure_vertical_macroblock_filter(b: &mut Bencher) { let hev_threshold = 5; let interior_limit = 15; let edge_limit = 15; let mut data = TEST_DATA.clone(); let stride = 8; b.iter(|| { for x in 0..8 { black_box(macroblock_filter_vertical( hev_threshold, interior_limit, edge_limit, &mut data, 4 * stride + x, stride, )); } }); } #[bench] fn measure_horizontal_subblock_filter(b: &mut Bencher) { let hev_threshold = 5; let interior_limit = 15; let edge_limit = 15; let mut data = TEST_DATA.clone(); let stride = 8; b.iter(|| { for y in 0usize..8 { black_box(subblock_filter_horizontal( hev_threshold, interior_limit, edge_limit, &mut data[y * stride..][..8], )) } }); } #[bench] fn measure_vertical_subblock_filter(b: &mut Bencher) { let hev_threshold = 5; let interior_limit = 15; let edge_limit = 15; let mut data = TEST_DATA.clone(); let stride = 8; b.iter(|| { for x in 0..8 { black_box(subblock_filter_vertical( hev_threshold, interior_limit, edge_limit, &mut data, 4 * stride + x, stride, )) } }); } #[bench] fn measure_simple_segment_horizontal_filter(b: &mut Bencher) { let edge_limit = 15; let mut data = TEST_DATA.clone(); let stride = 8; b.iter(|| { for y in 0usize..8 { black_box(simple_segment_horizontal( edge_limit, &mut data[y * stride..][..8], )) } }); } #[bench] fn measure_simple_segment_vertical_filter(b: &mut Bencher) { let edge_limit = 15; let mut data = TEST_DATA.clone(); let stride = 8; b.iter(|| { for x in 0usize..16 { black_box(simple_segment_vertical( edge_limit, &mut data, 4 * stride + x, stride, )) } }); } } image-webp-0.2.4/src/lossless.rs000064400000000000000000000714751046102023000146660ustar 00000000000000//! Decoding of lossless WebP images //! //! [Lossless spec](https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification) use std::io::BufRead; use std::mem; use crate::decoder::DecodingError; use crate::lossless_transform::{ apply_color_indexing_transform, apply_color_transform, apply_predictor_transform, apply_subtract_green_transform, }; use super::huffman::HuffmanTree; use super::lossless_transform::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() } const NUM_TRANSFORM_TYPES: usize = 4; //Decodes lossless WebP images #[derive(Debug)] pub(crate) struct LosslessDecoder { bit_reader: BitReader, transforms: [Option; NUM_TRANSFORM_TYPES], transform_order: Vec, width: u16, height: u16, } impl LosslessDecoder { /// Create a new decoder pub(crate) const fn new(r: R) -> Self { Self { bit_reader: BitReader::new(r), transforms: [None, None, None, None], transform_order: Vec::new(), width: 0, height: 0, } } /// Decodes a frame. /// /// In an alpha chunk the width and height are not included in the header, so they should be /// provided by setting the `implicit_dimensions` argument. Otherwise that argument should be /// `None` and the frame dimensions will be determined by reading the VP8L header. pub(crate) fn decode_frame( &mut self, width: u32, height: u32, implicit_dimensions: bool, buf: &mut [u8], ) -> Result<(), DecodingError> { if implicit_dimensions { self.width = width as u16; self.height = height as u16; } else { let signature = self.bit_reader.read_bits::(8)?; if signature != 0x2f { return Err(DecodingError::LosslessSignatureInvalid(signature)); } self.width = self.bit_reader.read_bits::(14)? + 1; self.height = self.bit_reader.read_bits::(14)? + 1; if u32::from(self.width) != width || u32::from(self.height) != height { return Err(DecodingError::InconsistentImageSizes); } let _alpha_used = self.bit_reader.read_bits::(1)?; let version_num = self.bit_reader.read_bits::(3)?; if version_num != 0 { return Err(DecodingError::VersionNumberInvalid(version_num)); } } let transformed_width = self.read_transforms()?; let transformed_size = usize::from(transformed_width) * usize::from(self.height) * 4; self.decode_image_stream( transformed_width, self.height, true, &mut buf[..transformed_size], )?; let mut image_size = transformed_size; let mut width = transformed_width; for &trans_index in self.transform_order.iter().rev() { let transform = self.transforms[usize::from(trans_index)].as_ref().unwrap(); match transform { TransformType::PredictorTransform { size_bits, predictor_data, } => apply_predictor_transform( &mut buf[..image_size], width, self.height, *size_bits, predictor_data, )?, TransformType::ColorTransform { size_bits, transform_data, } => { apply_color_transform( &mut buf[..image_size], width, *size_bits, transform_data, ); } TransformType::SubtractGreen => { apply_subtract_green_transform(&mut buf[..image_size]); } TransformType::ColorIndexingTransform { table_size, table_data, } => { width = self.width; image_size = usize::from(width) * usize::from(self.height) * 4; apply_color_indexing_transform( buf, width, self.height, *table_size, table_data, ); } } } Ok(()) } /// 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, data: &mut [u8], ) -> Result<(), DecodingError> { let color_cache_bits = self.read_color_cache()?; let color_cache = color_cache_bits.map(|bits| ColorCache { color_cache_bits: bits, color_cache: vec![[0; 4]; 1 << bits], }); let huffman_info = self.read_huffman_codes(is_argb_img, xsize, ysize, color_cache)?; self.decode_image_data(xsize, ysize, huffman_info, data) } /// Reads transforms and their data from the bitstream fn read_transforms(&mut self) -> Result { let mut xsize = self.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(DecodingError::TransformError); } 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.height, size_bits); let mut predictor_data = vec![0; usize::from(block_xsize) * usize::from(block_ysize) * 4]; self.decode_image_stream(block_xsize, block_ysize, false, &mut predictor_data)?; TransformType::PredictorTransform { size_bits, predictor_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.height, size_bits); let mut transform_data = vec![0; usize::from(block_xsize) * usize::from(block_ysize) * 4]; self.decode_image_stream(block_xsize, block_ysize, false, &mut transform_data)?; TransformType::ColorTransform { size_bits, transform_data, } } 2 => { //subtract green TransformType::SubtractGreen } 3 => { let color_table_size = self.bit_reader.read_bits::(8)? + 1; let mut color_map = vec![0; usize::from(color_table_size) * 4]; self.decode_image_stream(color_table_size, 1, false, &mut color_map)?; 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 [u8]) { for i in 4..color_map.len() { color_map[i] = color_map[i].wrapping_add(color_map[i - 4]); } } /// Reads huffman codes associated with an image fn read_huffman_codes( &mut self, read_meta: bool, xsize: u16, ysize: u16, color_cache: Option, ) -> Result { let mut num_huff_groups = 1u32; 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); let mut data = vec![0; usize::from(huffman_xsize) * usize::from(huffman_ysize) * 4]; self.decode_image_stream(huffman_xsize, huffman_ysize, false, &mut data)?; entropy_image = data .chunks_exact(4) .map(|pixel| { let meta_huff_code = (u16::from(pixel[0]) << 8) | u16::from(pixel[1]); if u32::from(meta_huff_code) >= num_huff_groups { num_huff_groups = u32::from(meta_huff_code) + 1; } meta_huff_code }) .collect::>(); } 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) -> Result { let simple = self.bit_reader.read_bits::(1)? == 1; if simple { let num_symbols = self.bit_reader.read_bits::(1)? + 1; let is_first_8bits = self.bit_reader.read_bits::(1)?; let zero_symbol = self.bit_reader.read_bits::(1 + 7 * is_first_8bits)?; if zero_symbol >= alphabet_size { return Err(DecodingError::BitStreamError); } if num_symbols == 1 { Ok(HuffmanTree::build_single_node(zero_symbol)) } else { let one_symbol = self.bit_reader.read_bits::(8)?; if one_symbol >= alphabet_size { return Err(DecodingError::BitStreamError); } Ok(HuffmanTree::build_two_node(zero_symbol, one_symbol)) } } 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, ) -> Result, DecodingError> { 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)?; let max_minus_two = self.bit_reader.read_bits::(length_nbits)?; if max_minus_two > num_symbols - 2 { return Err(DecodingError::BitStreamError); } 2 + max_minus_two } 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; self.bit_reader.fill()?; 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(DecodingError::BitStreamError), }; let repeat_offset = match slot { 0 | 1 => 3, 2 => 11, _ => return Err(DecodingError::BitStreamError), }; let mut repeat = self.bit_reader.read_bits::(extra_bits)? + repeat_offset; if symbol + repeat > num_symbols { return Err(DecodingError::BitStreamError); } 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, data: &mut [u8], ) -> Result<(), DecodingError> { let num_values = usize::from(width) * usize::from(height); let huff_index = huffman_info.get_huff_index(0, 0); let mut tree = &huffman_info.huffman_code_groups[huff_index]; let mut index = 0; let mut next_block_start = 0; while index < num_values { self.bit_reader.fill()?; if index >= next_block_start { let x = index % usize::from(width); let y = index / usize::from(width); next_block_start = (x | usize::from(huffman_info.mask)).min(usize::from(width - 1)) + y * usize::from(width) + 1; let huff_index = huffman_info.get_huff_index(x as u16, y as u16); tree = &huffman_info.huffman_code_groups[huff_index]; // Fast path: If all the codes each contain only a single // symbol, then the pixel data isn't written to the bitstream // and we can just fill the output buffer with the symbol // directly. if tree[..4].iter().all(|t| t.is_single_node()) { let code = tree[GREEN].read_symbol(&mut self.bit_reader)?; if code < 256 { let n = if huffman_info.bits == 0 { num_values } else { next_block_start - index }; 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)?; let value = [red as u8, code as u8, blue as u8, alpha as u8]; for i in 0..n { data[index * 4 + i * 4..][..4].copy_from_slice(&value); } if let Some(color_cache) = huffman_info.color_cache.as_mut() { color_cache.insert(value); } index += n; continue; } } } 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 green = code as u8; let red = tree[RED].read_symbol(&mut self.bit_reader)? as u8; let blue = tree[BLUE].read_symbol(&mut self.bit_reader)? as u8; if self.bit_reader.nbits < 15 { self.bit_reader.fill()?; } let alpha = tree[ALPHA].read_symbol(&mut self.bit_reader)? as u8; data[index * 4] = red; data[index * 4 + 1] = green; data[index * 4 + 2] = blue; data[index * 4 + 3] = alpha; if let Some(color_cache) = huffman_info.color_cache.as_mut() { color_cache.insert([red, green, blue, alpha]); } index += 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(DecodingError::BitStreamError); } if dist == 1 { let value: [u8; 4] = data[(index - dist) * 4..][..4].try_into().unwrap(); for i in 0..length { data[index * 4 + i * 4..][..4].copy_from_slice(&value); } } else { if index + length + 3 <= num_values { let start = (index - dist) * 4; data.copy_within(start..start + 16, index * 4); if length > 4 || dist < 4 { for i in (0..length * 4).step_by((dist * 4).min(16)).skip(1) { data.copy_within(start + i..start + i + 16, index * 4 + i); } } } else { for i in 0..length * 4 { data[index * 4 + i] = data[index * 4 + i - dist * 4]; } } if let Some(color_cache) = huffman_info.color_cache.as_mut() { for pixel in data[index * 4..][..length * 4].chunks_exact(4) { color_cache.insert(pixel.try_into().unwrap()); } } } index += length; } else { //color cache, so use previously stored pixels to get this pixel let color_cache = huffman_info .color_cache .as_mut() .ok_or(DecodingError::BitStreamError)?; let color = color_cache.lookup((code - 280).into()); data[index * 4..][..4].copy_from_slice(&color); index += 1; if index < next_block_start { if let Some((bits, code)) = tree[GREEN].peek_symbol(&self.bit_reader) { if code >= 280 { self.bit_reader.consume(bits)?; data[index * 4..][..4] .copy_from_slice(&color_cache.lookup((code - 280).into())); index += 1; } } } } } Ok(()) } /// Reads color cache data from the bitstream fn read_color_cache(&mut self) -> Result, DecodingError> { 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(DecodingError::InvalidColorCacheBits(code_bits)); } 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, ) -> Result { 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; let bits = bit_reader.peek(extra_bits) as usize; bit_reader.consume(extra_bits)?; Ok(offset + 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) * usize::from(self.xsize) + usize::from(x >> self.bits); let meta_huff_code: usize = usize::from(self.image[position]); meta_huff_code } } #[derive(Debug, Clone)] struct ColorCache { color_cache_bits: u8, color_cache: Vec<[u8; 4]>, } impl ColorCache { #[inline(always)] fn insert(&mut self, color: [u8; 4]) { let [r, g, b, a] = color; let color_u32 = (u32::from(r) << 16) | (u32::from(g) << 8) | (u32::from(b)) | (u32::from(a) << 24); let index = (0x1e35a7bdu32.wrapping_mul(color_u32)) >> (32 - self.color_cache_bits); self.color_cache[index as usize] = color; } #[inline(always)] fn lookup(&self, index: usize) -> [u8; 4] { self.color_cache[index] } } #[derive(Debug, Clone)] pub(crate) struct BitReader { reader: R, buffer: u64, nbits: u8, } impl BitReader { const fn new(reader: R) -> Self { Self { reader, buffer: 0, nbits: 0, } } /// Fills the buffer with bits from the input stream. /// /// After this function, the internal buffer will contain 64-bits or have reached the end of /// the input stream. pub(crate) fn fill(&mut self) -> Result<(), DecodingError> { debug_assert!(self.nbits < 64); let mut buf = self.reader.fill_buf()?; if buf.len() >= 8 { let lookahead = u64::from_le_bytes(buf[..8].try_into().unwrap()); self.reader.consume(usize::from((63 - self.nbits) / 8)); self.buffer |= lookahead << self.nbits; self.nbits |= 56; } else { while !buf.is_empty() && self.nbits < 56 { self.buffer |= u64::from(buf[0]) << self.nbits; self.nbits += 8; self.reader.consume(1); buf = self.reader.fill_buf()?; } } Ok(()) } /// Peeks at the next `num` bits in the buffer. pub(crate) const fn peek(&self, num: u8) -> u64 { self.buffer & ((1 << num) - 1) } /// Peeks at the full buffer. pub(crate) const fn peek_full(&self) -> u64 { self.buffer } /// Consumes `num` bits from the buffer returning an error if there are not enough bits. pub(crate) fn consume(&mut self, num: u8) -> Result<(), DecodingError> { if self.nbits < num { return Err(DecodingError::BitStreamError); } self.buffer >>= num; self.nbits -= num; Ok(()) } /// Convenience function to read a number of bits and convert them to a type. pub(crate) fn read_bits>(&mut self, num: u8) -> Result { debug_assert!(num as usize <= 8 * mem::size_of::()); debug_assert!(num <= 32); if self.nbits < num { self.fill()?; } let value = self.peek(num) as u32; self.consume(num)?; value.try_into().map_err(|_| { debug_assert!(false, "Value too large to fit in type"); DecodingError::BitStreamError }) } } #[cfg(test)] mod test { use std::io::Cursor; use super::BitReader; #[test] fn bit_read_test() { //10011100 01000001 11100001 let mut bit_reader = BitReader::new(Cursor::new(vec![0x9C, 0x41, 0xE1])); 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() { //01101010 let mut bit_reader = BitReader::new(Cursor::new(vec![0x6A])); assert_eq!(bit_reader.read_bits::(3).unwrap(), 2); //010 assert_eq!(bit_reader.read_bits::(5).unwrap(), 13); //01101 assert!(bit_reader.read_bits::(4).is_err()); //error } } image-webp-0.2.4/src/lossless_transform.rs000064400000000000000000000660321046102023000167520ustar 00000000000000use std::ops::Range; use crate::decoder::DecodingError; use super::lossless::subsample_size; #[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, }, } pub(crate) fn apply_predictor_transform( image_data: &mut [u8], width: u16, height: u16, size_bits: u8, predictor_data: &[u8], ) -> Result<(), DecodingError> { let block_xsize = usize::from(subsample_size(width, size_bits)); let width = usize::from(width); let height = usize::from(height); // Handle top and left borders specially. This involves ignoring mode and using specific // predictors for each. image_data[3] = image_data[3].wrapping_add(255); apply_predictor_transform_1(image_data, 4..width * 4, width); for y in 1..height { for i in 0..4 { image_data[y * width * 4 + i] = image_data[y * width * 4 + i].wrapping_add(image_data[(y - 1) * width * 4 + i]); } } for y in 1..height { for block_x in 0..block_xsize { let block_index = (y >> size_bits) * block_xsize + block_x; let predictor = predictor_data[block_index * 4 + 1]; let start_index = (y * width + (block_x << size_bits).max(1)) * 4; let end_index = (y * width + ((block_x + 1) << size_bits).min(width)) * 4; match predictor { 0 => apply_predictor_transform_0(image_data, start_index..end_index, width), 1 => apply_predictor_transform_1(image_data, start_index..end_index, width), 2 => apply_predictor_transform_2(image_data, start_index..end_index, width), 3 => apply_predictor_transform_3(image_data, start_index..end_index, width), 4 => apply_predictor_transform_4(image_data, start_index..end_index, width), 5 => apply_predictor_transform_5(image_data, start_index..end_index, width), 6 => apply_predictor_transform_6(image_data, start_index..end_index, width), 7 => apply_predictor_transform_7(image_data, start_index..end_index, width), 8 => apply_predictor_transform_8(image_data, start_index..end_index, width), 9 => apply_predictor_transform_9(image_data, start_index..end_index, width), 10 => apply_predictor_transform_10(image_data, start_index..end_index, width), 11 => apply_predictor_transform_11(image_data, start_index..end_index, width), 12 => apply_predictor_transform_12(image_data, start_index..end_index, width), 13 => apply_predictor_transform_13(image_data, start_index..end_index, width), _ => {} } } } Ok(()) } pub fn apply_predictor_transform_0(image_data: &mut [u8], range: Range, _width: usize) { assert!(range.end <= image_data.len()); let mut i = range.start + 3; while i < range.end { image_data[i] = image_data[i].wrapping_add(0xff); i += 4; } } pub fn apply_predictor_transform_1(image_data: &mut [u8], range: Range, _width: usize) { assert!(range.end <= image_data.len()); let mut i = range.start; while i < range.end { image_data[i] = image_data[i].wrapping_add(image_data[i - 4]); i += 1; } } pub fn apply_predictor_transform_2(image_data: &mut [u8], range: Range, width: usize) { assert!(range.end <= image_data.len()); let mut i = range.start; while i < range.end { image_data[i] = image_data[i].wrapping_add(image_data[i - width * 4]); i += 1; } } pub fn apply_predictor_transform_3(image_data: &mut [u8], range: Range, width: usize) { assert!(range.end <= image_data.len()); let mut i = range.start; while i < range.end { image_data[i] = image_data[i].wrapping_add(image_data[i - width * 4 + 4]); i += 1; } } pub fn apply_predictor_transform_4(image_data: &mut [u8], range: Range, width: usize) { assert!(range.end <= image_data.len()); let mut i = range.start; while i < range.end { image_data[i] = image_data[i].wrapping_add(image_data[i - width * 4 - 4]); i += 1; } } pub fn apply_predictor_transform_5(image_data: &mut [u8], range: Range, width: usize) { let (old, current) = image_data[..range.end].split_at_mut(range.start); let mut prev: [u8; 4] = old[range.start - 4..][..4].try_into().unwrap(); let top_right = &old[range.start - width * 4 + 4..]; let top = &old[range.start - width * 4..]; for ((chunk, tr), t) in current .chunks_exact_mut(4) .zip(top_right.chunks_exact(4)) .zip(top.chunks_exact(4)) { prev = [ chunk[0].wrapping_add(average2_autovec(average2_autovec(prev[0], tr[0]), t[0])), chunk[1].wrapping_add(average2_autovec(average2_autovec(prev[1], tr[1]), t[1])), chunk[2].wrapping_add(average2_autovec(average2_autovec(prev[2], tr[2]), t[2])), chunk[3].wrapping_add(average2_autovec(average2_autovec(prev[3], tr[3]), t[3])), ]; chunk.copy_from_slice(&prev); } } pub fn apply_predictor_transform_6(image_data: &mut [u8], range: Range, width: usize) { assert!(range.end <= image_data.len()); let mut i = range.start; while i < range.end { image_data[i] = image_data[i].wrapping_add(average2(image_data[i - 4], image_data[i - width * 4 - 4])); i += 1; } } pub fn apply_predictor_transform_7(image_data: &mut [u8], range: Range, width: usize) { let (old, current) = image_data[..range.end].split_at_mut(range.start); let mut prev: [u8; 4] = old[range.start - 4..][..4].try_into().unwrap(); let top = &old[range.start - width * 4..][..(range.end - range.start)]; let mut current_chunks = current.chunks_exact_mut(64); let mut top_chunks = top.chunks_exact(64); for (current, top) in (&mut current_chunks).zip(&mut top_chunks) { for (chunk, t) in current.chunks_exact_mut(4).zip(top.chunks_exact(4)) { prev = [ chunk[0].wrapping_add(average2_autovec(prev[0], t[0])), chunk[1].wrapping_add(average2_autovec(prev[1], t[1])), chunk[2].wrapping_add(average2_autovec(prev[2], t[2])), chunk[3].wrapping_add(average2_autovec(prev[3], t[3])), ]; chunk.copy_from_slice(&prev); } } for (chunk, t) in current_chunks .into_remainder() .chunks_exact_mut(4) .zip(top_chunks.remainder().chunks_exact(4)) { prev = [ chunk[0].wrapping_add(average2_autovec(prev[0], t[0])), chunk[1].wrapping_add(average2_autovec(prev[1], t[1])), chunk[2].wrapping_add(average2_autovec(prev[2], t[2])), chunk[3].wrapping_add(average2_autovec(prev[3], t[3])), ]; chunk.copy_from_slice(&prev); } } pub fn apply_predictor_transform_8(image_data: &mut [u8], range: Range, width: usize) { assert!(range.end <= image_data.len()); let mut i = range.start; while i < range.end { image_data[i] = image_data[i].wrapping_add(average2( image_data[i - width * 4 - 4], image_data[i - width * 4], )); i += 1; } } pub fn apply_predictor_transform_9(image_data: &mut [u8], range: Range, width: usize) { assert!(range.end <= image_data.len()); let mut i = range.start; while i < range.end { image_data[i] = image_data[i].wrapping_add(average2( image_data[i - width * 4], image_data[i - width * 4 + 4], )); i += 1; } } pub fn apply_predictor_transform_10(image_data: &mut [u8], range: Range, width: usize) { let (old, current) = image_data[..range.end].split_at_mut(range.start); let mut prev: [u8; 4] = old[range.start - 4..][..4].try_into().unwrap(); let top_left = &old[range.start - width * 4 - 4..]; let top = &old[range.start - width * 4..]; let top_right = &old[range.start - width * 4 + 4..]; for (((chunk, tl), t), tr) in current .chunks_exact_mut(4) .zip(top_left.chunks_exact(4)) .zip(top.chunks_exact(4)) .zip(top_right.chunks_exact(4)) { prev = [ chunk[0].wrapping_add(average2(average2(prev[0], tl[0]), average2(t[0], tr[0]))), chunk[1].wrapping_add(average2(average2(prev[1], tl[1]), average2(t[1], tr[1]))), chunk[2].wrapping_add(average2(average2(prev[2], tl[2]), average2(t[2], tr[2]))), chunk[3].wrapping_add(average2(average2(prev[3], tl[3]), average2(t[3], tr[3]))), ]; chunk.copy_from_slice(&prev); } } pub fn apply_predictor_transform_11(image_data: &mut [u8], range: Range, width: usize) { let (old, current) = image_data[..range.end].split_at_mut(range.start); let top = &old[range.start - width * 4..]; let mut l = [ i16::from(old[range.start - 4]), i16::from(old[range.start - 3]), i16::from(old[range.start - 2]), i16::from(old[range.start - 1]), ]; let mut tl = [ i16::from(old[range.start - width * 4 - 4]), i16::from(old[range.start - width * 4 - 3]), i16::from(old[range.start - width * 4 - 2]), i16::from(old[range.start - width * 4 - 1]), ]; for (chunk, top) in current.chunks_exact_mut(4).zip(top.chunks_exact(4)) { let t = [ i16::from(top[0]), i16::from(top[1]), i16::from(top[2]), i16::from(top[3]), ]; let mut predict_left = 0; let mut predict_top = 0; for i in 0..4 { let predict = l[i] + t[i] - tl[i]; predict_left += i16::abs(predict - l[i]); predict_top += i16::abs(predict - t[i]); } if predict_left < predict_top { chunk.copy_from_slice(&[ chunk[0].wrapping_add(l[0] as u8), chunk[1].wrapping_add(l[1] as u8), chunk[2].wrapping_add(l[2] as u8), chunk[3].wrapping_add(l[3] as u8), ]); } else { chunk.copy_from_slice(&[ chunk[0].wrapping_add(t[0] as u8), chunk[1].wrapping_add(t[1] as u8), chunk[2].wrapping_add(t[2] as u8), chunk[3].wrapping_add(t[3] as u8), ]); } tl = t; l = [ i16::from(chunk[0]), i16::from(chunk[1]), i16::from(chunk[2]), i16::from(chunk[3]), ]; } } pub fn apply_predictor_transform_12(image_data: &mut [u8], range: Range, width: usize) { let (old, current) = image_data[..range.end].split_at_mut(range.start); let mut prev: [u8; 4] = old[range.start - 4..][..4].try_into().unwrap(); let top_left = &old[range.start - width * 4 - 4..]; let top = &old[range.start - width * 4..]; for ((chunk, tl), t) in current .chunks_exact_mut(4) .zip(top_left.chunks_exact(4)) .zip(top.chunks_exact(4)) { prev = [ chunk[0].wrapping_add(clamp_add_subtract_full( i16::from(prev[0]), i16::from(t[0]), i16::from(tl[0]), )), chunk[1].wrapping_add(clamp_add_subtract_full( i16::from(prev[1]), i16::from(t[1]), i16::from(tl[1]), )), chunk[2].wrapping_add(clamp_add_subtract_full( i16::from(prev[2]), i16::from(t[2]), i16::from(tl[2]), )), chunk[3].wrapping_add(clamp_add_subtract_full( i16::from(prev[3]), i16::from(t[3]), i16::from(tl[3]), )), ]; chunk.copy_from_slice(&prev); } } pub fn apply_predictor_transform_13(image_data: &mut [u8], range: Range, width: usize) { let (old, current) = image_data[..range.end].split_at_mut(range.start); let mut prev: [u8; 4] = old[range.start - 4..][..4].try_into().unwrap(); let top_left = &old[range.start - width * 4 - 4..][..(range.end - range.start)]; let top = &old[range.start - width * 4..][..(range.end - range.start)]; for ((chunk, tl), t) in current .chunks_exact_mut(4) .zip(top_left.chunks_exact(4)) .zip(top.chunks_exact(4)) { prev = [ chunk[0].wrapping_add(clamp_add_subtract_half( (i16::from(prev[0]) + i16::from(t[0])) / 2, i16::from(tl[0]), )), chunk[1].wrapping_add(clamp_add_subtract_half( (i16::from(prev[1]) + i16::from(t[1])) / 2, i16::from(tl[1]), )), chunk[2].wrapping_add(clamp_add_subtract_half( (i16::from(prev[2]) + i16::from(t[2])) / 2, i16::from(tl[2]), )), chunk[3].wrapping_add(clamp_add_subtract_half( (i16::from(prev[3]) + i16::from(t[3])) / 2, i16::from(tl[3]), )), ]; chunk.copy_from_slice(&prev); } } pub(crate) fn apply_color_transform( image_data: &mut [u8], width: u16, size_bits: u8, transform_data: &[u8], ) { let block_xsize = usize::from(subsample_size(width, size_bits)); let width = usize::from(width); for (y, row) in image_data.chunks_exact_mut(width * 4).enumerate() { let row_transform_data_start = (y >> size_bits) * block_xsize * 4; // the length of block_tf_data should be `block_xsize * 4`, so we could slice it with [..block_xsize * 4] // but there is no point - `.zip()` runs until either of the iterators is consumed, // so the extra slicing operation would be doing more work for no reason let row_tf_data = &transform_data[row_transform_data_start..]; for (block, transform) in row .chunks_mut(4 << size_bits) .zip(row_tf_data.chunks_exact(4)) { let red_to_blue = transform[0]; let green_to_blue = transform[1]; let green_to_red = transform[2]; for pixel in block.chunks_exact_mut(4) { let green = u32::from(pixel[1]); let mut temp_red = u32::from(pixel[0]); let mut temp_blue = u32::from(pixel[2]); temp_red += color_transform_delta(green_to_red as i8, green as i8); temp_blue += color_transform_delta(green_to_blue as i8, green as i8); temp_blue += color_transform_delta(red_to_blue as i8, temp_red as i8); pixel[0] = (temp_red & 0xff) as u8; pixel[2] = (temp_blue & 0xff) as u8; } } } } pub(crate) fn apply_subtract_green_transform(image_data: &mut [u8]) { for pixel in image_data.chunks_exact_mut(4) { pixel[0] = pixel[0].wrapping_add(pixel[1]); pixel[2] = pixel[2].wrapping_add(pixel[1]); } } pub(crate) fn apply_color_indexing_transform( image_data: &mut [u8], width: u16, height: u16, table_size: u16, table_data: &[u8], ) { assert!(table_size > 0); if table_size > 16 { // convert the table of colors into a Vec of color values that can be directly indexed let mut table: Vec<[u8; 4]> = table_data .chunks_exact(4) // convince the compiler that each chunk is 4 bytes long, important for optimizations in the loop below .map(|c| TryInto::<[u8; 4]>::try_into(c).unwrap()) .collect(); // pad the table to 256 values if it's smaller than that so we could index into it by u8 without bounds checks // also required for correctness: WebP spec requires out-of-bounds indices to be treated as [0,0,0,0] table.resize(256, [0; 4]); // convince the compiler that the length of the table is 256 to avoid bounds checks in the loop below let table: &[[u8; 4]; 256] = table.as_slice().try_into().unwrap(); for pixel in image_data.chunks_exact_mut(4) { // Index is in G channel. // WebP format encodes ARGB pixels, but we permute to RGBA immediately after reading from the bitstream. pixel.copy_from_slice(&table[pixel[1] as usize]); } } else { // table_size_u16 is 1 to 16 let table_size = table_size as u8; // Dispatch to specialized implementation for each table size band for performance. // Otherwise the compiler doesn't know the size of our copies // and ends up calling out to memmove for every pixel even though a single load is sufficient. if table_size <= 2 { // Max 2 colors, 1 bit per pixel index -> W_BITS = 3 const W_BITS_VAL: u8 = 3; // EXP_ENTRY_SIZE is 4 bytes/pixel * (1 << W_BITS_VAL) pixels/entry const EXP_ENTRY_SIZE_VAL: usize = 4 * (1 << W_BITS_VAL); // 4 * 8 = 32 apply_color_indexing_transform_small_table::( image_data, width, height, table_size, table_data, ); } else if table_size <= 4 { // Max 4 colors, 2 bits per pixel index -> W_BITS = 2 const W_BITS_VAL: u8 = 2; const EXP_ENTRY_SIZE_VAL: usize = 4 * (1 << W_BITS_VAL); // 4 * 4 = 16 apply_color_indexing_transform_small_table::( image_data, width, height, table_size, table_data, ); } else { // Max 16 colors (5 to 16), 4 bits per pixel index -> W_BITS = 1 // table_size_u16 must be <= 16 here const W_BITS_VAL: u8 = 1; const EXP_ENTRY_SIZE_VAL: usize = 4 * (1 << W_BITS_VAL); // 4 * 2 = 8 apply_color_indexing_transform_small_table::( image_data, width, height, table_size, table_data, ); } } } // Helper function with const generics for W_BITS and EXP_ENTRY_SIZE fn apply_color_indexing_transform_small_table( image_data: &mut [u8], width: u16, height: u16, table_size: u8, // Max 16 table_data: &[u8], ) { // As of Rust 1.87 we cannot use `const` here. The compiler can still optimize them heavily // because W_BITS is a const generic for each instantiation of this function. let pixels_per_packed_byte_u8: u8 = 1 << W_BITS; let bits_per_entry_u8: u8 = 8 / pixels_per_packed_byte_u8; let mask_u8: u8 = (1 << bits_per_entry_u8) - 1; // This is also effectively a compile-time constant for each instantiation. let pixels_per_packed_byte_usize: usize = pixels_per_packed_byte_u8 as usize; // Verify that the passed EXP_ENTRY_SIZE matches our calculation based on W_BITS, just as a sanity check. debug_assert_eq!( EXP_ENTRY_SIZE, 4 * pixels_per_packed_byte_usize, "Mismatch in EXP_ENTRY_SIZE" ); // Precompute the full lookup table. // Each of the 256 possible packed byte values maps to an array of RGBA pixels. // The array type uses the const generic EXP_ENTRY_SIZE. let expanded_lookup_table_storage: Vec<[u8; EXP_ENTRY_SIZE]> = (0..256u16) .map(|packed_byte_value_u16| { let mut entry_pixels_array = [0u8; EXP_ENTRY_SIZE]; // Uses const generic let packed_byte_value = packed_byte_value_u16 as u8; // Loop bound is effectively constant for each instantiation. for pixel_sub_index in 0..pixels_per_packed_byte_usize { let shift_amount = (pixel_sub_index as u8) * bits_per_entry_u8; let k = (packed_byte_value >> shift_amount) & mask_u8; let color_source_array: [u8; 4] = if k < table_size { let color_data_offset = usize::from(k) * 4; table_data[color_data_offset..color_data_offset + 4] .try_into() .unwrap() } else { [0u8; 4] // WebP spec: out-of-bounds indices are [0,0,0,0] }; let array_fill_offset = pixel_sub_index * 4; entry_pixels_array[array_fill_offset..array_fill_offset + 4] .copy_from_slice(&color_source_array); } entry_pixels_array }) .collect(); let expanded_lookup_table_array: &[[u8; EXP_ENTRY_SIZE]; 256] = expanded_lookup_table_storage.as_slice().try_into().unwrap(); let packed_image_width_in_blocks = width.div_ceil(pixels_per_packed_byte_u8.into()) as usize; if width == 0 || height == 0 { return; } let final_block_expanded_size_bytes = (width as usize * 4) - EXP_ENTRY_SIZE * (packed_image_width_in_blocks.saturating_sub(1)); let input_stride_bytes_packed = packed_image_width_in_blocks * 4; let output_stride_bytes_expanded = width as usize * 4; let mut packed_indices_for_row: Vec = vec![0; packed_image_width_in_blocks]; for y_rev_idx in 0..height as usize { let y = height as usize - 1 - y_rev_idx; let packed_row_input_global_offset = y * input_stride_bytes_packed; let packed_argb_row_slice = &image_data[packed_row_input_global_offset..][..input_stride_bytes_packed]; for (packed_argb_chunk, packed_idx) in packed_argb_row_slice .chunks_exact(4) .zip(packed_indices_for_row.iter_mut()) { *packed_idx = packed_argb_chunk[1]; } let output_row_global_offset = y * output_stride_bytes_expanded; let output_row_slice_mut = &mut image_data[output_row_global_offset..][..output_stride_bytes_expanded]; let num_full_blocks = packed_image_width_in_blocks.saturating_sub(1); let (full_blocks_part, final_block_part) = output_row_slice_mut.split_at_mut(num_full_blocks * EXP_ENTRY_SIZE); for (output_chunk_slice, &packed_index_byte) in full_blocks_part .chunks_exact_mut(EXP_ENTRY_SIZE) // Uses const generic to avoid expensive memmove call .zip(packed_indices_for_row.iter()) { let output_chunk_array: &mut [u8; EXP_ENTRY_SIZE] = output_chunk_slice.try_into().unwrap(); let colors_data_array = &expanded_lookup_table_array[packed_index_byte as usize]; *output_chunk_array = *colors_data_array; } if packed_image_width_in_blocks > 0 { let final_packed_index_byte = packed_indices_for_row[packed_image_width_in_blocks - 1]; let colors_data_full_array = &expanded_lookup_table_array[final_packed_index_byte as usize]; final_block_part .copy_from_slice(&colors_data_full_array[..final_block_expanded_size_bytes]); } } } //predictor functions /// Get average of 2 bytes fn average2(a: u8, b: u8) -> u8 { ((u16::from(a) + u16::from(b)) / 2) as u8 } /// Get average of 2 bytes, allows some predictors to be autovectorized by /// keeping computation within lanes of `u8`. /// /// LLVM is capable of optimizing `average2` into this but not in all cases. fn average2_autovec(a: u8, b: u8) -> u8 { (a & b) + ((a ^ b) >> 1) } /// Clamp add subtract full on one part fn clamp_add_subtract_full(a: i16, b: i16, c: i16) -> u8 { // Clippy suggests the clamp method, but it seems to optimize worse as of rustc 1.82.0 nightly. #![allow(clippy::manual_clamp)] (a + b - c).max(0).min(255) as u8 } /// Clamp add subtract half on one part fn clamp_add_subtract_half(a: i16, b: i16) -> u8 { // Clippy suggests the clamp method, but it seems to optimize worse as of rustc 1.82.0 nightly. #![allow(clippy::manual_clamp)] (a + (a - b) / 2).max(0).min(255) as u8 } /// Does color transform on 2 numbers fn color_transform_delta(t: i8, c: i8) -> u32 { (i32::from(t) * i32::from(c)) as u32 >> 5 } #[cfg(all(test, feature = "_benchmarks"))] mod benches { use rand::Rng; use test::{black_box, Bencher}; fn measure_predictor(b: &mut Bencher, predictor: fn(&mut [u8], std::ops::Range, usize)) { let width = 256; let mut data = vec![0u8; width * 8]; rand::thread_rng().fill(&mut data[..]); b.bytes = 4 * width as u64 - 4; b.iter(|| { predictor( black_box(&mut data), black_box(width * 4 + 4..width * 8), black_box(width), ) }); } #[bench] fn predictor00(b: &mut Bencher) { measure_predictor(b, super::apply_predictor_transform_0); } #[bench] fn predictor01(b: &mut Bencher) { measure_predictor(b, super::apply_predictor_transform_1); } #[bench] fn predictor02(b: &mut Bencher) { measure_predictor(b, super::apply_predictor_transform_2); } #[bench] fn predictor03(b: &mut Bencher) { measure_predictor(b, super::apply_predictor_transform_3); } #[bench] fn predictor04(b: &mut Bencher) { measure_predictor(b, super::apply_predictor_transform_4); } #[bench] fn predictor05(b: &mut Bencher) { measure_predictor(b, super::apply_predictor_transform_5); } #[bench] fn predictor06(b: &mut Bencher) { measure_predictor(b, super::apply_predictor_transform_6); } #[bench] fn predictor07(b: &mut Bencher) { measure_predictor(b, super::apply_predictor_transform_7); } #[bench] fn predictor08(b: &mut Bencher) { measure_predictor(b, super::apply_predictor_transform_8); } #[bench] fn predictor09(b: &mut Bencher) { measure_predictor(b, super::apply_predictor_transform_9); } #[bench] fn predictor10(b: &mut Bencher) { measure_predictor(b, super::apply_predictor_transform_10); } #[bench] fn predictor11(b: &mut Bencher) { measure_predictor(b, super::apply_predictor_transform_11); } #[bench] fn predictor12(b: &mut Bencher) { measure_predictor(b, super::apply_predictor_transform_12); } #[bench] fn predictor13(b: &mut Bencher) { measure_predictor(b, super::apply_predictor_transform_13); } #[bench] fn color_transform(b: &mut Bencher) { let width = 256; let height = 256; let size_bits = 3; let mut data = vec![0u8; width * height * 4]; let mut transform_data = vec![0u8; (width * height * 4) >> (size_bits * 2)]; rand::thread_rng().fill(&mut data[..]); rand::thread_rng().fill(&mut transform_data[..]); b.bytes = 4 * width as u64 * height as u64; b.iter(|| { super::apply_color_transform( black_box(&mut data), black_box(width as u16), black_box(size_bits), black_box(&transform_data), ); }); } #[bench] fn subtract_green(b: &mut Bencher) { let mut data = vec![0u8; 1024 * 4]; rand::thread_rng().fill(&mut data[..]); b.bytes = data.len() as u64; b.iter(|| { super::apply_subtract_green_transform(black_box(&mut data)); }); } } image-webp-0.2.4/src/mod.rs000064400000000000000000000010601046102023000135550ustar 00000000000000//! Decoding and Encoding of WebP Images #[cfg(feature = "webp-encoder")] pub use self::encoder::{WebPEncoder, WebPQuality}; #[cfg(feature = "webp-encoder")] mod encoder; #[cfg(feature = "webp")] pub use self::decoder::WebPDecoder; #[cfg(feature = "webp")] mod decoder; #[cfg(feature = "webp")] mod extended; #[cfg(feature = "webp")] mod huffman; #[cfg(feature = "webp")] mod loop_filter; #[cfg(feature = "webp")] mod lossless; #[cfg(feature = "webp")] mod lossless_transform; #[cfg(feature = "webp")] mod transform; #[cfg(feature = "webp")] pub mod vp8; image-webp-0.2.4/src/transform.rs000064400000000000000000000052351046102023000150210ustar 00000000000000static CONST1: i64 = 20091; static CONST2: i64 = 35468; pub(crate) fn idct4x4(block: &mut [i32]) { // The intermediate results may overflow the types, so we stretch the type. fn fetch(block: &[i32], idx: usize) -> i64 { i64::from(block[idx]) } // Perform one lenght check up front to avoid subsequent bounds checks in this function assert!(block.len() >= 16); 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]) { // Perform one length check up front to avoid subsequent bounds checks in this function assert!(block.len() >= 16); 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 block in block.chunks_exact_mut(4) { let a1 = block[0] + block[3]; let b1 = block[1] + block[2]; let c1 = block[1] - block[2]; let d1 = block[0] - block[3]; let a2 = a1 + b1; let b2 = c1 + d1; let c2 = a1 - b1; let d2 = d1 - c1; block[0] = (a2 + 3) >> 3; block[1] = (b2 + 3) >> 3; block[2] = (c2 + 3) >> 3; block[3] = (d2 + 3) >> 3; } } image-webp-0.2.4/src/vp8.rs000064400000000000000000002752641046102023000135360ustar 00000000000000//! An implementation of the VP8 Video Codec //! //! This module contains a partial implementation of the //! VP8 video format as defined in RFC-6386. //! //! It decodes Keyframes only. //! VP8 is the underpinning of the WebP image format //! //! # Related Links //! * [rfc-6386](http://tools.ietf.org/html/rfc6386) - The VP8 Data Format and Decoding Guide //! * [VP8.pdf](http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/37073.pdf) - An overview of of the VP8 format use byteorder_lite::{LittleEndian, ReadBytesExt}; use std::default::Default; use std::io::Read; use crate::decoder::{DecodingError, UpsamplingMethod}; use crate::yuv; use super::vp8_arithmetic_decoder::ArithmeticDecoder; use super::{loop_filter, transform}; 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; #[derive(Clone, Copy)] pub(crate) struct TreeNode { pub left: u8, pub right: u8, pub prob: Prob, pub index: u8, } impl TreeNode { const UNINIT: TreeNode = TreeNode { left: 0, right: 0, prob: 0, index: 0, }; const fn prepare_branch(t: i8) -> u8 { if t > 0 { (t as u8) / 2 } else { let value = -t; 0x80 | (value as u8) } } pub(crate) const fn value_from_branch(t: u8) -> i8 { (t & !0x80) as i8 } } const fn tree_nodes_from( tree: [i8; N], probs: [Prob; M], ) -> [TreeNode; M] { if N != 2 * M { panic!("invalid tree with probs"); } let mut nodes = [TreeNode::UNINIT; M]; let mut i = 0; while i < M { nodes[i].left = TreeNode::prepare_branch(tree[2 * i]); nodes[i].right = TreeNode::prepare_branch(tree[2 * i + 1]); nodes[i].prob = probs[i]; nodes[i].index = i as u8; i += 1; } nodes } const SEGMENT_ID_TREE: [i8; 6] = [2, 4, -0, -1, -2, -3]; const SEGMENT_TREE_NODE_DEFAULTS: [TreeNode; 3] = tree_nodes_from(SEGMENT_ID_TREE, [255; 3]); // Section 11.2 // Tree for determining the keyframe luma intra prediction modes: const 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 const KEYFRAME_YMODE_PROBS: [Prob; 4] = [145, 156, 163, 128]; const KEYFRAME_YMODE_NODES: [TreeNode; 4] = tree_nodes_from(KEYFRAME_YMODE_TREE, KEYFRAME_YMODE_PROBS); // Tree for determining the keyframe B_PRED mode: const 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 const KEYFRAME_BPRED_MODE_PROBS: [[[Prob; 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], ], ]; const KEYFRAME_BPRED_MODE_NODES: [[[TreeNode; 9]; 10]; 10] = { let mut output = [[[TreeNode::UNINIT; 9]; 10]; 10]; let mut i = 0; while i < output.len() { let mut j = 0; while j < output[i].len() { output[i][j] = tree_nodes_from(KEYFRAME_BPRED_MODE_TREE, KEYFRAME_BPRED_MODE_PROBS[i][j]); j += 1; } i += 1; } output }; // Section 11.4 Tree for determining macroblock the chroma mode const KEYFRAME_UV_MODE_TREE: [i8; 6] = [-DC_PRED, 2, -V_PRED, 4, -H_PRED, -TM_PRED]; // Probabilities for determining macroblock mode const KEYFRAME_UV_MODE_PROBS: [Prob; 3] = [142, 114, 183]; const KEYFRAME_UV_MODE_NODES: [TreeNode; 3] = tree_nodes_from(KEYFRAME_UV_MODE_TREE, KEYFRAME_UV_MODE_PROBS); // Section 13.4 type TokenProbTables = [[[[Prob; NUM_DCT_TOKENS - 1]; 3]; 8]; 4]; type TokenProbTreeNodes = [[[[TreeNode; NUM_DCT_TOKENS - 1]; 3]; 8]; 4]; // Probabilities that a token's probability will be updated const 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 const 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], ], ], ]; const COEFF_PROB_NODES: TokenProbTreeNodes = { let mut output = [[[[TreeNode::UNINIT; 11]; 3]; 8]; 4]; let mut i = 0; while i < output.len() { let mut j = 0; while j < output[i].len() { let mut k = 0; while k < output[i][j].len() { output[i][j][k] = tree_nodes_from(DCT_TOKEN_TREE, COEFF_PROBS[i][j][k]); k += 1; } j += 1; } i += 1; } output }; // 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; const 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, ]; const 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], ]; const DCT_CAT_BASE: [u8; 6] = [5, 7, 11, 19, 35, 67]; const COEFF_BANDS: [u8; 16] = [0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7]; #[rustfmt::skip] const 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] const 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, ]; const ZIGZAG: [u8; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15]; #[derive(Default, Clone, Copy)] struct MacroBlock { bpred: [IntraMode; 16], complexity: [u8; 9], luma_mode: LumaMode, chroma_mode: ChromaMode, segmentid: u8, coeffs_skipped: bool, non_zero_dct: 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 { const fn chroma_width(&self) -> u16 { self.width.div_ceil(2) } const fn buffer_width(&self) -> u16 { let difference = self.width % 16; if difference > 0 { self.width + (16 - difference % 16) } else { self.width } } /// Fills an rgb buffer from the YUV buffers pub(crate) fn fill_rgb(&self, buf: &mut [u8], upsampling_method: UpsamplingMethod) { const BPP: usize = 3; match upsampling_method { UpsamplingMethod::Bilinear => { yuv::fill_rgb_buffer_fancy::( buf, &self.ybuf, &self.ubuf, &self.vbuf, usize::from(self.width), usize::from(self.height), usize::from(self.buffer_width()), ); } UpsamplingMethod::Simple => { yuv::fill_rgb_buffer_simple::( buf, &self.ybuf, &self.ubuf, &self.vbuf, usize::from(self.width), usize::from(self.chroma_width()), usize::from(self.buffer_width()), ); } } } /// Fills an rgba buffer from the YUV buffers pub(crate) fn fill_rgba(&self, buf: &mut [u8], upsampling_method: UpsamplingMethod) { const BPP: usize = 4; match upsampling_method { UpsamplingMethod::Bilinear => { yuv::fill_rgb_buffer_fancy::( buf, &self.ybuf, &self.ubuf, &self.vbuf, usize::from(self.width), usize::from(self.height), usize::from(self.buffer_width()), ); } UpsamplingMethod::Simple => { yuv::fill_rgb_buffer_simple::( buf, &self.ybuf, &self.ubuf, &self.vbuf, usize::from(self.width), usize::from(self.chroma_width()), usize::from(self.buffer_width()), ); } } } /// Gets the buffer size #[must_use] 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: ArithmeticDecoder, mbwidth: u16, mbheight: u16, macroblocks: Vec, frame: Frame, segments_enabled: bool, segments_update_map: bool, segment: [Segment; MAX_SEGMENTS], loop_filter_adjustments_enabled: bool, ref_delta: [i32; 4], mode_delta: [i32; 4], partitions: [ArithmeticDecoder; 8], num_partitions: u8, segment_tree_nodes: [TreeNode; 3], token_probs: Box, // Section 9.10 prob_intra: Prob, // Section 9.11 prob_skip_false: Option, top: Vec, left: MacroBlock, // The borders from the previous macroblock, used for predictions // See Section 12 // Note that the left border contains the top left pixel top_border_y: Vec, left_border_y: Vec, top_border_u: Vec, left_border_u: Vec, top_border_v: Vec, left_border_v: Vec, } impl Vp8Decoder { /// Create a new decoder. /// The reader must present a raw vp8 bitstream to the decoder fn new(r: R) -> Self { let f = Frame::default(); let s = Segment::default(); let m = MacroBlock::default(); Self { r, b: ArithmeticDecoder::new(), mbwidth: 0, mbheight: 0, macroblocks: Vec::new(), frame: f, segments_enabled: false, segments_update_map: false, segment: [s; MAX_SEGMENTS], loop_filter_adjustments_enabled: false, ref_delta: [0; 4], mode_delta: [0; 4], partitions: [ ArithmeticDecoder::new(), ArithmeticDecoder::new(), ArithmeticDecoder::new(), ArithmeticDecoder::new(), ArithmeticDecoder::new(), ArithmeticDecoder::new(), ArithmeticDecoder::new(), ArithmeticDecoder::new(), ], num_partitions: 1, segment_tree_nodes: SEGMENT_TREE_NODE_DEFAULTS, token_probs: Box::new(COEFF_PROB_NODES), // Section 9.10 prob_intra: 0u8, // Section 9.11 prob_skip_false: None, top: Vec::new(), left: m, top_border_y: Vec::new(), left_border_y: Vec::new(), top_border_u: Vec::new(), left_border_u: Vec::new(), top_border_v: Vec::new(), left_border_v: Vec::new(), } } fn update_token_probabilities(&mut self) -> Result<(), DecodingError> { let mut res = self.b.start_accumulated_result(); 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).or_accumulate(&mut res) { let v = self.b.read_literal(8).or_accumulate(&mut res); self.token_probs[i][j][k][t].prob = v; } } } } } self.b.check(res, ()) } fn init_partitions(&mut self, n: usize) -> Result<(), DecodingError> { 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 size = size as usize; let mut buf = vec![[0; 4]; size.div_ceil(4)]; let bytes: &mut [u8] = buf.as_mut_slice().as_flattened_mut(); self.r.read_exact(&mut bytes[..size])?; self.partitions[i].init(buf, size)?; } } let mut buf = Vec::new(); self.r.read_to_end(&mut buf)?; let size = buf.len(); let mut chunks = vec![[0; 4]; size.div_ceil(4)]; chunks.as_mut_slice().as_flattened_mut()[..size].copy_from_slice(&buf); self.partitions[n - 1].init(chunks, size)?; Ok(()) } fn read_quantization_indices(&mut self) -> Result<(), DecodingError> { fn dc_quant(index: i32) -> i16 { DC_QUANT[index.clamp(0, 127) as usize] } fn ac_quant(index: i32) -> i16 { AC_QUANT[index.clamp(0, 127) as usize] } let mut res = self.b.start_accumulated_result(); let yac_abs = self.b.read_literal(7).or_accumulate(&mut res); let ydc_delta = self.b.read_optional_signed_value(4).or_accumulate(&mut res); let y2dc_delta = self.b.read_optional_signed_value(4).or_accumulate(&mut res); let y2ac_delta = self.b.read_optional_signed_value(4).or_accumulate(&mut res); let uvdc_delta = self.b.read_optional_signed_value(4).or_accumulate(&mut res); let uvac_delta = self.b.read_optional_signed_value(4).or_accumulate(&mut res); let n = if self.segments_enabled { MAX_SEGMENTS } else { 1 }; for i in 0usize..n { let base = i32::from(if self.segments_enabled { if self.segment[i].delta_values { i16::from(self.segment[i].quantizer_level) + i16::from(yac_abs) } else { i16::from(self.segment[i].quantizer_level) } } else { 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; } } self.b.check(res, ()) } fn read_loop_filter_adjustments(&mut self) -> Result<(), DecodingError> { let mut res = self.b.start_accumulated_result(); if self.b.read_flag().or_accumulate(&mut res) { for i in 0usize..4 { self.ref_delta[i] = self.b.read_optional_signed_value(6).or_accumulate(&mut res); } for i in 0usize..4 { self.mode_delta[i] = self.b.read_optional_signed_value(6).or_accumulate(&mut res); } } self.b.check(res, ()) } fn read_segment_updates(&mut self) -> Result<(), DecodingError> { let mut res = self.b.start_accumulated_result(); // Section 9.3 self.segments_update_map = self.b.read_flag().or_accumulate(&mut res); let update_segment_feature_data = self.b.read_flag().or_accumulate(&mut res); if update_segment_feature_data { let segment_feature_mode = self.b.read_flag().or_accumulate(&mut res); for i in 0usize..MAX_SEGMENTS { self.segment[i].delta_values = !segment_feature_mode; } for i in 0usize..MAX_SEGMENTS { self.segment[i].quantizer_level = self.b.read_optional_signed_value(7).or_accumulate(&mut res) as i8; } for i in 0usize..MAX_SEGMENTS { self.segment[i].loopfilter_level = self.b.read_optional_signed_value(6).or_accumulate(&mut res) as i8; } } if self.segments_update_map { for i in 0usize..3 { let update = self.b.read_flag().or_accumulate(&mut res); let prob = if update { self.b.read_literal(8).or_accumulate(&mut res) } else { 255 }; self.segment_tree_nodes[i].prob = prob; } } self.b.check(res, ()) } fn read_frame_header(&mut self) -> Result<(), DecodingError> { 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(DecodingError::Vp8MagicInvalid(tag)); } 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().copied().unwrap_or_default(); self.mbwidth = self.frame.width.div_ceil(16); self.mbheight = self.frame.height.div_ceil(16); self.frame.ybuf = vec![0u8; usize::from(self.mbwidth) * 16 * usize::from(self.mbheight) * 16]; self.frame.ubuf = vec![0u8; usize::from(self.mbwidth) * 8 * usize::from(self.mbheight) * 8]; self.frame.vbuf = vec![0u8; usize::from(self.mbwidth) * 8 * usize::from(self.mbheight) * 8]; self.top_border_y = vec![127u8; self.frame.width as usize + 4 + 16]; self.left_border_y = vec![129u8; 1 + 16]; // 8 pixels per macroblock self.top_border_u = vec![127u8; 8 * self.mbwidth as usize]; self.left_border_u = vec![129u8; 1 + 8]; self.top_border_v = vec![127u8; 8 * self.mbwidth as usize]; self.left_border_v = vec![129u8; 1 + 8]; } let size = first_partition_size as usize; let mut buf = vec![[0; 4]; size.div_ceil(4)]; let bytes: &mut [u8] = buf.as_mut_slice().as_flattened_mut(); self.r.read_exact(&mut bytes[..size])?; // initialise binary decoder self.b.init(buf, size)?; let mut res = self.b.start_accumulated_result(); if self.frame.keyframe { let color_space = self.b.read_literal(1).or_accumulate(&mut res); self.frame.pixel_type = self.b.read_literal(1).or_accumulate(&mut res); if color_space != 0 { return Err(DecodingError::ColorSpaceInvalid(color_space)); } } self.segments_enabled = self.b.read_flag().or_accumulate(&mut res); if self.segments_enabled { self.read_segment_updates()?; } self.frame.filter_type = self.b.read_flag().or_accumulate(&mut res); self.frame.filter_level = self.b.read_literal(6).or_accumulate(&mut res); self.frame.sharpness_level = self.b.read_literal(3).or_accumulate(&mut res); self.loop_filter_adjustments_enabled = self.b.read_flag().or_accumulate(&mut res); if self.loop_filter_adjustments_enabled { self.read_loop_filter_adjustments()?; } let num_partitions = 1 << self.b.read_literal(2).or_accumulate(&mut res) as usize; self.b.check(res, ())?; self.num_partitions = num_partitions as u8; 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(DecodingError::UnsupportedFeature( "Non-keyframe frames".to_owned(), )); } // Refresh entropy probs ????? let _ = self.b.read_literal(1); self.update_token_probabilities()?; let mut res = self.b.start_accumulated_result(); let mb_no_skip_coeff = self.b.read_literal(1).or_accumulate(&mut res); self.prob_skip_false = if mb_no_skip_coeff == 1 { Some(self.b.read_literal(8).or_accumulate(&mut res)) } else { None }; self.b.check(res, ())?; if !self.frame.keyframe { // 9.10 remaining frame data self.prob_intra = 0; // FIXME: support this? return Err(DecodingError::UnsupportedFeature( "Non-keyframe frames".to_owned(), )); } else { // Reset motion vectors } Ok(()) } fn read_macroblock_header(&mut self, mbx: usize) -> Result { let mut mb = MacroBlock::default(); let mut res = self.b.start_accumulated_result(); if self.segments_enabled && self.segments_update_map { mb.segmentid = (self.b.read_with_tree(&self.segment_tree_nodes)).or_accumulate(&mut res) as u8; }; mb.coeffs_skipped = if let Some(prob) = self.prob_skip_false { self.b.read_bool(prob).or_accumulate(&mut res) } else { false }; let inter_predicted = if !self.frame.keyframe { self.b.read_bool(self.prob_intra).or_accumulate(&mut res) } else { false }; if inter_predicted { return Err(DecodingError::UnsupportedFeature( "VP8 inter-prediction".to_owned(), )); } if self.frame.keyframe { // intra prediction let luma = (self.b.read_with_tree(&KEYFRAME_YMODE_NODES)).or_accumulate(&mut res); mb.luma_mode = LumaMode::from_i8(luma).ok_or(DecodingError::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_NODES[top as usize][left as usize], ); let intra = intra.or_accumulate(&mut res); let bmode = IntraMode::from_i8(intra) .ok_or(DecodingError::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_NODES)).or_accumulate(&mut res); mb.chroma_mode = ChromaMode::from_i8(chroma) .ok_or(DecodingError::ChromaPredictionModeInvalid(chroma))?; } self.top[mbx].chroma_mode = mb.chroma_mode; self.top[mbx].luma_mode = mb.luma_mode; self.top[mbx].bpred = mb.bpred; self.b.check(res, mb) } fn intra_predict_luma(&mut self, mbx: usize, mby: usize, mb: &MacroBlock, resdata: &[i32]) { let stride = 1usize + 16 + 4; let mw = self.mbwidth as usize; let mut ws = create_border_luma(mbx, mby, mw, &self.top_border_y, &self.left_border_y); 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_y[0] = ws[16]; for (i, left) in self.left_border_y[1..][..16].iter_mut().enumerate() { *left = ws[(i + 1) * stride + 16]; } for (top, &w) in self.top_border_y[mbx * 16..][..16] .iter_mut() .zip(&ws[16 * stride + 1..][..16]) { *top = w; } for y in 0usize..16 { for (ybuf, &ws) in self.frame.ybuf[(mby * 16 + y) * mw * 16 + mbx * 16..][..16] .iter_mut() .zip(ws[(1 + y) * stride + 1..][..16].iter()) { *ybuf = ws; } } } fn intra_predict_chroma(&mut self, mbx: usize, mby: usize, mb: &MacroBlock, resdata: &[i32]) { let stride = 1usize + 8; let mw = self.mbwidth as usize; //8x8 with left top border of 1 let mut uws = create_border_chroma(mbx, mby, &self.top_border_u, &self.left_border_u); let mut vws = create_border_chroma(mbx, mby, &self.top_border_v, &self.left_border_v); 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); } } set_chroma_border(&mut self.left_border_u, &mut self.top_border_u, &uws, mbx); set_chroma_border(&mut self.left_border_v, &mut self.top_border_v, &vws, mbx); for y in 0usize..8 { let uv_buf_index = (mby * 8 + y) * mw * 8 + mbx * 8; let ws_index = (1 + y) * stride + 1; for (((ub, vb), &uw), &vw) in self.frame.ubuf[uv_buf_index..][..8] .iter_mut() .zip(self.frame.vbuf[uv_buf_index..][..8].iter_mut()) .zip(uws[ws_index..][..8].iter()) .zip(vws[ws_index..][..8].iter()) { *ub = uw; *vb = vw; } } } fn read_coefficients( &mut self, block: &mut [i32; 16], p: usize, plane: usize, complexity: usize, dcq: i16, acq: i16, ) -> Result { // perform bounds checks once up front, // so that the compiler doesn't have to insert them in the hot loop below assert!(complexity <= 2); let first = if plane == 0 { 1usize } else { 0usize }; let probs = &self.token_probs[plane]; let decoder = &mut self.partitions[p]; let mut res = decoder.start_accumulated_result(); let mut complexity = complexity; let mut has_coefficients = false; let mut skip = false; for i in first..16usize { let band = COEFF_BANDS[i] as usize; let tree = &probs[band][complexity]; let token = decoder .read_with_tree_with_first_node(tree, tree[skip as usize]) .or_accumulate(&mut res); 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 probs = PROB_DCT_CAT[(category - DCT_CAT1) as usize]; let mut extra = 0i16; for t in probs.iter().copied() { if t == 0 { break; } let b = decoder.read_bool(t).or_accumulate(&mut res); extra = extra + extra + i16::from(b); } 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 decoder.read_flag().or_accumulate(&mut res) { abs_value = -abs_value; } let zigzag = ZIGZAG[i] as usize; block[zigzag] = abs_value * i32::from(if zigzag > 0 { acq } else { dcq }); has_coefficients = true; } decoder.check(res, has_coefficients) } fn read_residual_data( &mut self, mb: &mut MacroBlock, mbx: usize, p: usize, ) -> Result<[i32; 384], DecodingError> { 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..][..16]; let block: &mut [i32; 16] = block.try_into().unwrap(); 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 { mb.non_zero_dct = true; 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..][..16]; let block: &mut [i32; 16] = block.try_into().unwrap(); 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 { mb.non_zero_dct = true; 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; } } Ok(blocks) } /// Does loop filtering on the macroblock fn loop_filter(&mut self, mbx: usize, mby: usize, mb: &MacroBlock) { let luma_w = self.mbwidth as usize * 16; let chroma_w = self.mbwidth as usize * 8; 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; // we skip subblock filtering if the coding mode isn't B_PRED and there's no DCT coefficient coded let do_subblock_filtering = mb.luma_mode == LumaMode::B || (!mb.coeffs_skipped && mb.non_zero_dct); //filter across left of macroblock if mbx > 0 { //simple loop filtering if self.frame.filter_type { for y in 0usize..16 { let y0 = mby * 16 + y; let x0 = mbx * 16; loop_filter::simple_segment_horizontal( mbedge_limit, &mut self.frame.ybuf[y0 * luma_w + x0 - 4..][..8], ); } } else { for y in 0usize..16 { let y0 = mby * 16 + y; let x0 = mbx * 16; loop_filter::macroblock_filter_horizontal( hev_threshold, interior_limit, mbedge_limit, &mut self.frame.ybuf[y0 * luma_w + x0 - 4..][..8], ); } for y in 0usize..8 { let y0 = mby * 8 + y; let x0 = mbx * 8; loop_filter::macroblock_filter_horizontal( hev_threshold, interior_limit, mbedge_limit, &mut self.frame.ubuf[y0 * chroma_w + x0 - 4..][..8], ); loop_filter::macroblock_filter_horizontal( hev_threshold, interior_limit, mbedge_limit, &mut self.frame.vbuf[y0 * chroma_w + x0 - 4..][..8], ); } } } //filter across vertical subblocks in macroblock if do_subblock_filtering { if self.frame.filter_type { for x in (4usize..16 - 1).step_by(4) { for y in 0..16 { let y0 = mby * 16 + y; let x0 = mbx * 16 + x; loop_filter::simple_segment_horizontal( sub_bedge_limit, &mut self.frame.ybuf[y0 * luma_w + x0 - 4..][..8], ); } } } else { for x in (4usize..16 - 3).step_by(4) { for y in 0..16 { let y0 = mby * 16 + y; let x0 = mbx * 16 + x; loop_filter::subblock_filter_horizontal( hev_threshold, interior_limit, sub_bedge_limit, &mut self.frame.ybuf[y0 * luma_w + x0 - 4..][..8], ); } } for y in 0usize..8 { let y0 = mby * 8 + y; let x0 = mbx * 8 + 4; loop_filter::subblock_filter_horizontal( hev_threshold, interior_limit, sub_bedge_limit, &mut self.frame.ubuf[y0 * chroma_w + x0 - 4..][..8], ); loop_filter::subblock_filter_horizontal( hev_threshold, interior_limit, sub_bedge_limit, &mut self.frame.vbuf[y0 * chroma_w + x0 - 4..][..8], ); } } } //filter across top of macroblock if mby > 0 { if self.frame.filter_type { for x in 0usize..16 { let y0 = mby * 16; let x0 = mbx * 16 + x; loop_filter::simple_segment_vertical( 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 for x in 0usize..16 { let y0 = mby * 16; let x0 = mbx * 16 + x; loop_filter::macroblock_filter_vertical( hev_threshold, interior_limit, mbedge_limit, &mut self.frame.ybuf[..], y0 * luma_w + x0, luma_w, ); } for x in 0usize..8 { let y0 = mby * 8; let x0 = mbx * 8 + x; loop_filter::macroblock_filter_vertical( hev_threshold, interior_limit, mbedge_limit, &mut self.frame.ubuf[..], y0 * chroma_w + x0, chroma_w, ); loop_filter::macroblock_filter_vertical( 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 do_subblock_filtering { if self.frame.filter_type { for y in (4usize..16 - 1).step_by(4) { for x in 0..16 { let y0 = mby * 16 + y; let x0 = mbx * 16 + x; loop_filter::simple_segment_vertical( sub_bedge_limit, &mut self.frame.ybuf[..], y0 * luma_w + x0, luma_w, ); } } } else { for y in (4usize..16 - 3).step_by(4) { for x in 0..16 { let y0 = mby * 16 + y; let x0 = mbx * 16 + x; loop_filter::subblock_filter_vertical( hev_threshold, interior_limit, sub_bedge_limit, &mut self.frame.ybuf[..], y0 * luma_w + x0, luma_w, ); } } for x in 0..8 { let y0 = mby * 8 + 4; let x0 = mbx * 8 + x; loop_filter::subblock_filter_vertical( hev_threshold, interior_limit, sub_bedge_limit, &mut self.frame.ubuf[..], y0 * chroma_w + x0, chroma_w, ); loop_filter::subblock_filter_vertical( 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 = i32::from(self.frame.filter_level); // if frame level filter level is 0, we must skip loop filter if filter_level == 0 { return (0, 0, 0); } 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 = filter_level.clamp(0, 63); if self.loop_filter_adjustments_enabled { filter_level += self.ref_delta[0]; if macroblock.luma_mode == LumaMode::B { filter_level += self.mode_delta[0]; } } let filter_level = filter_level.clamp(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 if filter_level >= 15 { 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(r: R) -> Result { let decoder = Self::new(r); decoder.decode_frame_() } fn decode_frame_(mut self) -> Result { 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 mut mb = self.read_macroblock_header(mbx)?; let blocks = if !mb.coeffs_skipped { self.read_residual_data(&mut 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_y = vec![129u8; 1 + 16]; self.left_border_u = vec![129u8; 1 + 8]; self.left_border_v = vec![129u8; 1 + 8]; } //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 { const fn from_i8(val: i8) -> Option { Some(match val { DC_PRED => Self::DC, V_PRED => Self::V, H_PRED => Self::H, TM_PRED => Self::TM, B_PRED => Self::B, _ => return None, }) } const fn into_intra(self) -> Option { Some(match self { Self::DC => IntraMode::DC, Self::V => IntraMode::VE, Self::H => IntraMode::HE, Self::TM => IntraMode::TM, Self::B => return None, }) } } impl ChromaMode { const fn from_i8(val: i8) -> Option { Some(match val { DC_PRED => Self::DC, V_PRED => Self::V, H_PRED => Self::H, TM_PRED => Self::TM, _ => return None, }) } } impl IntraMode { const fn from_i8(val: i8) -> Option { Some(match val { B_DC_PRED => Self::DC, B_TM_PRED => Self::TM, B_VE_PRED => Self::VE, B_HE_PRED => Self::HE, B_LD_PRED => Self::LD, B_RD_PRED => Self::RD, B_VR_PRED => Self::VR, B_VL_PRED => Self::VL, B_HD_PRED => Self::HD, B_HU_PRED => Self::HU, _ => return None, }) } } fn init_top_macroblocks(width: usize) -> Vec { let mb_width = width.div_ceil(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 (above, &top) in above[..16].iter_mut().zip(&top[mbx * 16..]) { *above = top; } if mbx == mbw - 1 { for above in &mut above[16..] { *above = top[mbx * 16 + 15]; } } else { for (above, &top) in above[16..].iter_mut().zip(&top[mbx * 16 + 16..]) { *above = top; } } } } 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, &left) in (0usize..16).zip(&left[1..]) { ws[(i + 1) * stride] = left; } } // P ws[0] = if mby == 0 { 127 } else if mbx == 0 { 129 } else { left[0] }; ws } const CHROMA_BLOCK_SIZE: usize = (8 + 1) * (8 + 1); // creates the left and top border for chroma prediction fn create_border_chroma( mbx: usize, mby: usize, top: &[u8], left: &[u8], ) -> [u8; CHROMA_BLOCK_SIZE] { let stride: usize = 1usize + 8; let mut chroma_block = [0u8; CHROMA_BLOCK_SIZE]; // above { let above = &mut chroma_block[1..stride]; if mby == 0 { for above in above.iter_mut() { *above = 127; } } else { for (above, &top) in above.iter_mut().zip(&top[mbx * 8..]) { *above = top; } } } // left if mbx == 0 { for y in 0usize..8 { chroma_block[(y + 1) * stride] = 129; } } else { for (y, &left) in (0usize..8).zip(&left[1..]) { chroma_block[(y + 1) * stride] = left; } } chroma_block[0] = if mby == 0 { 127 } else if mbx == 0 { 129 } else { left[0] }; chroma_block } // set border fn set_chroma_border( left_border: &mut [u8], top_border: &mut [u8], chroma_block: &[u8], mbx: usize, ) { let stride = 1usize + 8; // top left is top right of previous chroma block left_border[0] = chroma_block[8]; // left border for (i, left) in left_border[1..][..8].iter_mut().enumerate() { *left = chroma_block[(i + 1) * stride + 8]; } for (top, &w) in top_border[mbx * 8..][..8] .iter_mut() .zip(&chroma_block[8 * stride + 1..][..8]) { *top = w; } } 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. // // Clippy suggests the clamp method, but it seems to optimize worse as of rustc 1.82.0 nightly. #[allow(clippy::manual_clamp)] 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..][..4].iter_mut().zip(row.iter()) { *p = (a + i32::from(*p)).max(0).min(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) { // This pass copies the top row to the rows below it. let (above, curr) = a.split_at_mut(stride * y0); let above_slice = &above[x0..]; for curr_chunk in curr.chunks_exact_mut(stride).take(size) { for (curr, &above) in curr_chunk[1..].iter_mut().zip(above_slice) { *curr = above; } } } fn predict_hpred(a: &mut [u8], size: usize, x0: usize, y0: usize, stride: usize) { // This pass copies the first value of a row to the values right of it. for chunk in a.chunks_exact_mut(stride).skip(y0).take(size) { let left = chunk[x0 - 1]; chunk[x0..].iter_mut().for_each(|a| *a = left); } } 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 { sum += a[1..=size].iter().fold(0, |acc, &x| acc + u32::from(x)); shf += 1; } let dcval = if !left && !above { 128 } else { (sum + (1 << (shf - 1))) >> shf }; for y in 0usize..size { a[1 + stride * (y + 1)..][..size] .iter_mut() .for_each(|a| *a = dcval as u8); } } // Clippy suggests the clamp method, but it seems to optimize worse as of rustc 1.82.0 nightly. #[allow(clippy::manual_clamp)] fn predict_tmpred(a: &mut [u8], size: usize, x0: usize, y0: usize, stride: usize) { // The formula for tmpred is: // X_ij = L_i + A_j - P (i, j=0, 1, 2, 3) // // |-----|-----|-----|-----|-----| // | P | A0 | A1 | A2 | A3 | // |-----|-----|-----|-----|-----| // | L0 | X00 | X01 | X02 | X03 | // |-----|-----|-----|-----|-----| // | L1 | X10 | X11 | X12 | X13 | // |-----|-----|-----|-----|-----| // | L2 | X20 | X21 | X22 | X23 | // |-----|-----|-----|-----|-----| // | L3 | X30 | X31 | X32 | X33 | // |-----|-----|-----|-----|-----| // Diagram from p. 52 of RFC 6386 // Split at L0 let (above, x_block) = a.split_at_mut(y0 * stride + (x0 - 1)); let p = i32::from(above[(y0 - 1) * stride + x0 - 1]); let above_slice = &above[(y0 - 1) * stride + x0..]; for y in 0usize..size { let left_minus_p = i32::from(x_block[y * stride]) - p; // Add 1 to skip over L0 byte x_block[y * stride + 1..][..size] .iter_mut() .zip(above_slice) .for_each(|(cur, &abv)| *cur = (left_minus_p + i32::from(abv)).max(0).min(255) as u8); } } fn predict_bdcpred(a: &mut [u8], x0: usize, y0: usize, stride: usize) { let mut v = 4; a[(y0 - 1) * stride + x0..][..4] .iter() .for_each(|&a| v += u32::from(a)); for i in 0usize..4 { v += u32::from(a[(y0 + i) * stride + x0 - 1]); } v >>= 3; for chunk in a.chunks_exact_mut(stride).skip(y0).take(4) { for ch in &mut chunk[x0..][..4] { *ch = 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 { for a_p in &mut a[pos..=pos + 3] { *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(all(test, feature = "_benchmarks"))] mod benches { use super::*; use test::{black_box, Bencher}; const W: usize = 256; const H: usize = 256; fn make_sample_image() -> Vec { let mut v = Vec::with_capacity((W * H * 4) as usize); for c in 0u8..=255 { for k in 0u8..=255 { v.push(c); v.push(0); v.push(0); v.push(k); } } v } #[bench] fn bench_predict_4x4(b: &mut Bencher) { let mut v = black_box(make_sample_image()); let res_data = vec![1i32; W * H * 4]; let modes = [ IntraMode::TM, IntraMode::VE, IntraMode::HE, IntraMode::DC, IntraMode::LD, IntraMode::RD, IntraMode::VR, IntraMode::VL, IntraMode::HD, IntraMode::HU, IntraMode::TM, IntraMode::VE, IntraMode::HE, IntraMode::DC, IntraMode::LD, IntraMode::RD, ]; b.iter(|| { black_box(predict_4x4(&mut v, W * 2, &modes, &res_data)); }); } #[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); }); } #[bench] fn bench_predict_bldpred(b: &mut Bencher) { let mut v = black_box(make_sample_image()); b.iter(|| { black_box(predict_bldpred(black_box(&mut v), 5, 5, W * 2)); }); } #[bench] fn bench_predict_brdpred(b: &mut Bencher) { let mut v = black_box(make_sample_image()); b.iter(|| { black_box(predict_brdpred(black_box(&mut v), 5, 5, W * 2)); }); } #[bench] fn bench_predict_bhepred(b: &mut Bencher) { let mut v = black_box(make_sample_image()); b.iter(|| { black_box(predict_bhepred(black_box(&mut v), 5, 5, W * 2)); }); } #[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)); }); } #[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)); }); } } #[cfg(test)] mod tests { use super::*; #[test] fn test_avg2() { for i in 0u8..=255 { for j in 0u8..=255 { let ceil_avg = (f32::from(i) + f32::from(j)) / 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 = (2.0f32.mul_add(f32::from(j), f32::from(i)) + { f32::from(k) } + 2.0) / 4.0; let floor_avg = floor_avg.floor() as u8; assert_eq!( floor_avg, avg3(i, j, k), "avg3({}, {}, {}), expected {}, got {}.", i, j, k, floor_avg, avg3(i, j, k) ); } } } } #[test] fn test_edge_pixels() { #[rustfmt::skip] let im = vec![5, 6, 7, 8, 9, 4, 0, 0, 0, 0, 3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0]; let (e0, e1, e2, e3, e4, e5, e6, e7, e8) = edge_pixels(&im, 1, 1, 5); assert_eq!(e0, 1); assert_eq!(e1, 2); assert_eq!(e2, 3); assert_eq!(e3, 4); assert_eq!(e4, 5); assert_eq!(e5, 6); assert_eq!(e6, 7); assert_eq!(e7, 8); assert_eq!(e8, 9); } #[test] fn test_top_pixels() { #[rustfmt::skip] let im = vec![1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; let (e0, e1, e2, e3, e4, e5, e6, e7) = top_pixels(&im, 0, 1, 8); assert_eq!(e0, 1); assert_eq!(e1, 2); assert_eq!(e2, 3); assert_eq!(e3, 4); assert_eq!(e4, 5); assert_eq!(e5, 6); assert_eq!(e6, 7); assert_eq!(e7, 8); } #[test] fn test_add_residue() { let mut pblock = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; let rblock = [ -1, -2, -3, -4, 250, 249, 248, 250, -10, -18, -192, -17, -3, 15, 18, 9, ]; let expected: [u8; 16] = [0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 10, 29, 33, 25]; add_residue(&mut pblock, &rblock, 0, 0, 4); for (&e, &i) in expected.iter().zip(&pblock) { assert_eq!(e, i); } } #[test] fn test_predict_bhepred() { #[rustfmt::skip] let expected: Vec = vec![5, 0, 0, 0, 0, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1]; #[rustfmt::skip] let mut im = vec![5, 0, 0, 0, 0, 4, 0, 0, 0, 0, 3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0]; predict_bhepred(&mut im, 1, 1, 5); for (&e, i) in expected.iter().zip(im) { assert_eq!(e, i); } } #[test] fn test_predict_brdpred() { #[rustfmt::skip] let expected: Vec = vec![5, 6, 7, 8, 9, 4, 5, 6, 7, 8, 3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]; #[rustfmt::skip] let mut im = vec![5, 6, 7, 8, 9, 4, 0, 0, 0, 0, 3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0]; predict_brdpred(&mut im, 1, 1, 5); for (&e, i) in expected.iter().zip(im) { assert_eq!(e, i); } } #[test] fn test_predict_bldpred() { #[rustfmt::skip] let mut im: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; let avg_1 = 2u8; let avg_2 = 3u8; let avg_3 = 4u8; let avg_4 = 5u8; let avg_5 = 6u8; let avg_6 = 7u8; let avg_7 = 8u8; predict_bldpred(&mut im, 0, 1, 8); assert_eq!(im[8], avg_1); assert_eq!(im[9], avg_2); assert_eq!(im[10], avg_3); assert_eq!(im[11], avg_4); assert_eq!(im[16], avg_2); assert_eq!(im[17], avg_3); assert_eq!(im[18], avg_4); assert_eq!(im[19], avg_5); assert_eq!(im[24], avg_3); assert_eq!(im[25], avg_4); assert_eq!(im[26], avg_5); assert_eq!(im[27], avg_6); assert_eq!(im[32], avg_4); assert_eq!(im[33], avg_5); assert_eq!(im[34], avg_6); assert_eq!(im[35], avg_7); } #[test] fn test_predict_bvepred() { #[rustfmt::skip] let mut im: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; let avg_1 = 2u8; let avg_2 = 3u8; let avg_3 = 4u8; let avg_4 = 5u8; predict_bvepred(&mut im, 1, 1, 9); assert_eq!(im[10], avg_1); assert_eq!(im[11], avg_2); assert_eq!(im[12], avg_3); assert_eq!(im[13], avg_4); assert_eq!(im[19], avg_1); assert_eq!(im[20], avg_2); assert_eq!(im[21], avg_3); assert_eq!(im[22], avg_4); assert_eq!(im[28], avg_1); assert_eq!(im[29], avg_2); assert_eq!(im[30], avg_3); assert_eq!(im[31], avg_4); assert_eq!(im[37], avg_1); assert_eq!(im[38], avg_2); assert_eq!(im[39], avg_3); assert_eq!(im[40], avg_4); } } image-webp-0.2.4/src/vp8_arithmetic_decoder.rs000064400000000000000000000500201046102023000174110ustar 00000000000000use crate::decoder::DecodingError; use super::vp8::TreeNode; #[must_use] #[repr(transparent)] pub(crate) struct BitResult { value_if_not_past_eof: T, } #[must_use] pub(crate) struct BitResultAccumulator; impl BitResult { const fn ok(value: T) -> Self { Self { value_if_not_past_eof: value, } } /// Instead of checking this result now, accumulate the burden of checking /// into an accumulator. This accumulator must be checked in the end. #[inline(always)] pub(crate) fn or_accumulate(self, acc: &mut BitResultAccumulator) -> T { let _ = acc; self.value_if_not_past_eof } } impl BitResult { fn err() -> Self { Self { value_if_not_past_eof: T::default(), } } } #[cfg_attr(test, derive(Debug))] pub(crate) struct ArithmeticDecoder { chunks: Box<[[u8; 4]]>, state: State, final_bytes: [u8; 3], final_bytes_remaining: i8, } #[cfg_attr(test, derive(Debug))] #[derive(Clone, Copy)] struct State { chunk_index: usize, value: u64, range: u32, bit_count: i32, } #[cfg_attr(test, derive(Debug))] struct FastDecoder<'a> { chunks: &'a [[u8; 4]], uncommitted_state: State, save_state: &'a mut State, } impl ArithmeticDecoder { pub(crate) fn new() -> ArithmeticDecoder { let state = State { chunk_index: 0, value: 0, range: 255, bit_count: -8, }; ArithmeticDecoder { chunks: Box::new([]), state, final_bytes: [0; 3], final_bytes_remaining: Self::FINAL_BYTES_REMAINING_EOF, } } pub(crate) fn init(&mut self, mut buf: Vec<[u8; 4]>, len: usize) -> Result<(), DecodingError> { let mut final_bytes = [0; 3]; let final_bytes_remaining = if len == 4 * buf.len() { 0 } else { // Pop the last chunk (which is partial), then get length. let Some(last_chunk) = buf.pop() else { return Err(DecodingError::NotEnoughInitData); }; let len_rounded_down = 4 * buf.len(); let num_bytes_popped = len - len_rounded_down; debug_assert!(num_bytes_popped <= 3); final_bytes[..num_bytes_popped].copy_from_slice(&last_chunk[..num_bytes_popped]); for i in num_bytes_popped..4 { debug_assert_eq!(last_chunk[i], 0, "unexpected {last_chunk:?}"); } num_bytes_popped as i8 }; let chunks = buf.into_boxed_slice(); let state = State { chunk_index: 0, value: 0, range: 255, bit_count: -8, }; *self = Self { chunks, state, final_bytes, final_bytes_remaining, }; Ok(()) } /// Start a span of reading operations from the buffer, without stopping /// when the buffer runs out. For all valid webp images, the buffer will not /// run out prematurely. Conversely if the buffer ends early, the webp image /// cannot be correctly decoded and any intermediate results need to be /// discarded anyway. /// /// Each call to `start_accumulated_result` must be followed by a call to /// `check` on the *same* `ArithmeticDecoder`. #[inline(always)] pub(crate) fn start_accumulated_result(&mut self) -> BitResultAccumulator { BitResultAccumulator } /// Check that the read operations done so far were all valid. #[inline(always)] pub(crate) fn check( &self, acc: BitResultAccumulator, value_if_not_past_eof: T, ) -> Result { // The accumulator does not store any state because doing so is // too computationally expensive. Passing it around is a bit of // formality (that is optimized out) to ensure we call `check` . // Instead we check whether we have read past the end of the file. let BitResultAccumulator = acc; if self.is_past_eof() { Err(DecodingError::BitStreamError) } else { Ok(value_if_not_past_eof) } } fn keep_accumulating( &self, acc: BitResultAccumulator, value_if_not_past_eof: T, ) -> BitResult { // The BitResult will be checked later by a different accumulator. // Because it does not carry state, that is fine. let BitResultAccumulator = acc; BitResult::ok(value_if_not_past_eof) } // Do not inline this because inlining seems to worsen performance. #[inline(never)] pub(crate) fn read_bool(&mut self, probability: u8) -> BitResult { if let Some(b) = self.fast().read_bool(probability) { return BitResult::ok(b); } self.cold_read_bool(probability) } // Do not inline this because inlining seems to worsen performance. #[inline(never)] pub(crate) fn read_flag(&mut self) -> BitResult { if let Some(b) = self.fast().read_flag() { return BitResult::ok(b); } self.cold_read_flag() } // Do not inline this because inlining seems to worsen performance. #[inline(never)] pub(crate) fn read_literal(&mut self, n: u8) -> BitResult { if let Some(v) = self.fast().read_literal(n) { return BitResult::ok(v); } self.cold_read_literal(n) } // Do not inline this because inlining seems to worsen performance. #[inline(never)] pub(crate) fn read_optional_signed_value(&mut self, n: u8) -> BitResult { if let Some(v) = self.fast().read_optional_signed_value(n) { return BitResult::ok(v); } self.cold_read_optional_signed_value(n) } // This is generic and inlined just to skip the first bounds check. #[inline] pub(crate) fn read_with_tree(&mut self, tree: &[TreeNode; N]) -> BitResult { let first_node = tree[0]; self.read_with_tree_with_first_node(tree, first_node) } // Do not inline this because inlining significantly worsens performance. #[inline(never)] pub(crate) fn read_with_tree_with_first_node( &mut self, tree: &[TreeNode], first_node: TreeNode, ) -> BitResult { if let Some(v) = self.fast().read_with_tree(tree, first_node) { return BitResult::ok(v); } self.cold_read_with_tree(tree, usize::from(first_node.index)) } // As a similar (but different) speedup to BitResult, the FastDecoder reads // bits under an assumption and validates it at the end. // // The idea here is that for normal-sized webp images, the vast majority // of bits are somewhere other than in the last four bytes. Therefore we // can pretend the buffer has infinite size. After we are done reading, // we check if we actually read past the end of `self.chunks`. // If so, we backtrack (or rather we discard `uncommitted_state`) // and try again with the slow approach. This might result in doing double // work for those last few bytes -- in fact we even keep retrying the fast // method to save an if-statement --, but more than make up for that by // speeding up reading from the other thousands or millions of bytes. fn fast(&mut self) -> FastDecoder<'_> { FastDecoder { chunks: &self.chunks, uncommitted_state: self.state, save_state: &mut self.state, } } const FINAL_BYTES_REMAINING_EOF: i8 = -0xE; fn load_from_final_bytes(&mut self) { match self.final_bytes_remaining { 1.. => { self.final_bytes_remaining -= 1; let byte = self.final_bytes[0]; self.final_bytes.rotate_left(1); self.state.value <<= 8; self.state.value |= u64::from(byte); self.state.bit_count += 8; } 0 => { // libwebp seems to (sometimes?) allow bitstreams that read one byte past the end. // This replicates that logic. self.final_bytes_remaining -= 1; self.state.value <<= 8; self.state.bit_count += 8; } _ => { self.final_bytes_remaining = Self::FINAL_BYTES_REMAINING_EOF; } } } fn is_past_eof(&self) -> bool { self.final_bytes_remaining == Self::FINAL_BYTES_REMAINING_EOF } fn cold_read_bit(&mut self, probability: u8) -> BitResult { if self.state.bit_count < 0 { if let Some(chunk) = self.chunks.get(self.state.chunk_index).copied() { let v = u32::from_be_bytes(chunk); self.state.chunk_index += 1; self.state.value <<= 32; self.state.value |= u64::from(v); self.state.bit_count += 32; } else { self.load_from_final_bytes(); if self.is_past_eof() { return BitResult::err(); } } } debug_assert!(self.state.bit_count >= 0); let probability = u32::from(probability); let split = 1 + (((self.state.range - 1) * probability) >> 8); let bigsplit = u64::from(split) << self.state.bit_count; let retval = if let Some(new_value) = self.state.value.checked_sub(bigsplit) { self.state.range -= split; self.state.value = new_value; true } else { self.state.range = split; false }; debug_assert!(self.state.range > 0); // Compute shift required to satisfy `self.state.range >= 128`. // Apply that shift to `self.state.range` and `self.state.bitcount`. // // Subtract 24 because we only care about leading zeros in the // lowest byte of `self.state.range` which is a `u32`. let shift = self.state.range.leading_zeros().saturating_sub(24); self.state.range <<= shift; self.state.bit_count -= shift as i32; debug_assert!(self.state.range >= 128); BitResult::ok(retval) } #[cold] #[inline(never)] fn cold_read_bool(&mut self, probability: u8) -> BitResult { self.cold_read_bit(probability) } #[cold] #[inline(never)] fn cold_read_flag(&mut self) -> BitResult { self.cold_read_bit(128) } #[cold] #[inline(never)] fn cold_read_literal(&mut self, n: u8) -> BitResult { let mut v = 0u8; let mut res = self.start_accumulated_result(); for _ in 0..n { let b = self.cold_read_flag().or_accumulate(&mut res); v = (v << 1) + u8::from(b); } self.keep_accumulating(res, v) } #[cold] #[inline(never)] fn cold_read_optional_signed_value(&mut self, n: u8) -> BitResult { let mut res = self.start_accumulated_result(); let flag = self.cold_read_flag().or_accumulate(&mut res); if !flag { // We should not read further bits if the flag is not set. return self.keep_accumulating(res, 0); } let magnitude = self.cold_read_literal(n).or_accumulate(&mut res); let sign = self.cold_read_flag().or_accumulate(&mut res); let value = if sign { -i32::from(magnitude) } else { i32::from(magnitude) }; self.keep_accumulating(res, value) } #[cold] #[inline(never)] fn cold_read_with_tree(&mut self, tree: &[TreeNode], start: usize) -> BitResult { let mut index = start; let mut res = self.start_accumulated_result(); loop { let node = tree[index]; let prob = node.prob; let b = self.cold_read_bit(prob).or_accumulate(&mut res); let t = if b { node.right } else { node.left }; let new_index = usize::from(t); if new_index < tree.len() { index = new_index; } else { let value = TreeNode::value_from_branch(t); return self.keep_accumulating(res, value); } } } } impl FastDecoder<'_> { fn commit_if_valid(self, value_if_not_past_eof: T) -> Option { // If `chunk_index > self.chunks.len()`, it means we used zeroes // instead of an actual chunk and `value_if_not_past_eof` is nonsense. if self.uncommitted_state.chunk_index <= self.chunks.len() { *self.save_state = self.uncommitted_state; Some(value_if_not_past_eof) } else { None } } fn read_bool(mut self, probability: u8) -> Option { let bit = self.fast_read_bit(probability); self.commit_if_valid(bit) } fn read_flag(mut self) -> Option { let value = self.fast_read_flag(); self.commit_if_valid(value) } fn read_literal(mut self, n: u8) -> Option { let value = self.fast_read_literal(n); self.commit_if_valid(value) } fn read_optional_signed_value(mut self, n: u8) -> Option { let flag = self.fast_read_flag(); if !flag { // We should not read further bits if the flag is not set. return self.commit_if_valid(0); } let magnitude = self.fast_read_literal(n); let sign = self.fast_read_flag(); let value = if sign { -i32::from(magnitude) } else { i32::from(magnitude) }; self.commit_if_valid(value) } fn read_with_tree(mut self, tree: &[TreeNode], first_node: TreeNode) -> Option { let value = self.fast_read_with_tree(tree, first_node); self.commit_if_valid(value) } fn fast_read_bit(&mut self, probability: u8) -> bool { let State { mut chunk_index, mut value, mut range, mut bit_count, } = self.uncommitted_state; if bit_count < 0 { let chunk = self.chunks.get(chunk_index).copied(); // We ignore invalid data inside the `fast_` functions, // but we increase `chunk_index` below, so we can check // whether we read invalid data in `commit_if_valid`. let chunk = chunk.unwrap_or_default(); let v = u32::from_be_bytes(chunk); chunk_index += 1; value <<= 32; value |= u64::from(v); bit_count += 32; } debug_assert!(bit_count >= 0); let probability = u32::from(probability); let split = 1 + (((range - 1) * probability) >> 8); let bigsplit = u64::from(split) << bit_count; let retval = if let Some(new_value) = value.checked_sub(bigsplit) { range -= split; value = new_value; true } else { range = split; false }; debug_assert!(range > 0); // Compute shift required to satisfy `range >= 128`. // Apply that shift to `range` and `self.bitcount`. // // Subtract 24 because we only care about leading zeros in the // lowest byte of `range` which is a `u32`. let shift = range.leading_zeros().saturating_sub(24); range <<= shift; bit_count -= shift as i32; debug_assert!(range >= 128); self.uncommitted_state = State { chunk_index, value, range, bit_count, }; retval } fn fast_read_flag(&mut self) -> bool { let State { mut chunk_index, mut value, mut range, mut bit_count, } = self.uncommitted_state; if bit_count < 0 { let chunk = self.chunks.get(chunk_index).copied(); // We ignore invalid data inside the `fast_` functions, // but we increase `chunk_index` below, so we can check // whether we read invalid data in `commit_if_valid`. let chunk = chunk.unwrap_or_default(); let v = u32::from_be_bytes(chunk); chunk_index += 1; value <<= 32; value |= u64::from(v); bit_count += 32; } debug_assert!(bit_count >= 0); let half_range = range / 2; let split = range - half_range; let bigsplit = u64::from(split) << bit_count; let retval = if let Some(new_value) = value.checked_sub(bigsplit) { range = half_range; value = new_value; true } else { range = split; false }; debug_assert!(range > 0); // Compute shift required to satisfy `range >= 128`. // Apply that shift to `range` and `self.bitcount`. // // Subtract 24 because we only care about leading zeros in the // lowest byte of `range` which is a `u32`. let shift = range.leading_zeros().saturating_sub(24); range <<= shift; bit_count -= shift as i32; debug_assert!(range >= 128); self.uncommitted_state = State { chunk_index, value, range, bit_count, }; retval } fn fast_read_literal(&mut self, n: u8) -> u8 { let mut v = 0u8; for _ in 0..n { let b = self.fast_read_flag(); v = (v << 1) + u8::from(b); } v } fn fast_read_with_tree(&mut self, tree: &[TreeNode], mut node: TreeNode) -> i8 { loop { let prob = node.prob; let b = self.fast_read_bit(prob); let i = if b { node.right } else { node.left }; let Some(next_node) = tree.get(usize::from(i)) else { return TreeNode::value_from_branch(i); }; node = *next_node; } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_arithmetic_decoder_hello_short() { let mut decoder = ArithmeticDecoder::new(); let data = b"hel"; let size = data.len(); let mut buf = vec![[0u8; 4]; 1]; buf.as_mut_slice().as_flattened_mut()[..size].copy_from_slice(&data[..]); decoder.init(buf, size).unwrap(); let mut res = decoder.start_accumulated_result(); assert_eq!(false, decoder.read_flag().or_accumulate(&mut res)); assert_eq!(true, decoder.read_bool(10).or_accumulate(&mut res)); assert_eq!(false, decoder.read_bool(250).or_accumulate(&mut res)); assert_eq!(1, decoder.read_literal(1).or_accumulate(&mut res)); assert_eq!(5, decoder.read_literal(3).or_accumulate(&mut res)); assert_eq!(64, decoder.read_literal(8).or_accumulate(&mut res)); assert_eq!(185, decoder.read_literal(8).or_accumulate(&mut res)); decoder.check(res, ()).unwrap(); } #[test] fn test_arithmetic_decoder_hello_long() { let mut decoder = ArithmeticDecoder::new(); let data = b"hello world"; let size = data.len(); let mut buf = vec![[0u8; 4]; (size + 3) / 4]; buf.as_mut_slice().as_flattened_mut()[..size].copy_from_slice(&data[..]); decoder.init(buf, size).unwrap(); let mut res = decoder.start_accumulated_result(); assert_eq!(false, decoder.read_flag().or_accumulate(&mut res)); assert_eq!(true, decoder.read_bool(10).or_accumulate(&mut res)); assert_eq!(false, decoder.read_bool(250).or_accumulate(&mut res)); assert_eq!(1, decoder.read_literal(1).or_accumulate(&mut res)); assert_eq!(5, decoder.read_literal(3).or_accumulate(&mut res)); assert_eq!(64, decoder.read_literal(8).or_accumulate(&mut res)); assert_eq!(185, decoder.read_literal(8).or_accumulate(&mut res)); assert_eq!(31, decoder.read_literal(8).or_accumulate(&mut res)); decoder.check(res, ()).unwrap(); } #[test] fn test_arithmetic_decoder_uninit() { let mut decoder = ArithmeticDecoder::new(); let mut res = decoder.start_accumulated_result(); let _ = decoder.read_flag().or_accumulate(&mut res); let result = decoder.check(res, ()); assert!(result.is_err()); } } image-webp-0.2.4/src/yuv.rs000064400000000000000000000352051046102023000136310ustar 00000000000000//! Utilities for doing the YUV -> RGB conversion //! The images are encoded in the Y'CbCr format as detailed here: //! so need to be converted to RGB to be displayed //! To do the YUV -> RGB conversion we need to first decide how to map the yuv values to the pixels //! The y buffer is the same size as the pixel buffer so that maps 1-1 but the //! u and v buffers are half the size of the pixel buffer so we need to scale it up //! The simple way to upscale is just to take each u/v value and associate it with the 4 //! pixels around it e.g. for a 4x4 image: //! //! |||||| //! |yyyy| //! |yyyy| //! |yyyy| //! |yyyy| //! |||||| //! //! ||||||| //! |uu|vv| //! |uu|vv| //! ||||||| //! //! Then each of the 2x2 pixels would match the u/v from the same quadrant //! //! However fancy upsampling is the default for libwebp which does a little more work to make the values smoother //! It interpolates u and v so that for e.g. the pixel 1 down and 1 from the left the u value //! would be (9*u0 + 3*u1 + 3*u2 + u3 + 8) / 16 and similar for the other pixels //! The edges are mirrored, so for the pixel 1 down and 0 from the left it uses (9*u0 + 3*u2 + 3*u0 + u2 + 8) / 16 /// `_mm_mulhi_epu16` emulation fn mulhi(v: u8, coeff: u16) -> i32 { ((u32::from(v) * u32::from(coeff)) >> 8) as i32 } /// This function has been rewritten to encourage auto-vectorization. /// /// Based on [src/dsp/yuv.h](https://github.com/webmproject/libwebp/blob/8534f53960befac04c9631e6e50d21dcb42dfeaf/src/dsp/yuv.h#L79) /// from the libwebp source. /// ```text /// const YUV_FIX2: i32 = 6; /// const YUV_MASK2: i32 = (256 << YUV_FIX2) - 1; /// fn clip(v: i32) -> u8 { /// if (v & !YUV_MASK2) == 0 { /// (v >> YUV_FIX2) as u8 /// } else if v < 0 { /// 0 /// } else { /// 255 /// } /// } /// ``` // Clippy suggests the clamp method, but it seems to optimize worse as of rustc 1.82.0 nightly. #[allow(clippy::manual_clamp)] fn clip(v: i32) -> u8 { const YUV_FIX2: i32 = 6; (v >> YUV_FIX2).max(0).min(255) as u8 } #[inline(always)] fn yuv_to_r(y: u8, v: u8) -> u8 { clip(mulhi(y, 19077) + mulhi(v, 26149) - 14234) } #[inline(always)] fn yuv_to_g(y: u8, u: u8, v: u8) -> u8 { clip(mulhi(y, 19077) - mulhi(u, 6419) - mulhi(v, 13320) + 8708) } #[inline(always)] fn yuv_to_b(y: u8, u: u8) -> u8 { clip(mulhi(y, 19077) + mulhi(u, 33050) - 17685) } /// Fills an rgb buffer with the image from the yuv buffers /// Size of the buffer is assumed to be correct /// BPP is short for bytes per pixel, allows both rgb and rgba to be decoded pub(crate) fn fill_rgb_buffer_fancy( buffer: &mut [u8], y_buffer: &[u8], u_buffer: &[u8], v_buffer: &[u8], width: usize, height: usize, buffer_width: usize, ) { // buffer width is always even so don't need to do div_ceil let chroma_buffer_width = buffer_width / 2; let chroma_width = width.div_ceil(2); // fill top row first since it only uses the top u/v row let top_row_y = &y_buffer[..width]; let top_row_u = &u_buffer[..chroma_width]; let top_row_v = &v_buffer[..chroma_width]; let top_row_buffer = &mut buffer[..width * BPP]; fill_row_fancy_with_1_uv_row::(top_row_buffer, top_row_y, top_row_u, top_row_v); let mut main_row_chunks = buffer[width * BPP..].chunks_exact_mut(width * BPP * 2); // the y buffer iterator limits the end of the row iterator so we need this end index let end_y_index = height * buffer_width; let mut main_y_chunks = y_buffer[buffer_width..end_y_index].chunks_exact(buffer_width * 2); let mut main_u_windows = u_buffer .windows(chroma_buffer_width * 2) .step_by(chroma_buffer_width); let mut main_v_windows = v_buffer .windows(chroma_buffer_width * 2) .step_by(chroma_buffer_width); for (((row_buffer, y_rows), u_rows), v_rows) in (&mut main_row_chunks) .zip(&mut main_y_chunks) .zip(&mut main_u_windows) .zip(&mut main_v_windows) { let (u_row_1, u_row_2) = u_rows.split_at(chroma_buffer_width); let (v_row_1, v_row_2) = v_rows.split_at(chroma_buffer_width); let (row_buf_1, row_buf_2) = row_buffer.split_at_mut(width * BPP); let (y_row_1, y_row_2) = y_rows.split_at(buffer_width); fill_row_fancy_with_2_uv_rows::( row_buf_1, &y_row_1[..width], &u_row_1[..chroma_width], &u_row_2[..chroma_width], &v_row_1[..chroma_width], &v_row_2[..chroma_width], ); fill_row_fancy_with_2_uv_rows::( row_buf_2, &y_row_2[..width], &u_row_2[..chroma_width], &u_row_1[..chroma_width], &v_row_2[..chroma_width], &v_row_1[..chroma_width], ); } let final_row_buffer = main_row_chunks.into_remainder(); // if the image has even height there will be one final row with only one u/v row matching it if !final_row_buffer.is_empty() { let final_y_row = main_y_chunks.remainder(); let chroma_height = height.div_ceil(2); let start_chroma_index = (chroma_height - 1) * chroma_buffer_width; let final_u_row = &u_buffer[start_chroma_index..]; let final_v_row = &v_buffer[start_chroma_index..]; fill_row_fancy_with_1_uv_row::( final_row_buffer, &final_y_row[..width], &final_u_row[..chroma_width], &final_v_row[..chroma_width], ); } } /// Fills a row with the fancy interpolation as detailed fn fill_row_fancy_with_2_uv_rows( row_buffer: &mut [u8], y_row: &[u8], u_row_1: &[u8], u_row_2: &[u8], v_row_1: &[u8], v_row_2: &[u8], ) { // need to do left pixel separately since it will only have one u/v value { let rgb1 = &mut row_buffer[0..3]; let y_value = y_row[0]; // first pixel uses the first u/v as the main one let u_value = get_fancy_chroma_value(u_row_1[0], u_row_1[0], u_row_2[0], u_row_2[0]); let v_value = get_fancy_chroma_value(v_row_1[0], v_row_1[0], v_row_2[0], v_row_2[0]); set_pixel(rgb1, y_value, u_value, v_value); } let rest_row_buffer = &mut row_buffer[BPP..]; let rest_y_row = &y_row[1..]; // we do two pixels at a time since they share the same u/v values let mut main_row_chunks = rest_row_buffer.chunks_exact_mut(BPP * 2); let mut main_y_chunks = rest_y_row.chunks_exact(2); for (((((rgb, y_val), u_val_1), u_val_2), v_val_1), v_val_2) in (&mut main_row_chunks) .zip(&mut main_y_chunks) .zip(u_row_1.windows(2)) .zip(u_row_2.windows(2)) .zip(v_row_1.windows(2)) .zip(v_row_2.windows(2)) { { let rgb1 = &mut rgb[0..3]; let y_value = y_val[0]; // first pixel uses the first u/v as the main one let u_value = get_fancy_chroma_value(u_val_1[0], u_val_1[1], u_val_2[0], u_val_2[1]); let v_value = get_fancy_chroma_value(v_val_1[0], v_val_1[1], v_val_2[0], v_val_2[1]); set_pixel(rgb1, y_value, u_value, v_value); } { let rgb2 = &mut rgb[BPP..]; let y_value = y_val[1]; let u_value = get_fancy_chroma_value(u_val_1[1], u_val_1[0], u_val_2[1], u_val_2[0]); let v_value = get_fancy_chroma_value(v_val_1[1], v_val_1[0], v_val_2[1], v_val_2[0]); set_pixel(rgb2, y_value, u_value, v_value); } } let final_pixel = main_row_chunks.into_remainder(); let final_y = main_y_chunks.remainder(); if let (rgb, [y_value]) = (final_pixel, final_y) { let final_u_1 = *u_row_1.last().unwrap(); let final_u_2 = *u_row_2.last().unwrap(); let final_v_1 = *v_row_1.last().unwrap(); let final_v_2 = *v_row_2.last().unwrap(); let rgb1 = &mut rgb[0..3]; // first pixel uses the first u/v as the main one let u_value = get_fancy_chroma_value(final_u_1, final_u_1, final_u_2, final_u_2); let v_value = get_fancy_chroma_value(final_v_1, final_v_1, final_v_2, final_v_2); set_pixel(rgb1, *y_value, u_value, v_value); } } fn fill_row_fancy_with_1_uv_row( row_buffer: &mut [u8], y_row: &[u8], u_row: &[u8], v_row: &[u8], ) { // doing left pixel first { let rgb1 = &mut row_buffer[0..3]; let y_value = y_row[0]; let u_value = u_row[0]; let v_value = v_row[0]; set_pixel(rgb1, y_value, u_value, v_value); } // two pixels at a time since they share the same u/v value let mut main_row_chunks = row_buffer[BPP..].chunks_exact_mut(BPP * 2); let mut main_y_row_chunks = y_row[1..].chunks_exact(2); for (((rgb, y_val), u_val), v_val) in (&mut main_row_chunks) .zip(&mut main_y_row_chunks) .zip(u_row.windows(2)) .zip(v_row.windows(2)) { { let rgb1 = &mut rgb[0..3]; let y_value = y_val[0]; // first pixel uses the first u/v as the main one let u_value = get_fancy_chroma_value(u_val[0], u_val[1], u_val[0], u_val[1]); let v_value = get_fancy_chroma_value(v_val[0], v_val[1], v_val[0], v_val[1]); set_pixel(rgb1, y_value, u_value, v_value); } { let rgb2 = &mut rgb[BPP..]; let y_value = y_val[1]; let u_value = get_fancy_chroma_value(u_val[1], u_val[0], u_val[1], u_val[0]); let v_value = get_fancy_chroma_value(v_val[1], v_val[0], v_val[1], v_val[0]); set_pixel(rgb2, y_value, u_value, v_value); } } let final_pixel = main_row_chunks.into_remainder(); let final_y = main_y_row_chunks.remainder(); if let (rgb, [final_y]) = (final_pixel, final_y) { let final_u = *u_row.last().unwrap(); let final_v = *v_row.last().unwrap(); set_pixel(rgb, *final_y, final_u, final_v); } } #[inline] fn get_fancy_chroma_value(main: u8, secondary1: u8, secondary2: u8, tertiary: u8) -> u8 { let val0 = u16::from(main); let val1 = u16::from(secondary1); let val2 = u16::from(secondary2); let val3 = u16::from(tertiary); ((9 * val0 + 3 * val1 + 3 * val2 + val3 + 8) / 16) as u8 } #[inline] fn set_pixel(rgb: &mut [u8], y: u8, u: u8, v: u8) { rgb[0] = yuv_to_r(y, v); rgb[1] = yuv_to_g(y, u, v); rgb[2] = yuv_to_b(y, u); } /// Simple conversion, not currently used but could add a config to allow for using the simple #[allow(unused)] pub(crate) fn fill_rgb_buffer_simple( buffer: &mut [u8], y_buffer: &[u8], u_buffer: &[u8], v_buffer: &[u8], width: usize, chroma_width: usize, buffer_width: usize, ) { let u_row_twice_iter = u_buffer .chunks_exact(buffer_width / 2) .flat_map(|n| std::iter::repeat(n).take(2)); let v_row_twice_iter = v_buffer .chunks_exact(buffer_width / 2) .flat_map(|n| std::iter::repeat(n).take(2)); for (((row, y_row), u_row), v_row) in buffer .chunks_exact_mut(width * BPP) .zip(y_buffer.chunks_exact(buffer_width)) .zip(u_row_twice_iter) .zip(v_row_twice_iter) { fill_rgba_row_simple::( &y_row[..width], &u_row[..chroma_width], &v_row[..chroma_width], row, ); } } fn fill_rgba_row_simple( y_vec: &[u8], u_vec: &[u8], v_vec: &[u8], rgba: &mut [u8], ) { // Fill 2 pixels per iteration: these pixels share `u` and `v` components let mut rgb_chunks = rgba.chunks_exact_mut(BPP * 2); let mut y_chunks = y_vec.chunks_exact(2); let mut u_iter = u_vec.iter(); let mut v_iter = v_vec.iter(); for (((rgb, y), &u), &v) in (&mut rgb_chunks) .zip(&mut y_chunks) .zip(&mut u_iter) .zip(&mut v_iter) { let coeffs = [ mulhi(v, 26149), mulhi(u, 6419), mulhi(v, 13320), mulhi(u, 33050), ]; let get_r = |y: u8| clip(mulhi(y, 19077) + coeffs[0] - 14234); let get_g = |y: u8| clip(mulhi(y, 19077) - coeffs[1] - coeffs[2] + 8708); let get_b = |y: u8| clip(mulhi(y, 19077) + coeffs[3] - 17685); let rgb1 = &mut rgb[0..3]; rgb1[0] = get_r(y[0]); rgb1[1] = get_g(y[0]); rgb1[2] = get_b(y[0]); let rgb2 = &mut rgb[BPP..]; rgb2[0] = get_r(y[1]); rgb2[1] = get_g(y[1]); rgb2[2] = get_b(y[1]); } let remainder = rgb_chunks.into_remainder(); if remainder.len() >= 3 { if let (Some(&y), Some(&u), Some(&v)) = ( y_chunks.remainder().iter().next(), u_iter.next(), v_iter.next(), ) { let coeffs = [ mulhi(v, 26149), mulhi(u, 6419), mulhi(v, 13320), mulhi(u, 33050), ]; remainder[0] = clip(mulhi(y, 19077) + coeffs[0] - 14234); remainder[1] = clip(mulhi(y, 19077) - coeffs[1] - coeffs[2] + 8708); remainder[2] = clip(mulhi(y, 19077) + coeffs[3] - 17685); } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_fancy_grid() { #[rustfmt::skip] let y_buffer = [ 77, 162, 202, 185, 28, 13, 199, 182, 135, 147, 164, 135, 66, 27, 171, 130, ]; #[rustfmt::skip] let u_buffer = [ 34, 101, 123, 163 ]; #[rustfmt::skip] let v_buffer = [ 97, 167, 149, 23, ]; let mut rgb_buffer = [0u8; 16 * 3]; fill_rgb_buffer_fancy::<3>(&mut rgb_buffer, &y_buffer, &u_buffer, &v_buffer, 4, 4, 4); #[rustfmt::skip] let upsampled_u_buffer = [ 34, 51, 84, 101, 56, 71, 101, 117, 101, 112, 136, 148, 123, 133, 153, 163, ]; #[rustfmt::skip] let upsampled_v_buffer = [ 97, 115, 150, 167, 110, 115, 126, 131, 136, 117, 78, 59, 149, 118, 55, 23, ]; let mut upsampled_rgb_buffer = [0u8; 16 * 3]; for (((rgb_val, y), u), v) in upsampled_rgb_buffer .chunks_exact_mut(3) .zip(y_buffer) .zip(upsampled_u_buffer) .zip(upsampled_v_buffer) { rgb_val[0] = yuv_to_r(y, v); rgb_val[1] = yuv_to_g(y, u, v); rgb_val[2] = yuv_to_b(y, u); } assert_eq!(rgb_buffer, upsampled_rgb_buffer); } #[test] fn test_yuv_conversions() { let (y, u, v) = (203, 40, 42); assert_eq!(yuv_to_r(y, v), 80); assert_eq!(yuv_to_g(y, u, v), 255); assert_eq!(yuv_to_b(y, u), 40); } }