jpeg-encoder-0.6.0/.cargo_vcs_info.json0000644000000001360000000000100134010ustar { "git": { "sha1": "c533b68b03aadea0ae2733ca3fde74e430958e65" }, "path_in_vcs": "" }jpeg-encoder-0.6.0/.github/workflows/rust.yml000064400000000000000000000020261046102023000173060ustar 00000000000000name: Rust on: push: branches: [ main ] pull_request: branches: [ main ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest strategy: matrix: rust: ["1.61", stable, nightly] steps: - name: Installing Rust toolchain uses: actions-rs/toolchain@v1 with: override: true toolchain: ${{ matrix.rust }} - uses: actions/checkout@v2 - name: Build run: cargo build --verbose --features simd - name: Run tests run: cargo test --verbose --features simd no_std: runs-on: ubuntu-latest strategy: matrix: rust: [ "1.61", stable, nightly ] steps: - name: Installing Rust toolchain uses: actions-rs/toolchain@v1 with: override: true toolchain: ${{ matrix.rust }} - uses: actions/checkout@v2 - name: Build run: cargo build --verbose --no-default-features - name: Run tests run: cargo test --verbose --no-default-features --tests jpeg-encoder-0.6.0/.gitignore000064400000000000000000000000221046102023000141530ustar 00000000000000/target Cargo.lockjpeg-encoder-0.6.0/Cargo.toml0000644000000020310000000000100113730ustar # 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" name = "jpeg-encoder" version = "0.6.0" authors = ["Volker Ströbel "] description = "JPEG encoder" readme = "README.md" keywords = [ "jpg", "jpeg", "encoder", "image", ] categories = ["multimedia::images"] license = "MIT OR Apache-2.0" repository = "https://github.com/vstroebel/jpeg-encoder" [profile.dev] opt-level = 1 [[bench]] name = "encode" harness = false [dependencies] [dev-dependencies.jpeg-decoder] version = "0.3" default-features = false [features] default = ["std"] simd = ["std"] std = [] jpeg-encoder-0.6.0/Cargo.toml.orig000064400000000000000000000012441046102023000150610ustar 00000000000000[package] name = "jpeg-encoder" version = "0.6.0" authors = ["Volker Ströbel "] edition = "2021" license = "MIT OR Apache-2.0" description = "JPEG encoder" categories = ["multimedia::images"] keywords = ["jpg", "jpeg", "encoder", "image"] readme = "README.md" repository = "https://github.com/vstroebel/jpeg-encoder" [features] default = ["std"] simd = ["std"] std = [] [dependencies] [dev-dependencies] jpeg-decoder = { version = "0.3", default-features = false } #criterion = { version = "0.5", default-features = false, features = ["plotters", "cargo_bench_support"]} [profile.dev] opt-level = 1 [[bench]] name = "encode" harness = false jpeg-encoder-0.6.0/LICENSE-APACHE000064400000000000000000000247151046102023000141260ustar 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 APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.jpeg-encoder-0.6.0/LICENSE-MIT000064400000000000000000000021001046102023000136160ustar 00000000000000Copyright (c) 2021 Volker Ströbel 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.jpeg-encoder-0.6.0/README.md000064400000000000000000000035331046102023000134540ustar 00000000000000# JPEG encoder [![docs.rs badge](https://docs.rs/jpeg-encoder/badge.svg)](https://docs.rs/jpeg-encoder/) [![crates.io badge](https://img.shields.io/crates/v/jpeg-encoder.svg)](https://crates.io/crates/jpeg-encoder/) [![Rust](https://github.com/vstroebel/jpeg-encoder/actions/workflows/rust.yml/badge.svg)](https://github.com/vstroebel/jpeg-encoder/actions/workflows/rust.yml) A JPEG encoder written in Rust featuring: - Baseline and progressive compression - Chroma subsampling - Optimized huffman tables - 1, 3 and 4 component colorspaces - Restart interval - Custom quantization tables - AVX2 based optimizations (Optional) - Support for no_std + alloc - No `unsafe` by default (Enabling the `simd` feature adds unsafe code) ## Example ```rust use jpeg_encoder::{Encoder, ColorType}; // An array with 4 pixels in RGB format. let data = [ 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, ]; // Create new encoder that writes to a file with maximum quality (100) let mut encoder = Encoder::new_file("some.jpeg", 100)?; // Encode the data with dimension 2x2 encoder.encode(&data, 2, 2, ColorType::Rgb)?; ``` ## Crate features - `std` (default): Enables functionality dependent on the std lib - `simd`: Enables SIMD optimizations (implies `std` and only AVX2 as for now) ## Minimum Supported Version of Rust (MSRV) This crate needs at least 1.61 or higher. ## License This project is licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) ## Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in jpeg-encoder by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. jpeg-encoder-0.6.0/benches/encode.rs000064400000000000000000000132161046102023000154060ustar 00000000000000use criterion::{black_box, criterion_group, criterion_main, Criterion}; use jpeg_encoder::{ColorType, Encoder, SamplingFactor}; use std::time::Duration; fn create_bench_img() -> (Vec, u16, u16) { let width = 2000; let height = 1800; let mut data = Vec::with_capacity(width * height * 3); for y in 0..height { for x in 0..width { if (x * y) % 13 == 0 { data.push(0); data.push(0); data.push(0); } else if (x * y) % 17 == 0 { data.push(255); data.push(255); data.push(255); } else if (x * y) % 19 == 0 { data.push(255); data.push(0); data.push(0); } else if (x * y) % 21 == 0 { data.push(0); data.push(0); data.push(255); } else if (x * y) % 23 == 0 { data.push(0); data.push(255); data.push(0); } else if (x * y) % 25 == 0 { data.push(96); data.push(255); data.push(96); } else if (x * y) % 27 == 0 { data.push(255); data.push(96); data.push(96); } else if (x * y) % 29 == 0 { data.push(96); data.push(96); data.push(255); } else { data.push((x % 256) as u8); data.push((x % 256) as u8); data.push(((x * y) % 256) as u8); } } } (data, width as u16, height as u16) } fn encode_rgb_100(res: &mut Vec, data: &[u8], width: u16, height: u16) { let encoder = Encoder::new(res, 100); encoder.encode(data, width, height, ColorType::Rgb).unwrap(); } fn encode_rgb_4x1(res: &mut Vec, data: &[u8], width: u16, height: u16) { let mut encoder = Encoder::new(res, 80); encoder.set_sampling_factor(SamplingFactor::F_4_1); encoder.encode(data, width, height, ColorType::Rgb).unwrap(); } fn encode_rgb_progressive(res: &mut Vec, data: &[u8], width: u16, height: u16) { let mut encoder = Encoder::new(res, 80); encoder.set_progressive(true); encoder.encode(data, width, height, ColorType::Rgb).unwrap(); } fn encode_rgb_optimized(res: &mut Vec, data: &[u8], width: u16, height: u16) { let mut encoder = Encoder::new(res, 100); encoder.set_optimized_huffman_tables(true); encoder.encode(data, width, height, ColorType::Rgb).unwrap(); } fn encode_rgb_optimized_progressive(res: &mut Vec, data: &[u8], width: u16, height: u16) { let mut encoder = Encoder::new(res, 100); encoder.set_optimized_huffman_tables(true); encoder.set_progressive(true); encoder.encode(data, width, height, ColorType::Rgb).unwrap(); } fn criterion_benchmark(c: &mut Criterion) { let mut res = Vec::with_capacity(32 * 1024 * 1024); let (data, width, height) = create_bench_img(); let mut group = c.benchmark_group("encode rgb"); group.measurement_time(Duration::from_secs(45)); group.warm_up_time(Duration::from_secs(10)); group.bench_function("encode rgb 100", |b| { b.iter(|| { res.clear(); encode_rgb_100( black_box(&mut res), black_box(&data), black_box(width), black_box(height), ) }) }); group.bench_function("encode rgb 4x1", |b| { b.iter(|| { res.clear(); encode_rgb_4x1( black_box(&mut res), black_box(&data), black_box(width), black_box(height), ) }) }); group.bench_function("encode rgb progressive", |b| { b.iter(|| { res.clear(); encode_rgb_progressive( black_box(&mut res), black_box(&data), black_box(width), black_box(height), ) }) }); group.bench_function("encode rgb optimized", |b| { b.iter(|| { res.clear(); encode_rgb_optimized( black_box(&mut res), black_box(&data), black_box(width), black_box(height), ) }) }); group.bench_function("encode rgb optimized progressive", |b| { b.iter(|| { res.clear(); encode_rgb_optimized_progressive( black_box(&mut res), black_box(&data), black_box(width), black_box(height), ) }) }); group.bench_function("encode rgb mixed", |b| { b.iter(|| { res.clear(); encode_rgb_100( black_box(&mut res), black_box(&data), black_box(width), black_box(height), ); res.clear(); encode_rgb_4x1( black_box(&mut res), black_box(&data), black_box(width), black_box(height), ); res.clear(); encode_rgb_progressive( black_box(&mut res), black_box(&data), black_box(width), black_box(height), ); res.clear(); encode_rgb_optimized_progressive( black_box(&mut res), black_box(&data), black_box(width), black_box(height), ); }) }); group.finish(); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); jpeg-encoder-0.6.0/src/avx2/fdct.rs000064400000000000000000000366041046102023000151370ustar 00000000000000/* * Ported from mozjpeg / jfdctint-avx2.asm to rust * Copyright 2009 Pierre Ossman for Cendio AB * Copyright (C) 2009, 2016, 2018, 2020, D. R. Commander. * * Based on the x86 SIMD extension for IJG JPEG library * Copyright (C) 1999-2006, MIYASAKA Masaru. */ #[cfg(target_arch = "x86")] use core::arch::x86::{ __m256i, _mm256_add_epi16, _mm256_add_epi32, _mm256_loadu_si256, _mm256_madd_epi16, _mm256_packs_epi32, _mm256_permute2x128_si256, _mm256_permute4x64_epi64, _mm256_set_epi16, _mm256_set_epi32, _mm256_sign_epi16, _mm256_slli_epi16, _mm256_srai_epi16, _mm256_srai_epi32, _mm256_storeu_si256, _mm256_sub_epi16, _mm256_unpackhi_epi16, _mm256_unpackhi_epi32, _mm256_unpacklo_epi16, _mm256_unpacklo_epi32, }; #[cfg(target_arch = "x86_64")] use core::arch::x86_64::{ __m256i, _mm256_add_epi16, _mm256_add_epi32, _mm256_loadu_si256, _mm256_madd_epi16, _mm256_packs_epi32, _mm256_permute2x128_si256, _mm256_permute4x64_epi64, _mm256_set_epi16, _mm256_set_epi32, _mm256_sign_epi16, _mm256_slli_epi16, _mm256_srai_epi16, _mm256_srai_epi32, _mm256_storeu_si256, _mm256_sub_epi16, _mm256_unpackhi_epi16, _mm256_unpackhi_epi32, _mm256_unpacklo_epi16, _mm256_unpacklo_epi32, }; const CONST_BITS: i32 = 13; const PASS1_BITS: i32 = 2; // FIX(0.298631336) const F_0_298: i16 = 2446; // FIX(0.390180644) const F_0_390: i16 = 3196; // FIX(0.541196100) const F_0_541: i16 = 4433; // FIX(0.765366865) const F_0_765: i16 = 6270; //FIX(0.899976223) const F_0_899: i16 = 7373; //FIX(1.175875602) const F_1_175: i16 = 9633; //FIX(1.501321110) const F_1_501: i16 = 12299; //FIX(1.847759065) const F_1_847: i16 = 15137; //FIX(1.961570560) const F_1_961: i16 = 16069; //FIX(2.053119869) const F_2_053: i16 = 16819; //FIX(2.562915447) const F_2_562: i16 = 20995; //FIX(3.072711026) const F_3_072: i16 = 25172; const DESCALE_P1: i32 = CONST_BITS - PASS1_BITS; const DESCALE_P2: i32 = CONST_BITS + PASS1_BITS; #[inline(always)] pub fn fdct_avx2(data: &mut [i16; 64]) { unsafe { fdct_avx2_internal(data); } } #[target_feature(enable = "avx2")] unsafe fn fdct_avx2_internal(data: &mut [i16; 64]) { #[allow(non_snake_case)] #[inline(always)] unsafe fn PW_F130_F054_MF130_F054() -> __m256i { _mm256_set_epi16( F_0_541, F_0_541 - F_1_847, F_0_541, F_0_541 - F_1_847, F_0_541, F_0_541 - F_1_847, F_0_541, F_0_541 - F_1_847, F_0_541, F_0_541 + F_0_765, F_0_541, F_0_541 + F_0_765, F_0_541, F_0_541 + F_0_765, F_0_541, F_0_541 + F_0_765, ) } #[allow(non_snake_case)] #[inline(always)] unsafe fn PW_MF078_F117_F078_F117() -> __m256i { _mm256_set_epi16( F_1_175, F_1_175 - F_0_390, F_1_175, F_1_175 - F_0_390, F_1_175, F_1_175 - F_0_390, F_1_175, F_1_175 - F_0_390, F_1_175, F_1_175 - F_1_961, F_1_175, F_1_175 - F_1_961, F_1_175, F_1_175 - F_1_961, F_1_175, F_1_175 - F_1_961, ) } #[allow(non_snake_case)] #[inline(always)] unsafe fn PW_MF060_MF089_MF050_MF256() -> __m256i { _mm256_set_epi16( -F_2_562, F_2_053 - F_2_562, -F_2_562, F_2_053 - F_2_562, -F_2_562, F_2_053 - F_2_562, -F_2_562, F_2_053 - F_2_562, -F_0_899, F_0_298 - F_0_899, -F_0_899, F_0_298 - F_0_899, -F_0_899, F_0_298 - F_0_899, -F_0_899, F_0_298 - F_0_899, ) } #[allow(non_snake_case)] #[inline(always)] unsafe fn PW_F050_MF256_F060_MF089() -> __m256i { _mm256_set_epi16( -F_0_899, F_1_501 - F_0_899, -F_0_899, F_1_501 - F_0_899, -F_0_899, F_1_501 - F_0_899, -F_0_899, F_1_501 - F_0_899, -F_2_562, F_3_072 - F_2_562, -F_2_562, F_3_072 - F_2_562, -F_2_562, F_3_072 - F_2_562, -F_2_562, F_3_072 - F_2_562, ) } #[allow(non_snake_case)] #[inline(always)] unsafe fn PD_DESCALE_P(first_pass: bool) -> __m256i { if first_pass { _mm256_set_epi32( 1 << (DESCALE_P1 - 1), 1 << (DESCALE_P1 - 1), 1 << (DESCALE_P1 - 1), 1 << (DESCALE_P1 - 1), 1 << (DESCALE_P1 - 1), 1 << (DESCALE_P1 - 1), 1 << (DESCALE_P1 - 1), 1 << (DESCALE_P1 - 1), ) } else { _mm256_set_epi32( 1 << (DESCALE_P2 - 1), 1 << (DESCALE_P2 - 1), 1 << (DESCALE_P2 - 1), 1 << (DESCALE_P2 - 1), 1 << (DESCALE_P2 - 1), 1 << (DESCALE_P2 - 1), 1 << (DESCALE_P2 - 1), 1 << (DESCALE_P2 - 1), ) } } #[allow(non_snake_case)] #[inline(always)] unsafe fn PW_DESCALE_P2X() -> __m256i { _mm256_set_epi32( 1 << (PASS1_BITS - 1), 1 << (PASS1_BITS - 1), 1 << (PASS1_BITS - 1), 1 << (PASS1_BITS - 1), 1 << (PASS1_BITS - 1), 1 << (PASS1_BITS - 1), 1 << (PASS1_BITS - 1), 1 << (PASS1_BITS - 1), ) } // In-place 8x8x16-bit matrix transpose using AVX2 instructions #[inline(always)] unsafe fn do_transpose( i1: __m256i, i2: __m256i, i3: __m256i, i4: __m256i, ) -> (__m256i, __m256i, __m256i, __m256i) { //i1=(00 01 02 03 04 05 06 07 40 41 42 43 44 45 46 47) //i2=(10 11 12 13 14 15 16 17 50 51 52 53 54 55 56 57) //i3=(20 21 22 23 24 25 26 27 60 61 62 63 64 65 66 67) //i4=(30 31 32 33 34 35 36 37 70 71 72 73 74 75 76 77) let t5 = _mm256_unpacklo_epi16(i1, i2); let t6 = _mm256_unpackhi_epi16(i1, i2); let t7 = _mm256_unpacklo_epi16(i3, i4); let t8 = _mm256_unpackhi_epi16(i3, i4); // transpose coefficients(phase 1) // t1=(00 10 01 11 02 12 03 13 40 50 41 51 42 52 43 53) // t2=(04 14 05 15 06 16 07 17 44 54 45 55 46 56 47 57) // t3=(20 30 21 31 22 32 23 33 60 70 61 71 62 72 63 73) // t4=(24 34 25 35 26 36 27 37 64 74 65 75 66 76 67 77) let t1 = _mm256_unpacklo_epi32(t5, t7); let t2 = _mm256_unpackhi_epi32(t5, t7); let t3 = _mm256_unpacklo_epi32(t6, t8); let t4 = _mm256_unpackhi_epi32(t6, t8); // transpose coefficients(phase 2) // t5=(00 10 20 30 01 11 21 31 40 50 60 70 41 51 61 71) // t6=(02 12 22 32 03 13 23 33 42 52 62 72 43 53 63 73) // t7=(04 14 24 34 05 15 25 35 44 54 64 74 45 55 65 75) // t8=(06 16 26 36 07 17 27 37 46 56 66 76 47 57 67 77) ( _mm256_permute4x64_epi64(t1, 0x8D), _mm256_permute4x64_epi64(t2, 0x8D), _mm256_permute4x64_epi64(t3, 0xD8), _mm256_permute4x64_epi64(t4, 0xD8), ) } // In-place 8x8x16-bit accurate integer forward DCT using AVX2 instructions #[inline(always)] unsafe fn do_dct( first_pass: bool, i1: __m256i, i2: __m256i, i3: __m256i, i4: __m256i, ) -> (__m256i, __m256i, __m256i, __m256i) { let t5 = _mm256_sub_epi16(i1, i4); // data1_0 - data6_7 = tmp6_7 let t6 = _mm256_add_epi16(i1, i4); // data1_0 + data6_7 = tmp1_0 let t7 = _mm256_add_epi16(i2, i3); // data3_2 + data4_5 = tmp3_2 let t8 = _mm256_sub_epi16(i2, i3); // data3_2 - data4_5 = tmp4_5 // Even part let t6 = _mm256_permute2x128_si256(t6, t6, 0x01); // t6=tmp0_1 let t1 = _mm256_add_epi16(t6, t7); // t1 = tmp0_1 + tmp3_2 = tmp10_11 let t6 = _mm256_sub_epi16(t6, t7); // t6 = tmp0_1 - tmp3_2 = tmp13_12 let t7 = _mm256_permute2x128_si256(t1, t1, 0x01); // t7 = tmp11_10 let t1 = _mm256_sign_epi16( t1, _mm256_set_epi16(-1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1), ); // tmp10_neg11 let t7 = _mm256_add_epi16(t7, t1); // t7 = (tmp10 + tmp11)_(tmp10 - tmp11) let t1 = if first_pass { _mm256_slli_epi16(t7, PASS1_BITS) } else { let t7 = _mm256_add_epi16(t7, PW_DESCALE_P2X()); _mm256_srai_epi16(t7, PASS1_BITS) }; // (Original) // z1 = (tmp12 + tmp13) * 0.541196100; // data2 = z1 + tmp13 * 0.765366865; // data6 = z1 + tmp12 * -1.847759065; // // (This implementation) // data2 = tmp13 * (0.541196100 + 0.765366865) + tmp12 * 0.541196100; // data6 = tmp13 * 0.541196100 + tmp12 * (0.541196100 - 1.847759065); let t7 = _mm256_permute2x128_si256(t6, t6, 0x01); // t7 = tmp12_13 let t2 = _mm256_unpacklo_epi16(t6, t7); let t6 = _mm256_unpackhi_epi16(t6, t7); let t2 = _mm256_madd_epi16(t2, PW_F130_F054_MF130_F054()); // t2 = data2_6L let t6 = _mm256_madd_epi16(t6, PW_F130_F054_MF130_F054()); // t6 = data2_6H let t2 = _mm256_add_epi32(t2, PD_DESCALE_P(first_pass)); let t6 = _mm256_add_epi32(t6, PD_DESCALE_P(first_pass)); let t2 = if first_pass { _mm256_srai_epi32(t2, DESCALE_P1) } else { _mm256_srai_epi32(t2, DESCALE_P2) }; let t6 = if first_pass { _mm256_srai_epi32(t6, DESCALE_P1) } else { _mm256_srai_epi32(t6, DESCALE_P2) }; let t3 = _mm256_packs_epi32(t2, t6); // t6 = data2_6 // Odd part let t7 = _mm256_add_epi16(t8, t5); // t7 = tmp4_5 + tmp6_7 = z3_4 // (Original) // z5 = (z3 + z4) * 1.175875602; // z3 = z3 * -1.961570560; // z4 = z4 * -0.390180644; // z3 += z5; // z4 += z5; // // (This implementation) // z3 = z3 * (1.175875602 - 1.961570560) + z4 * 1.175875602; // z4 = z3 * 1.175875602 + z4 * (1.175875602 - 0.390180644); let t2 = _mm256_permute2x128_si256(t7, t7, 0x01); // t2 = z4_3 let t6 = _mm256_unpacklo_epi16(t7, t2); let t7 = _mm256_unpackhi_epi16(t7, t2); let t6 = _mm256_madd_epi16(t6, PW_MF078_F117_F078_F117()); // t6 = z3_4L let t7 = _mm256_madd_epi16(t7, PW_MF078_F117_F078_F117()); // t7 = z3_4H // (Original) // z1 = tmp4 + tmp7; // z2 = tmp5 + tmp6; // tmp4 = tmp4 * 0.298631336; // tmp5 = tmp5 * 2.053119869; // tmp6 = tmp6 * 3.072711026; // tmp7 = tmp7 * 1.501321110; // z1 = z1 * -0.899976223; // z2 = z2 * -2.562915447; // data7 = tmp4 + z1 + z3; // data5 = tmp5 + z2 + z4; // data3 = tmp6 + z2 + z3; // data1 = tmp7 + z1 + z4; // // (This implementation) // tmp4 = tmp4 * (0.298631336 - 0.899976223) + tmp7 * -0.899976223; // tmp5 = tmp5 * (2.053119869 - 2.562915447) + tmp6 * -2.562915447; // tmp6 = tmp5 * -2.562915447 + tmp6 * (3.072711026 - 2.562915447); // tmp7 = tmp4 * -0.899976223 + tmp7 * (1.501321110 - 0.899976223); // data7 = tmp4 + z3; // data5 = tmp5 + z4; // data3 = tmp6 + z3; // data1 = tmp7 + z4; let t4 = _mm256_permute2x128_si256(t5, t5, 0x01); // t4 = tmp7_6 let t2 = _mm256_unpacklo_epi16(t8, t4); let t4 = _mm256_unpackhi_epi16(t8, t4); let t2 = _mm256_madd_epi16(t2, PW_MF060_MF089_MF050_MF256()); //t2 = tmp4_5L let t4 = _mm256_madd_epi16(t4, PW_MF060_MF089_MF050_MF256()); // t4 = tmp4_5H let t2 = _mm256_add_epi32(t2, t6); // t2 = data7_5L let t4 = _mm256_add_epi32(t4, t7); // t4 = data7_5H let t2 = _mm256_add_epi32(t2, PD_DESCALE_P(first_pass)); let t4 = _mm256_add_epi32(t4, PD_DESCALE_P(first_pass)); let t2 = if first_pass { _mm256_srai_epi32(t2, DESCALE_P1) } else { _mm256_srai_epi32(t2, DESCALE_P2) }; let t4 = if first_pass { _mm256_srai_epi32(t4, DESCALE_P1) } else { _mm256_srai_epi32(t4, DESCALE_P2) }; let t4 = _mm256_packs_epi32(t2, t4); // t4 = data7_5 let t2 = _mm256_permute2x128_si256(t8, t8, 0x01); // t2 = tmp5_4 let t8 = _mm256_unpacklo_epi16(t5, t2); let t5 = _mm256_unpackhi_epi16(t5, t2); let t8 = _mm256_madd_epi16(t8, PW_F050_MF256_F060_MF089()); // t8 = tmp6_7L let t5 = _mm256_madd_epi16(t5, PW_F050_MF256_F060_MF089()); // t5 = tmp6_7H let t8 = _mm256_add_epi32(t8, t6); // t8 = data3_1L let t5 = _mm256_add_epi32(t5, t7); // t5 = data3_1H let t8 = _mm256_add_epi32(t8, PD_DESCALE_P(first_pass)); let t5 = _mm256_add_epi32(t5, PD_DESCALE_P(first_pass)); let t8 = if first_pass { _mm256_srai_epi32(t8, DESCALE_P1) } else { _mm256_srai_epi32(t8, DESCALE_P2) }; let t5 = if first_pass { _mm256_srai_epi32(t5, DESCALE_P1) } else { _mm256_srai_epi32(t5, DESCALE_P2) }; let t2 = _mm256_packs_epi32(t8, t5); // t2 = data3_1 (t1, t2, t3, t4) } let in_data = core::mem::transmute(data.as_mut_ptr()); let ymm4 = _mm256_loadu_si256(in_data); let ymm5 = _mm256_loadu_si256(in_data.add(1)); let ymm6 = _mm256_loadu_si256(in_data.add(2)); let ymm7 = _mm256_loadu_si256(in_data.add(3)); // ---- Pass 1: process rows. // ymm4=(00 01 02 03 04 05 06 07 10 11 12 13 14 15 16 17) // ymm5=(20 21 22 23 24 25 26 27 30 31 32 33 34 35 36 37) // ymm6=(40 41 42 43 44 45 46 47 50 51 52 53 54 55 56 57) // ymm7=(60 61 62 63 64 65 66 67 70 71 72 73 74 75 76 77) let ymm0 = _mm256_permute2x128_si256(ymm4, ymm6, 0x20); let ymm1 = _mm256_permute2x128_si256(ymm4, ymm6, 0x31); let ymm2 = _mm256_permute2x128_si256(ymm5, ymm7, 0x20); let ymm3 = _mm256_permute2x128_si256(ymm5, ymm7, 0x31); // ymm0=(00 01 02 03 04 05 06 07 40 41 42 43 44 45 46 47) // ymm1=(10 11 12 13 14 15 16 17 50 51 52 53 54 55 56 57) // ymm2=(20 21 22 23 24 25 26 27 60 61 62 63 64 65 66 67) // ymm3=(30 31 32 33 34 35 36 37 70 71 72 73 74 75 76 77) let (ymm0, ymm1, ymm2, ymm3) = do_transpose(ymm0, ymm1, ymm2, ymm3); let (ymm0, ymm1, ymm2, ymm3) = do_dct(true, ymm0, ymm1, ymm2, ymm3); // ---- Pass 2: process columns. let ymm4 = _mm256_permute2x128_si256(ymm1, ymm3, 0x20); // ymm4=data3_7 let ymm1 = _mm256_permute2x128_si256(ymm1, ymm3, 0x31); // ymm1=data1_5 let (ymm0, ymm1, ymm2, ymm4) = do_transpose(ymm0, ymm1, ymm2, ymm4); let (ymm0, ymm1, ymm2, ymm4) = do_dct(false, ymm0, ymm1, ymm2, ymm4); let ymm3 = _mm256_permute2x128_si256(ymm0, ymm1, 0x30); // ymm3=data0_1 let ymm5 = _mm256_permute2x128_si256(ymm2, ymm1, 0x20); // ymm5=data2_3 let ymm6 = _mm256_permute2x128_si256(ymm0, ymm4, 0x31); // ymm6=data4_5 let ymm7 = _mm256_permute2x128_si256(ymm2, ymm4, 0x21); // ymm7=data6_7 let out_data = core::mem::transmute(data.as_mut_ptr()); _mm256_storeu_si256(out_data, ymm3); _mm256_storeu_si256(out_data.add(1), ymm5); _mm256_storeu_si256(out_data.add(2), ymm6); _mm256_storeu_si256(out_data.add(3), ymm7); } jpeg-encoder-0.6.0/src/avx2/ycbcr.rs000064400000000000000000000140071046102023000153120ustar 00000000000000#[cfg(target_arch = "x86")] use core::arch::x86::{ __m256i, _mm256_add_epi32, _mm256_mullo_epi32, _mm256_set1_epi32, _mm256_set_epi32, _mm256_srli_epi32, _mm256_sub_epi32, }; #[cfg(target_arch = "x86_64")] use core::arch::x86_64::{ __m256i, _mm256_add_epi32, _mm256_mullo_epi32, _mm256_set1_epi32, _mm256_set_epi32, _mm256_srli_epi32, _mm256_sub_epi32, }; use alloc::vec::Vec; use crate::{rgb_to_ycbcr, ImageBuffer, JpegColorType}; macro_rules! ycbcr_image_avx2 { ($name:ident, $num_colors:expr, $o1:expr, $o2:expr, $o3:expr) => { pub(crate) struct $name<'a>(pub &'a [u8], pub u16, pub u16); impl<'a> $name<'a> { #[target_feature(enable = "avx2")] unsafe fn fill_buffers_avx2(&self, y: u16, buffers: &mut [Vec; 4]) { unsafe fn load3(data: *const u8) -> __m256i { _mm256_set_epi32( *data as i32, *data.offset(1 * $num_colors) as i32, *data.offset(2 * $num_colors) as i32, *data.offset(3 * $num_colors) as i32, *data.offset(4 * $num_colors) as i32, *data.offset(5 * $num_colors) as i32, *data.offset(6 * $num_colors) as i32, *data.offset(7 * $num_colors) as i32, ) } let mut y_buffer = buffers[0].as_mut_ptr().add(buffers[0].len()); buffers[0].set_len(buffers[0].len() + self.width() as usize); let mut cb_buffer = buffers[1].as_mut_ptr().add(buffers[1].len()); buffers[1].set_len(buffers[1].len() + self.width() as usize); let mut cr_buffer = buffers[2].as_mut_ptr().add(buffers[2].len()); buffers[2].set_len(buffers[2].len() + self.width() as usize); let ymulr = _mm256_set1_epi32(19595); let ymulg = _mm256_set1_epi32(38470); let ymulb = _mm256_set1_epi32(7471); let cbmulr = _mm256_set1_epi32(-11059); let cbmulg = _mm256_set1_epi32(21709); let cbmulb = _mm256_set1_epi32(32768); let crmulr = _mm256_set1_epi32(32768); let crmulg = _mm256_set1_epi32(27439); let crmulb = _mm256_set1_epi32(5329); let mut data = self .0 .as_ptr() .offset((y as isize * self.1 as isize * $num_colors)); for _ in 0..self.width() / 8 { let r = load3(data.offset($o1)); let g = load3(data.offset($o2)); let b = load3(data.offset($o3)); data = data.add($num_colors * 8); let yr = _mm256_mullo_epi32(ymulr, r); let yg = _mm256_mullo_epi32(ymulg, g); let yb = _mm256_mullo_epi32(ymulb, b); let y = _mm256_add_epi32(_mm256_add_epi32(yr, yg), yb); let y = _mm256_add_epi32(y, _mm256_set1_epi32(0x7FFF)); let y = _mm256_srli_epi32(y, 16); let y: [i32; 8] = core::mem::transmute(y); let cbr = _mm256_mullo_epi32(cbmulr, r); let cbg = _mm256_mullo_epi32(cbmulg, g); let cbb = _mm256_mullo_epi32(cbmulb, b); let cb = _mm256_add_epi32(_mm256_sub_epi32(cbr, cbg), cbb); let cb = _mm256_add_epi32(cb, _mm256_set1_epi32(128 << 16)); let cb = _mm256_add_epi32(cb, _mm256_set1_epi32(0x7FFF)); let cb = _mm256_srli_epi32(cb, 16); let cb: [i32; 8] = core::mem::transmute(cb); let crr = _mm256_mullo_epi32(crmulr, r); let crg = _mm256_mullo_epi32(crmulg, g); let crb = _mm256_mullo_epi32(crmulb, b); let cr = _mm256_sub_epi32(_mm256_sub_epi32(crr, crg), crb); let cr = _mm256_add_epi32(cr, _mm256_set1_epi32(128 << 16)); let cr = _mm256_add_epi32(cr, _mm256_set1_epi32(0x7FFF)); let cr = _mm256_srli_epi32(cr, 16); let cr: [i32; 8] = core::mem::transmute(cr); for y in y.iter().rev() { *y_buffer = *y as u8; y_buffer = y_buffer.offset(1); } for cb in cb.iter().rev() { *cb_buffer = *cb as u8; cb_buffer = cb_buffer.offset(1); } for cr in cr.iter().rev() { *cr_buffer = *cr as u8; cr_buffer = cr_buffer.offset(1); } } for _ in 0..self.width() % 8 { let (y, cb, cr) = rgb_to_ycbcr(*data.offset($o1), *data.offset($o2), *data.offset($o3)); data = data.add($num_colors); *y_buffer = y; y_buffer = y_buffer.offset(1); *cb_buffer = cb; cb_buffer = cb_buffer.offset(1); *cr_buffer = cr; cr_buffer = cr_buffer.offset(1); } } } impl<'a> ImageBuffer for $name<'a> { fn get_jpeg_color_type(&self) -> JpegColorType { JpegColorType::Ycbcr } fn width(&self) -> u16 { self.1 } fn height(&self) -> u16 { self.2 } #[inline(always)] fn fill_buffers(&self, y: u16, buffers: &mut [Vec; 4]) { unsafe { self.fill_buffers_avx2(y, buffers); } } } }; } ycbcr_image_avx2!(RgbImageAVX2, 3, 0, 1, 2); ycbcr_image_avx2!(RgbaImageAVX2, 4, 0, 1, 2); ycbcr_image_avx2!(BgrImageAVX2, 3, 2, 1, 0); ycbcr_image_avx2!(BgraImageAVX2, 4, 2, 1, 0); jpeg-encoder-0.6.0/src/avx2.rs000064400000000000000000000004231046102023000142050ustar 00000000000000mod fdct; mod ycbcr; use crate::encoder::Operations; pub(crate) use fdct::fdct_avx2; pub(crate) use ycbcr::*; pub(crate) struct AVX2Operations; impl Operations for AVX2Operations { #[inline(always)] fn fdct(data: &mut [i16; 64]) { fdct_avx2(data); } } jpeg-encoder-0.6.0/src/encoder.rs000064400000000000000000001241211046102023000147460ustar 00000000000000use crate::fdct::fdct; use crate::huffman::{CodingClass, HuffmanTable}; use crate::image_buffer::*; use crate::marker::Marker; use crate::quantization::{QuantizationTable, QuantizationTableType}; use crate::writer::{JfifWrite, JfifWriter, ZIGZAG}; use crate::{Density, EncodingError}; use alloc::vec; use alloc::vec::Vec; #[cfg(feature = "std")] use std::io::BufWriter; #[cfg(feature = "std")] use std::fs::File; #[cfg(feature = "std")] use std::path::Path; /// # Color types used in encoding #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum JpegColorType { /// One component grayscale colorspace Luma, /// Three component YCbCr colorspace Ycbcr, /// 4 Component CMYK colorspace Cmyk, /// 4 Component YCbCrK colorspace Ycck, } impl JpegColorType { pub(crate) fn get_num_components(self) -> usize { use JpegColorType::*; match self { Luma => 1, Ycbcr => 3, Cmyk | Ycck => 4, } } } /// # Color types for input images /// /// Available color input formats for [Encoder::encode]. Other types can be used /// by implementing an [ImageBuffer](crate::ImageBuffer). #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ColorType { /// Grayscale with 1 byte per pixel Luma, /// RGB with 3 bytes per pixel Rgb, /// Red, Green, Blue with 4 bytes per pixel. The alpha channel will be ignored during encoding. Rgba, /// RGB with 3 bytes per pixel Bgr, /// RGBA with 4 bytes per pixel. The alpha channel will be ignored during encoding. Bgra, /// YCbCr with 3 bytes per pixel. Ycbcr, /// CMYK with 4 bytes per pixel. Cmyk, /// CMYK with 4 bytes per pixel. Encoded as YCCK (YCbCrK) CmykAsYcck, /// YCCK (YCbCrK) with 4 bytes per pixel. Ycck, } impl ColorType { pub(crate) fn get_bytes_per_pixel(self) -> usize { use ColorType::*; match self { Luma => 1, Rgb | Bgr | Ycbcr => 3, Rgba | Bgra | Cmyk | CmykAsYcck | Ycck => 4, } } } #[repr(u8)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] /// # Sampling factors for chroma subsampling /// /// ## Warning /// Sampling factor of 4 are not supported by all decoders or applications #[allow(non_camel_case_types)] pub enum SamplingFactor { F_1_1 = 1 << 4 | 1, F_2_1 = 2 << 4 | 1, F_1_2 = 1 << 4 | 2, F_2_2 = 2 << 4 | 2, F_4_1 = 4 << 4 | 1, F_4_2 = 4 << 4 | 2, F_1_4 = 1 << 4 | 4, F_2_4 = 2 << 4 | 4, /// Alias for F_1_1 R_4_4_4 = 0x80 | 1 << 4 | 1, /// Alias for F_1_2 R_4_4_0 = 0x80 | 1 << 4 | 2, /// Alias for F_1_4 R_4_4_1 = 0x80 | 1 << 4 | 4, /// Alias for F_2_1 R_4_2_2 = 0x80 | 2 << 4 | 1, /// Alias for F_2_2 R_4_2_0 = 0x80 | 2 << 4 | 2, /// Alias for F_2_4 R_4_2_1 = 0x80 | 2 << 4 | 4, /// Alias for F_4_1 R_4_1_1 = 0x80 | 4 << 4 | 1, /// Alias for F_4_2 R_4_1_0 = 0x80 | 4 << 4 | 2, } impl SamplingFactor { /// Get variant for supplied factors or None if not supported pub fn from_factors(horizontal: u8, vertical: u8) -> Option { use SamplingFactor::*; match (horizontal, vertical) { (1, 1) => Some(F_1_1), (1, 2) => Some(F_1_2), (1, 4) => Some(F_1_4), (2, 1) => Some(F_2_1), (2, 2) => Some(F_2_2), (2, 4) => Some(F_2_4), (4, 1) => Some(F_4_1), (4, 2) => Some(F_4_2), _ => None, } } pub(crate) fn get_sampling_factors(self) -> (u8, u8) { let value = self as u8; ((value >> 4) & 0x07, value & 0xf) } pub(crate) fn supports_interleaved(self) -> bool { use SamplingFactor::*; // Interleaved mode is only supported with h/v sampling factors of 1 or 2. // Sampling factors of 4 needs sequential encoding matches!( self, F_1_1 | F_2_1 | F_1_2 | F_2_2 | R_4_4_4 | R_4_4_0 | R_4_2_2 | R_4_2_0 ) } } pub(crate) struct Component { pub id: u8, pub quantization_table: u8, pub dc_huffman_table: u8, pub ac_huffman_table: u8, pub horizontal_sampling_factor: u8, pub vertical_sampling_factor: u8, } macro_rules! add_component { ($components:expr, $id:expr, $dest:expr, $h_sample:expr, $v_sample:expr) => { $components.push(Component { id: $id, quantization_table: $dest, dc_huffman_table: $dest, ac_huffman_table: $dest, horizontal_sampling_factor: $h_sample, vertical_sampling_factor: $v_sample, }); }; } /// # The JPEG encoder pub struct Encoder { writer: JfifWriter, density: Density, quality: u8, components: Vec, quantization_tables: [QuantizationTableType; 2], huffman_tables: [(HuffmanTable, HuffmanTable); 2], sampling_factor: SamplingFactor, progressive_scans: Option, restart_interval: Option, optimize_huffman_table: bool, app_segments: Vec<(u8, Vec)>, } impl Encoder { /// Create a new encoder with the given quality /// /// The quality must be between 1 and 100 where 100 is the highest image quality.
/// By default, quality settings below 90 use a chroma subsampling (2x2 / 4:2:0) which can /// be changed with [set_sampling_factor](Encoder::set_sampling_factor) pub fn new(w: W, quality: u8) -> Encoder { let huffman_tables = [ ( HuffmanTable::default_luma_dc(), HuffmanTable::default_luma_ac(), ), ( HuffmanTable::default_chroma_dc(), HuffmanTable::default_chroma_ac(), ), ]; let quantization_tables = [ QuantizationTableType::Default, QuantizationTableType::Default, ]; let sampling_factor = if quality < 90 { SamplingFactor::F_2_2 } else { SamplingFactor::F_1_1 }; Encoder { writer: JfifWriter::new(w), density: Density::None, quality, components: vec![], quantization_tables, huffman_tables, sampling_factor, progressive_scans: None, restart_interval: None, optimize_huffman_table: false, app_segments: Vec::new(), } } /// Set pixel density for the image /// /// By default, this value is None which is equal to "1 pixel per pixel". pub fn set_density(&mut self, density: Density) { self.density = density; } /// Return pixel density pub fn density(&self) -> Density { self.density } /// Set chroma subsampling factor pub fn set_sampling_factor(&mut self, sampling: SamplingFactor) { self.sampling_factor = sampling; } /// Get chroma subsampling factor pub fn sampling_factor(&self) -> SamplingFactor { self.sampling_factor } /// Set quantization tables for luma and chroma components pub fn set_quantization_tables( &mut self, luma: QuantizationTableType, chroma: QuantizationTableType, ) { self.quantization_tables = [luma, chroma]; } /// Get configured quantization tables pub fn quantization_tables(&self) -> &[QuantizationTableType; 2] { &self.quantization_tables } /// Controls if progressive encoding is used. /// /// By default, progressive encoding uses 4 scans.
/// Use [set_progressive_scans](Encoder::set_progressive_scans) to use a different number of scans pub fn set_progressive(&mut self, progressive: bool) { self.progressive_scans = if progressive { Some(4) } else { None }; } /// Set number of scans per component for progressive encoding /// /// Number of scans must be between 2 and 64. /// There is at least one scan for the DC coefficients and one for the remaining 63 AC coefficients. /// /// # Panics /// If number of scans is not within valid range pub fn set_progressive_scans(&mut self, scans: u8) { assert!( (2..=64).contains(&scans), "Invalid number of scans: {}", scans ); self.progressive_scans = Some(scans); } /// Return number of progressive scans if progressive encoding is enabled pub fn progressive_scans(&self) -> Option { self.progressive_scans } /// Set restart interval /// /// Set numbers of MCUs between restart markers. pub fn set_restart_interval(&mut self, interval: u16) { self.restart_interval = if interval == 0 { None } else { Some(interval) }; } /// Return the restart interval pub fn restart_interval(&self) -> Option { self.restart_interval } /// Set if optimized huffman table should be created /// /// Optimized tables result in slightly smaller file sizes but decrease encoding performance. pub fn set_optimized_huffman_tables(&mut self, optimize_huffman_table: bool) { self.optimize_huffman_table = optimize_huffman_table; } /// Returns if optimized huffman table should be generated pub fn optimized_huffman_tables(&self) -> bool { self.optimize_huffman_table } /// Appends a custom app segment to the JFIF file /// /// Segment numbers need to be in the range between 1 and 15
/// The maximum allowed data length is 2^16 - 2 bytes. /// /// # Errors /// /// Returns an error if the segment number is invalid or data exceeds the allowed size pub fn add_app_segment(&mut self, segment_nr: u8, data: &[u8]) -> Result<(), EncodingError> { if segment_nr == 0 || segment_nr > 15 { Err(EncodingError::InvalidAppSegment(segment_nr)) } else if data.len() > 65533 { Err(EncodingError::AppSegmentTooLarge(data.len())) } else { self.app_segments.push((segment_nr, data.to_vec())); Ok(()) } } /// Add an ICC profile /// /// The maximum allowed data length is 16,707,345 bytes. /// /// # Errors /// /// Returns an Error if the data exceeds the maximum size for the ICC profile pub fn add_icc_profile(&mut self, data: &[u8]) -> Result<(), EncodingError> { // Based on https://www.color.org/ICC_Minor_Revision_for_Web.pdf // B.4 Embedding ICC profiles in JFIF files const MARKER: &[u8; 12] = b"ICC_PROFILE\0"; const MAX_CHUNK_LENGTH: usize = 65535 - 2 - 12 - 2; let num_chunks = ceil_div(data.len(), MAX_CHUNK_LENGTH); // Sequence number is stored as a byte and starts with 1 if num_chunks >= 255 { return Err(EncodingError::IccTooLarge(data.len())); } let mut chunk_data = Vec::with_capacity(MAX_CHUNK_LENGTH); for (i, data) in data.chunks(MAX_CHUNK_LENGTH).enumerate() { chunk_data.clear(); chunk_data.extend_from_slice(MARKER); chunk_data.push(i as u8 + 1); chunk_data.push(num_chunks as u8); chunk_data.extend_from_slice(data); self.add_app_segment(2, &chunk_data)?; } Ok(()) } /// Encode an image /// /// Data format and length must conform to specified width, height and color type. pub fn encode( self, data: &[u8], width: u16, height: u16, color_type: ColorType, ) -> Result<(), EncodingError> { let required_data_len = width as usize * height as usize * color_type.get_bytes_per_pixel(); if data.len() < required_data_len { return Err(EncodingError::BadImageData { length: data.len(), required: required_data_len, }); } #[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))] { if std::is_x86_feature_detected!("avx2") { use crate::avx2::*; return match color_type { ColorType::Luma => self .encode_image_internal::<_, AVX2Operations>(GrayImage(data, width, height)), ColorType::Rgb => self.encode_image_internal::<_, AVX2Operations>( RgbImageAVX2(data, width, height), ), ColorType::Rgba => self.encode_image_internal::<_, AVX2Operations>( RgbaImageAVX2(data, width, height), ), ColorType::Bgr => self.encode_image_internal::<_, AVX2Operations>( BgrImageAVX2(data, width, height), ), ColorType::Bgra => self.encode_image_internal::<_, AVX2Operations>( BgraImageAVX2(data, width, height), ), ColorType::Ycbcr => self.encode_image_internal::<_, AVX2Operations>( YCbCrImage(data, width, height), ), ColorType::Cmyk => self .encode_image_internal::<_, AVX2Operations>(CmykImage(data, width, height)), ColorType::CmykAsYcck => self.encode_image_internal::<_, AVX2Operations>( CmykAsYcckImage(data, width, height), ), ColorType::Ycck => self .encode_image_internal::<_, AVX2Operations>(YcckImage(data, width, height)), }; } } match color_type { ColorType::Luma => self.encode_image(GrayImage(data, width, height))?, ColorType::Rgb => self.encode_image(RgbImage(data, width, height))?, ColorType::Rgba => self.encode_image(RgbaImage(data, width, height))?, ColorType::Bgr => self.encode_image(BgrImage(data, width, height))?, ColorType::Bgra => self.encode_image(BgraImage(data, width, height))?, ColorType::Ycbcr => self.encode_image(YCbCrImage(data, width, height))?, ColorType::Cmyk => self.encode_image(CmykImage(data, width, height))?, ColorType::CmykAsYcck => self.encode_image(CmykAsYcckImage(data, width, height))?, ColorType::Ycck => self.encode_image(YcckImage(data, width, height))?, } Ok(()) } /// Encode an image pub fn encode_image(self, image: I) -> Result<(), EncodingError> { #[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))] { if std::is_x86_feature_detected!("avx2") { use crate::avx2::*; return self.encode_image_internal::<_, AVX2Operations>(image); } } self.encode_image_internal::<_, DefaultOperations>(image) } fn encode_image_internal( mut self, image: I, ) -> Result<(), EncodingError> { if image.width() == 0 || image.height() == 0 { return Err(EncodingError::ZeroImageDimensions { width: image.width(), height: image.height(), }); } let q_tables = [ QuantizationTable::new_with_quality(&self.quantization_tables[0], self.quality, true), QuantizationTable::new_with_quality(&self.quantization_tables[1], self.quality, false), ]; let jpeg_color_type = image.get_jpeg_color_type(); self.init_components(jpeg_color_type); self.writer.write_marker(Marker::SOI)?; self.writer.write_header(&self.density)?; if jpeg_color_type == JpegColorType::Cmyk { //Set ColorTransform info to "Unknown" let app_14 = b"Adobe\0\0\0\0\0\0\0"; self.writer .write_segment(Marker::APP(14), app_14.as_ref())?; } else if jpeg_color_type == JpegColorType::Ycck { //Set ColorTransform info to YCCK let app_14 = b"Adobe\0\0\0\0\0\0\x02"; self.writer .write_segment(Marker::APP(14), app_14.as_ref())?; } for (nr, data) in &self.app_segments { self.writer.write_segment(Marker::APP(*nr), data)?; } if let Some(scans) = self.progressive_scans { self.encode_image_progressive::<_, OP>(image, scans, &q_tables)?; } else if self.optimize_huffman_table || !self.sampling_factor.supports_interleaved() { self.encode_image_sequential::<_, OP>(image, &q_tables)?; } else { self.encode_image_interleaved::<_, OP>(image, &q_tables)?; } self.writer.write_marker(Marker::EOI)?; Ok(()) } fn init_components(&mut self, color: JpegColorType) { let (horizontal_sampling_factor, vertical_sampling_factor) = self.sampling_factor.get_sampling_factors(); match color { JpegColorType::Luma => { add_component!(self.components, 0, 0, 1, 1); } JpegColorType::Ycbcr => { add_component!( self.components, 0, 0, horizontal_sampling_factor, vertical_sampling_factor ); add_component!(self.components, 1, 1, 1, 1); add_component!(self.components, 2, 1, 1, 1); } JpegColorType::Cmyk => { add_component!(self.components, 0, 1, 1, 1); add_component!(self.components, 1, 1, 1, 1); add_component!(self.components, 2, 1, 1, 1); add_component!( self.components, 3, 0, horizontal_sampling_factor, vertical_sampling_factor ); } JpegColorType::Ycck => { add_component!( self.components, 0, 0, horizontal_sampling_factor, vertical_sampling_factor ); add_component!(self.components, 1, 1, 1, 1); add_component!(self.components, 2, 1, 1, 1); add_component!( self.components, 3, 0, horizontal_sampling_factor, vertical_sampling_factor ); } } } fn get_max_sampling_size(&self) -> (usize, usize) { let max_h_sampling = self.components.iter().fold(1, |value, component| { value.max(component.horizontal_sampling_factor) }); let max_v_sampling = self.components.iter().fold(1, |value, component| { value.max(component.vertical_sampling_factor) }); (usize::from(max_h_sampling), usize::from(max_v_sampling)) } fn write_frame_header( &mut self, image: &I, q_tables: &[QuantizationTable; 2], ) -> Result<(), EncodingError> { self.writer.write_frame_header( image.width(), image.height(), &self.components, self.progressive_scans.is_some(), )?; self.writer.write_quantization_segment(0, &q_tables[0])?; self.writer.write_quantization_segment(1, &q_tables[1])?; self.writer .write_huffman_segment(CodingClass::Dc, 0, &self.huffman_tables[0].0)?; self.writer .write_huffman_segment(CodingClass::Ac, 0, &self.huffman_tables[0].1)?; if image.get_jpeg_color_type().get_num_components() >= 3 { self.writer .write_huffman_segment(CodingClass::Dc, 1, &self.huffman_tables[1].0)?; self.writer .write_huffman_segment(CodingClass::Ac, 1, &self.huffman_tables[1].1)?; } if let Some(restart_interval) = self.restart_interval { self.writer.write_dri(restart_interval)?; } Ok(()) } fn init_rows(&mut self, buffer_size: usize) -> [Vec; 4] { // To simplify the code and to give the compiler more infos to optimize stuff we always initialize 4 components // Resource overhead should be minimal because an empty Vec doesn't allocate match self.components.len() { 1 => [ Vec::with_capacity(buffer_size), Vec::new(), Vec::new(), Vec::new(), ], 3 => [ Vec::with_capacity(buffer_size), Vec::with_capacity(buffer_size), Vec::with_capacity(buffer_size), Vec::new(), ], 4 => [ Vec::with_capacity(buffer_size), Vec::with_capacity(buffer_size), Vec::with_capacity(buffer_size), Vec::with_capacity(buffer_size), ], len => unreachable!("Unsupported component length: {}", len), } } /// Encode all components with one scan /// /// This is only valid for sampling factors of 1 and 2 fn encode_image_interleaved( &mut self, image: I, q_tables: &[QuantizationTable; 2], ) -> Result<(), EncodingError> { self.write_frame_header(&image, q_tables)?; self.writer .write_scan_header(&self.components.iter().collect::>(), None)?; let (max_h_sampling, max_v_sampling) = self.get_max_sampling_size(); let width = image.width(); let height = image.height(); let num_cols = ceil_div(usize::from(width), 8 * max_h_sampling); let num_rows = ceil_div(usize::from(height), 8 * max_v_sampling); let buffer_width = num_cols * 8 * max_h_sampling; let buffer_size = buffer_width * 8 * max_v_sampling; let mut row: [Vec<_>; 4] = self.init_rows(buffer_size); let mut prev_dc = [0i16; 4]; let restart_interval = self.restart_interval.unwrap_or(0); let mut restarts = 0; let mut restarts_to_go = restart_interval; for block_y in 0..num_rows { for r in &mut row { r.clear(); } for y in 0..(8 * max_v_sampling) { let y = y + block_y * 8 * max_v_sampling; let y = (y.min(height as usize - 1)) as u16; image.fill_buffers(y, &mut row); for _ in usize::from(width)..buffer_width { for channel in &mut row { if !channel.is_empty() { channel.push(channel[channel.len() - 1]); } } } } for block_x in 0..num_cols { if restart_interval > 0 && restarts_to_go == 0 { self.writer.finalize_bit_buffer()?; self.writer .write_marker(Marker::RST((restarts % 8) as u8))?; prev_dc[0] = 0; prev_dc[1] = 0; prev_dc[2] = 0; prev_dc[3] = 0; } for (i, component) in self.components.iter().enumerate() { for v_offset in 0..component.vertical_sampling_factor as usize { for h_offset in 0..component.horizontal_sampling_factor as usize { let mut block = get_block( &row[i], block_x * 8 * max_h_sampling + (h_offset * 8), v_offset * 8, max_h_sampling / component.horizontal_sampling_factor as usize, max_v_sampling / component.vertical_sampling_factor as usize, buffer_width, ); OP::fdct(&mut block); let mut q_block = [0i16; 64]; OP::quantize_block( &block, &mut q_block, &q_tables[component.quantization_table as usize], ); self.writer.write_block( &q_block, prev_dc[i], &self.huffman_tables[component.dc_huffman_table as usize].0, &self.huffman_tables[component.ac_huffman_table as usize].1, )?; prev_dc[i] = q_block[0]; } } } if restart_interval > 0 { if restarts_to_go == 0 { restarts_to_go = restart_interval; restarts += 1; restarts &= 7; } restarts_to_go -= 1; } } } self.writer.finalize_bit_buffer()?; Ok(()) } /// Encode components with one scan per component fn encode_image_sequential( &mut self, image: I, q_tables: &[QuantizationTable; 2], ) -> Result<(), EncodingError> { let blocks = self.encode_blocks::<_, OP>(&image, q_tables); if self.optimize_huffman_table { self.optimize_huffman_table(&blocks); } self.write_frame_header(&image, q_tables)?; for (i, component) in self.components.iter().enumerate() { let restart_interval = self.restart_interval.unwrap_or(0); let mut restarts = 0; let mut restarts_to_go = restart_interval; self.writer.write_scan_header(&[component], None)?; let mut prev_dc = 0; for block in &blocks[i] { if restart_interval > 0 && restarts_to_go == 0 { self.writer.finalize_bit_buffer()?; self.writer .write_marker(Marker::RST((restarts % 8) as u8))?; prev_dc = 0; } self.writer.write_block( block, prev_dc, &self.huffman_tables[component.dc_huffman_table as usize].0, &self.huffman_tables[component.ac_huffman_table as usize].1, )?; prev_dc = block[0]; if restart_interval > 0 { if restarts_to_go == 0 { restarts_to_go = restart_interval; restarts += 1; restarts &= 7; } restarts_to_go -= 1; } } self.writer.finalize_bit_buffer()?; } Ok(()) } /// Encode image in progressive mode /// /// This only support spectral selection for now fn encode_image_progressive( &mut self, image: I, scans: u8, q_tables: &[QuantizationTable; 2], ) -> Result<(), EncodingError> { let blocks = self.encode_blocks::<_, OP>(&image, q_tables); if self.optimize_huffman_table { self.optimize_huffman_table(&blocks); } self.write_frame_header(&image, q_tables)?; // Phase 1: DC Scan // Only the DC coefficients can be transfer in the first component scans for (i, component) in self.components.iter().enumerate() { self.writer.write_scan_header(&[component], Some((0, 0)))?; let restart_interval = self.restart_interval.unwrap_or(0); let mut restarts = 0; let mut restarts_to_go = restart_interval; let mut prev_dc = 0; for block in &blocks[i] { if restart_interval > 0 && restarts_to_go == 0 { self.writer.finalize_bit_buffer()?; self.writer .write_marker(Marker::RST((restarts % 8) as u8))?; prev_dc = 0; } self.writer.write_dc( block[0], prev_dc, &self.huffman_tables[component.dc_huffman_table as usize].0, )?; prev_dc = block[0]; if restart_interval > 0 { if restarts_to_go == 0 { restarts_to_go = restart_interval; restarts += 1; restarts &= 7; } restarts_to_go -= 1; } } self.writer.finalize_bit_buffer()?; } // Phase 2: AC scans let scans = scans as usize - 1; let values_per_scan = 64 / scans; for scan in 0..scans { let start = (scan * values_per_scan).max(1); let end = if scan == scans - 1 { // ensure last scan is always transfers the remaining coefficients 64 } else { (scan + 1) * values_per_scan }; for (i, component) in self.components.iter().enumerate() { let restart_interval = self.restart_interval.unwrap_or(0); let mut restarts = 0; let mut restarts_to_go = restart_interval; self.writer .write_scan_header(&[component], Some((start as u8, end as u8 - 1)))?; for block in &blocks[i] { if restart_interval > 0 && restarts_to_go == 0 { self.writer.finalize_bit_buffer()?; self.writer .write_marker(Marker::RST((restarts % 8) as u8))?; } self.writer.write_ac_block( block, start, end, &self.huffman_tables[component.ac_huffman_table as usize].1, )?; if restart_interval > 0 { if restarts_to_go == 0 { restarts_to_go = restart_interval; restarts += 1; restarts &= 7; } restarts_to_go -= 1; } } self.writer.finalize_bit_buffer()?; } } Ok(()) } fn encode_blocks( &mut self, image: &I, q_tables: &[QuantizationTable; 2], ) -> [Vec<[i16; 64]>; 4] { let width = image.width(); let height = image.height(); let (max_h_sampling, max_v_sampling) = self.get_max_sampling_size(); let num_cols = ceil_div(usize::from(width), 8 * max_h_sampling) * max_h_sampling; let num_rows = ceil_div(usize::from(height), 8 * max_v_sampling) * max_v_sampling; debug_assert!(num_cols > 0); debug_assert!(num_rows > 0); let buffer_width = num_cols * 8; let buffer_size = num_cols * num_rows * 64; let mut row: [Vec<_>; 4] = self.init_rows(buffer_size); for y in 0..num_rows * 8 { let y = (y.min(usize::from(height) - 1)) as u16; image.fill_buffers(y, &mut row); for _ in usize::from(width)..num_cols * 8 { for channel in &mut row { if !channel.is_empty() { channel.push(channel[channel.len() - 1]); } } } } let num_cols = ceil_div(usize::from(width), 8); let num_rows = ceil_div(usize::from(height), 8); debug_assert!(num_cols > 0); debug_assert!(num_rows > 0); let mut blocks: [Vec<_>; 4] = self.init_block_buffers(buffer_size / 64); for (i, component) in self.components.iter().enumerate() { let h_scale = max_h_sampling / component.horizontal_sampling_factor as usize; let v_scale = max_v_sampling / component.vertical_sampling_factor as usize; let cols = ceil_div(num_cols, h_scale); let rows = ceil_div(num_rows, v_scale); debug_assert!(cols > 0); debug_assert!(rows > 0); for block_y in 0..rows { for block_x in 0..cols { let mut block = get_block( &row[i], block_x * 8 * h_scale, block_y * 8 * v_scale, h_scale, v_scale, buffer_width, ); OP::fdct(&mut block); let mut q_block = [0i16; 64]; OP::quantize_block( &block, &mut q_block, &q_tables[component.quantization_table as usize], ); blocks[i].push(q_block); } } } blocks } fn init_block_buffers(&mut self, buffer_size: usize) -> [Vec<[i16; 64]>; 4] { // To simplify the code and to give the compiler more infos to optimize stuff we always initialize 4 components // Resource overhead should be minimal because an empty Vec doesn't allocate match self.components.len() { 1 => [ Vec::with_capacity(buffer_size), Vec::new(), Vec::new(), Vec::new(), ], 3 => [ Vec::with_capacity(buffer_size), Vec::with_capacity(buffer_size), Vec::with_capacity(buffer_size), Vec::new(), ], 4 => [ Vec::with_capacity(buffer_size), Vec::with_capacity(buffer_size), Vec::with_capacity(buffer_size), Vec::with_capacity(buffer_size), ], len => unreachable!("Unsupported component length: {}", len), } } // Create new huffman tables optimized for this image fn optimize_huffman_table(&mut self, blocks: &[Vec<[i16; 64]>; 4]) { // TODO: Find out if it's possible to reuse some code from the writer let max_tables = self.components.len().min(2) as u8; for table in 0..max_tables { let mut dc_freq = [0u32; 257]; dc_freq[256] = 1; let mut ac_freq = [0u32; 257]; ac_freq[256] = 1; let mut had_ac = false; let mut had_dc = false; for (i, component) in self.components.iter().enumerate() { if component.dc_huffman_table == table { had_dc = true; let mut prev_dc = 0; debug_assert!(!blocks[i].is_empty()); for block in &blocks[i] { let value = block[0]; let diff = value - prev_dc; let num_bits = get_num_bits(diff); dc_freq[num_bits as usize] += 1; prev_dc = value; } } if component.ac_huffman_table == table { had_ac = true; if let Some(scans) = self.progressive_scans { let scans = scans as usize - 1; let values_per_scan = 64 / scans; for scan in 0..scans { let start = (scan * values_per_scan).max(1); let end = if scan == scans - 1 { // Due to rounding we might need to transfer more than values_per_scan values in the last scan 64 } else { (scan + 1) * values_per_scan }; debug_assert!(!blocks[i].is_empty()); for block in &blocks[i] { let mut zero_run = 0; for &value in &block[start..end] { if value == 0 { zero_run += 1; } else { while zero_run > 15 { ac_freq[0xF0] += 1; zero_run -= 16; } let num_bits = get_num_bits(value); let symbol = (zero_run << 4) | num_bits; ac_freq[symbol as usize] += 1; zero_run = 0; } } if zero_run > 0 { ac_freq[0] += 1; } } } } else { for block in &blocks[i] { let mut zero_run = 0; for &value in &block[1..] { if value == 0 { zero_run += 1; } else { while zero_run > 15 { ac_freq[0xF0] += 1; zero_run -= 16; } let num_bits = get_num_bits(value); let symbol = (zero_run << 4) | num_bits; ac_freq[symbol as usize] += 1; zero_run = 0; } } if zero_run > 0 { ac_freq[0] += 1; } } } } } assert!(had_dc, "Missing DC data for table {}", table); assert!(had_ac, "Missing AC data for table {}", table); self.huffman_tables[table as usize] = ( HuffmanTable::new_optimized(dc_freq), HuffmanTable::new_optimized(ac_freq), ); } } } #[cfg(feature = "std")] impl Encoder> { /// Create a new decoder that writes into a file /// /// See [new](Encoder::new) for further information. /// /// # Errors /// /// Returns an `IoError(std::io::Error)` if the file can't be created pub fn new_file>( path: P, quality: u8, ) -> Result>, EncodingError> { let file = File::create(path)?; let buf = BufWriter::new(file); Ok(Self::new(buf, quality)) } } fn get_block( data: &[u8], start_x: usize, start_y: usize, col_stride: usize, row_stride: usize, width: usize, ) -> [i16; 64] { let mut block = [0i16; 64]; for y in 0..8 { for x in 0..8 { let ix = start_x + (x * col_stride); let iy = start_y + (y * row_stride); block[y * 8 + x] = (data[iy * width + ix] as i16) - 128; } } block } fn ceil_div(value: usize, div: usize) -> usize { value / div + usize::from(value % div != 0) } fn get_num_bits(mut value: i16) -> u8 { if value < 0 { value = -value; } let mut num_bits = 0; while value > 0 { num_bits += 1; value >>= 1; } num_bits } pub(crate) trait Operations { #[inline(always)] fn fdct(data: &mut [i16; 64]) { fdct(data); } #[inline(always)] fn quantize_block(block: &[i16; 64], q_block: &mut [i16; 64], table: &QuantizationTable) { for i in 0..64 { let z = ZIGZAG[i] as usize; q_block[i] = table.quantize(block[z], z); } } } pub(crate) struct DefaultOperations; impl Operations for DefaultOperations {} #[cfg(test)] mod tests { use alloc::vec; use crate::encoder::get_num_bits; use crate::writer::get_code; use crate::{Encoder, SamplingFactor}; #[test] fn test_get_num_bits() { let min_max = 2i16.pow(13); for value in -min_max..=min_max { let num_bits1 = get_num_bits(value); let (num_bits2, _) = get_code(value); assert_eq!( num_bits1, num_bits2, "Difference in num bits for value {}: {} vs {}", value, num_bits1, num_bits2 ); } } #[test] fn sampling_factors() { assert_eq!(SamplingFactor::F_1_1.get_sampling_factors(), (1, 1)); assert_eq!(SamplingFactor::F_2_1.get_sampling_factors(), (2, 1)); assert_eq!(SamplingFactor::F_1_2.get_sampling_factors(), (1, 2)); assert_eq!(SamplingFactor::F_2_2.get_sampling_factors(), (2, 2)); assert_eq!(SamplingFactor::F_4_1.get_sampling_factors(), (4, 1)); assert_eq!(SamplingFactor::F_4_2.get_sampling_factors(), (4, 2)); assert_eq!(SamplingFactor::F_1_4.get_sampling_factors(), (1, 4)); assert_eq!(SamplingFactor::F_2_4.get_sampling_factors(), (2, 4)); assert_eq!(SamplingFactor::R_4_4_4.get_sampling_factors(), (1, 1)); assert_eq!(SamplingFactor::R_4_4_0.get_sampling_factors(), (1, 2)); assert_eq!(SamplingFactor::R_4_4_1.get_sampling_factors(), (1, 4)); assert_eq!(SamplingFactor::R_4_2_2.get_sampling_factors(), (2, 1)); assert_eq!(SamplingFactor::R_4_2_0.get_sampling_factors(), (2, 2)); assert_eq!(SamplingFactor::R_4_2_1.get_sampling_factors(), (2, 4)); assert_eq!(SamplingFactor::R_4_1_1.get_sampling_factors(), (4, 1)); assert_eq!(SamplingFactor::R_4_1_0.get_sampling_factors(), (4, 2)); } #[test] fn test_set_progressive() { let mut encoder = Encoder::new(vec![], 100); encoder.set_progressive(true); assert_eq!(encoder.progressive_scans(), Some(4)); encoder.set_progressive(false); assert_eq!(encoder.progressive_scans(), None); } } jpeg-encoder-0.6.0/src/error.rs000064400000000000000000000044171046102023000144650ustar 00000000000000use alloc::fmt::Display; #[cfg(feature = "std")] use std::error::Error; /// # The error type for encoding #[derive(Debug)] pub enum EncodingError { /// An invalid app segment number has been used InvalidAppSegment(u8), /// App segment exceeds maximum allowed data length AppSegmentTooLarge(usize), /// Color profile exceeds maximum allowed data length IccTooLarge(usize), /// Image data is too short BadImageData { length: usize, required: usize }, /// Width or height is zero ZeroImageDimensions { width: u16, height: u16 }, /// An io error occurred during writing #[cfg(feature = "std")] IoError(std::io::Error), /// An io error occurred during writing (Should be used in no_std cases instead of IoError) Write(alloc::string::String), } #[cfg(feature = "std")] impl From for EncodingError { fn from(err: std::io::Error) -> EncodingError { EncodingError::IoError(err) } } impl Display for EncodingError { fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result { use EncodingError::*; match self { InvalidAppSegment(nr) => write!(f, "Invalid app segment number: {}", nr), AppSegmentTooLarge(length) => write!( f, "App segment exceeds maximum allowed data length of 65533: {}", length ), IccTooLarge(length) => write!( f, "ICC profile exceeds maximum allowed data length: {}", length ), BadImageData { length, required } => write!( f, "Image data too small for dimensions and color_type: {} need at least {}", length, required ), ZeroImageDimensions { width, height } => { write!(f, "Image dimensions must be non zero: {}x{}", width, height) } #[cfg(feature = "std")] IoError(err) => err.fmt(f), Write(err) => write!(f, "{}", err), } } } #[cfg(feature = "std")] impl Error for EncodingError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { EncodingError::IoError(err) => Some(err), _ => None, } } } jpeg-encoder-0.6.0/src/fdct.rs000064400000000000000000000275531046102023000142620ustar 00000000000000/* * Ported from mozjpeg to rust * * This file was part of the Independent JPEG Group's software: * Copyright (C) 1991-1996, Thomas G. Lane. * libjpeg-turbo Modifications: * Copyright (C) 2015, 2020, D. R. Commander. * * Conditions of distribution and use: * In plain English: * * 1. We don't promise that this software works. (But if you find any bugs, * please let us know!) * 2. You can use this software for whatever you want. You don't have to pay us. * 3. You may not pretend that you wrote this software. If you use it in a * program, you must acknowledge somewhere in your documentation that * you've used the IJG code. * * In legalese: * * The authors make NO WARRANTY or representation, either express or implied, * with respect to this software, its quality, accuracy, merchantability, or * fitness for a particular purpose. This software is provided "AS IS", and you, * its user, assume the entire risk as to its quality and accuracy. * * This software is copyright (C) 1991-2020, Thomas G. Lane, Guido Vollbeding. * All Rights Reserved except as specified below. * * Permission is hereby granted to use, copy, modify, and distribute this * software (or portions thereof) for any purpose, without fee, subject to these * conditions: * (1) If any part of the source code for this software is distributed, then this * README file must be included, with this copyright and no-warranty notice * unaltered; and any additions, deletions, or changes to the original files * must be clearly indicated in accompanying documentation. * (2) If only executable code is distributed, then the accompanying * documentation must state that "this software is based in part on the work of * the Independent JPEG Group". * (3) Permission for use of this software is granted only if the user accepts * full responsibility for any undesirable consequences; the authors accept * NO LIABILITY for damages of any kind. * * These conditions apply to any software derived from or based on the IJG code, * not just to the unmodified library. If you use our work, you ought to * acknowledge us. * * Permission is NOT granted for the use of any IJG author's name or company name * in advertising or publicity relating to this software or products derived from * it. This software may be referred to only as "the Independent JPEG Group's * software". * * We specifically permit and encourage the use of this software as the basis of * commercial products, provided that all warranty or liability claims are * assumed by the product vendor. * * This file contains a slower but more accurate integer implementation of the * forward DCT (Discrete Cosine Transform). * * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT * on each column. Direct algorithms are also available, but they are * much more complex and seem not to be any faster when reduced to code. * * This implementation is based on an algorithm described in * C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT * Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics, * Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991. * The primary algorithm described there uses 11 multiplies and 29 adds. * We use their alternate method with 12 multiplies and 32 adds. * The advantage of this method is that no data path contains more than one * multiplication; this allows a very simple and accurate implementation in * scaled fixed-point arithmetic, with a minimal number of shifts. */ const CONST_BITS: i32 = 13; const PASS1_BITS: i32 = 2; const FIX_0_298631336: i32 = 2446; const FIX_0_390180644: i32 = 3196; const FIX_0_541196100: i32 = 4433; const FIX_0_765366865: i32 = 6270; const FIX_0_899976223: i32 = 7373; const FIX_1_175875602: i32 = 9633; const FIX_1_501321110: i32 = 12299; const FIX_1_847759065: i32 = 15137; const FIX_1_961570560: i32 = 16069; const FIX_2_053119869: i32 = 16819; const FIX_2_562915447: i32 = 20995; const FIX_3_072711026: i32 = 25172; const DCT_SIZE: usize = 8; #[inline(always)] fn descale(x: i32, n: i32) -> i32 { // right shift with rounding (x + (1 << (n - 1))) >> n } #[inline(always)] fn into_el(v: i32) -> i16 { v as i16 } #[allow(clippy::erasing_op)] #[allow(clippy::identity_op)] pub(crate) fn fdct(data: &mut [i16; 64]) { /* Pass 1: process rows. */ /* Note results are scaled up by sqrt(8) compared to a true DCT; */ /* furthermore, we scale the results by 2**PASS1_BITS. */ for y in 0..8 { let offset = y * 8; let tmp0 = i32::from(data[offset + 0]) + i32::from(data[offset + 7]); let tmp7 = i32::from(data[offset + 0]) - i32::from(data[offset + 7]); let tmp1 = i32::from(data[offset + 1]) + i32::from(data[offset + 6]); let tmp6 = i32::from(data[offset + 1]) - i32::from(data[offset + 6]); let tmp2 = i32::from(data[offset + 2]) + i32::from(data[offset + 5]); let tmp5 = i32::from(data[offset + 2]) - i32::from(data[offset + 5]); let tmp3 = i32::from(data[offset + 3]) + i32::from(data[offset + 4]); let tmp4 = i32::from(data[offset + 3]) - i32::from(data[offset + 4]); /* Even part per LL&M figure 1 --- note that published figure is faulty; * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". */ let tmp10 = tmp0 + tmp3; let tmp13 = tmp0 - tmp3; let tmp11 = tmp1 + tmp2; let tmp12 = tmp1 - tmp2; data[offset + 0] = into_el((tmp10 + tmp11) << PASS1_BITS); data[offset + 4] = into_el((tmp10 - tmp11) << PASS1_BITS); let z1 = (tmp12 + tmp13) * FIX_0_541196100; data[offset + 2] = into_el(descale( z1 + (tmp13 * FIX_0_765366865), CONST_BITS - PASS1_BITS, )); data[offset + 6] = into_el(descale( z1 + (tmp12 * -FIX_1_847759065), CONST_BITS - PASS1_BITS, )); /* Odd part per figure 8 --- note paper omits factor of sqrt(2). * cK represents cos(K*pi/16). * i0..i3 in the paper are tmp4..tmp7 here. */ let z1 = tmp4 + tmp7; let z2 = tmp5 + tmp6; let z3 = tmp4 + tmp6; let z4 = tmp5 + tmp7; let z5 = (z3 + z4) * FIX_1_175875602; /* sqrt(2) * c3 */ let tmp4 = tmp4 * FIX_0_298631336; /* sqrt(2) * (-c1+c3+c5-c7) */ let tmp5 = tmp5 * FIX_2_053119869; /* sqrt(2) * ( c1+c3-c5+c7) */ let tmp6 = tmp6 * FIX_3_072711026; /* sqrt(2) * ( c1+c3+c5-c7) */ let tmp7 = tmp7 * FIX_1_501321110; /* sqrt(2) * ( c1+c3-c5-c7) */ let z1 = z1 * -FIX_0_899976223; /* sqrt(2) * ( c7-c3) */ let z2 = z2 * -FIX_2_562915447; /* sqrt(2) * (-c1-c3) */ let z3 = z3 * -FIX_1_961570560; /* sqrt(2) * (-c3-c5) */ let z4 = z4 * -FIX_0_390180644; /* sqrt(2) * ( c5-c3) */ let z3 = z3 + z5; let z4 = z4 + z5; data[offset + 7] = into_el(descale(tmp4 + z1 + z3, CONST_BITS - PASS1_BITS)); data[offset + 5] = into_el(descale(tmp5 + z2 + z4, CONST_BITS - PASS1_BITS)); data[offset + 3] = into_el(descale(tmp6 + z2 + z3, CONST_BITS - PASS1_BITS)); data[offset + 1] = into_el(descale(tmp7 + z1 + z4, CONST_BITS - PASS1_BITS)); } /* Pass 2: process columns. * We remove the PASS1_BITS scaling, but leave the results scaled up * by an overall factor of 8. */ for x in 0..8 { let tmp0 = i32::from(data[DCT_SIZE * 0 + x]) + i32::from(data[DCT_SIZE * 7 + x]); let tmp7 = i32::from(data[DCT_SIZE * 0 + x]) - i32::from(data[DCT_SIZE * 7 + x]); let tmp1 = i32::from(data[DCT_SIZE * 1 + x]) + i32::from(data[DCT_SIZE * 6 + x]); let tmp6 = i32::from(data[DCT_SIZE * 1 + x]) - i32::from(data[DCT_SIZE * 6 + x]); let tmp2 = i32::from(data[DCT_SIZE * 2 + x]) + i32::from(data[DCT_SIZE * 5 + x]); let tmp5 = i32::from(data[DCT_SIZE * 2 + x]) - i32::from(data[DCT_SIZE * 5 + x]); let tmp3 = i32::from(data[DCT_SIZE * 3 + x]) + i32::from(data[DCT_SIZE * 4 + x]); let tmp4 = i32::from(data[DCT_SIZE * 3 + x]) - i32::from(data[DCT_SIZE * 4 + x]); /* Even part per LL&M figure 1 --- note that published figure is faulty; * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". */ let tmp10 = tmp0 + tmp3; let tmp13 = tmp0 - tmp3; let tmp11 = tmp1 + tmp2; let tmp12 = tmp1 - tmp2; data[DCT_SIZE * 0 + x] = into_el(descale(tmp10 + tmp11, PASS1_BITS)); data[DCT_SIZE * 4 + x] = into_el(descale(tmp10 - tmp11, PASS1_BITS)); let z1 = (tmp12 + tmp13) * FIX_0_541196100; data[DCT_SIZE * 2 + x] = into_el(descale( z1 + tmp13 * FIX_0_765366865, CONST_BITS + PASS1_BITS, )); data[DCT_SIZE * 6 + x] = into_el(descale( z1 + tmp12 * -FIX_1_847759065, CONST_BITS + PASS1_BITS, )); /* Odd part per figure 8 --- note paper omits factor of sqrt(2). * cK represents cos(K*pi/16). * i0..i3 in the paper are tmp4..tmp7 here. */ let z1 = tmp4 + tmp7; let z2 = tmp5 + tmp6; let z3 = tmp4 + tmp6; let z4 = tmp5 + tmp7; let z5 = (z3 + z4) * FIX_1_175875602; /* sqrt(2) * c3 */ let tmp4 = tmp4 * FIX_0_298631336; /* sqrt(2) * (-c1+c3+c5-c7) */ let tmp5 = tmp5 * FIX_2_053119869; /* sqrt(2) * ( c1+c3-c5+c7) */ let tmp6 = tmp6 * FIX_3_072711026; /* sqrt(2) * ( c1+c3+c5-c7) */ let tmp7 = tmp7 * FIX_1_501321110; /* sqrt(2) * ( c1+c3-c5-c7) */ let z1 = z1 * -FIX_0_899976223; /* sqrt(2) * ( c7-c3) */ let z2 = z2 * -FIX_2_562915447; /* sqrt(2) * (-c1-c3) */ let z3 = z3 * -FIX_1_961570560; /* sqrt(2) * (-c3-c5) */ let z4 = z4 * -FIX_0_390180644; /* sqrt(2) * ( c5-c3) */ let z3 = z3 + z5; let z4 = z4 + z5; data[DCT_SIZE * 7 + x] = into_el(descale(tmp4 + z1 + z3, CONST_BITS + PASS1_BITS)); data[DCT_SIZE * 5 + x] = into_el(descale(tmp5 + z2 + z4, CONST_BITS + PASS1_BITS)); data[DCT_SIZE * 3 + x] = into_el(descale(tmp6 + z2 + z3, CONST_BITS + PASS1_BITS)); data[DCT_SIZE * 1 + x] = into_el(descale(tmp7 + z1 + z4, CONST_BITS + PASS1_BITS)); } } #[cfg(test)] mod tests { // Inputs and outputs are taken from libjpegs jpeg_fdct_islow for a typical image use super::fdct; const INPUT1: [i16; 64] = [ -70, -71, -70, -68, -67, -67, -67, -67, -72, -73, -72, -70, -69, -69, -68, -69, -75, -76, -74, -73, -73, -72, -71, -70, -77, -78, -77, -75, -76, -75, -73, -71, -78, -77, -77, -76, -79, -77, -76, -75, -78, -78, -77, -77, -77, -77, -78, -77, -79, -79, -78, -78, -78, -78, -79, -78, -80, -79, -78, -78, -81, -80, -78, -76, ]; const OUTPUT1: [i16; 64] = [ -4786, -66, 2, -18, 12, 12, 5, -7, 223, -37, -8, 21, 8, 5, -4, 6, 60, 6, -10, 5, 0, -2, -1, 5, 21, 21, -15, 12, -2, -7, 1, 0, -2, -5, 16, -15, 0, 5, -4, -8, 0, -7, -4, 6, 7, -4, 5, 4, 3, 0, 1, -5, 0, -1, 4, 1, -5, 7, 0, -3, -6, 1, 1, -4, ]; const INPUT2: [i16; 64] = [ 21, 28, 11, 24, -45, -37, -55, -103, 38, -8, 31, 17, -19, 49, 15, -76, 22, -48, -36, -31, -23, 35, -23, -72, 13, -30, -45, -42, -44, -15, -20, -44, 13, -30, -45, -42, -44, -15, -20, -44, 13, -30, -45, -42, -44, -15, -20, -44, 13, -30, -45, -42, -44, -15, -20, -44, 13, -30, -45, -42, -44, -15, -20, -44, ]; const OUTPUT2: [i16; 64] = [ -1420, 717, 187, 910, -244, 579, 222, -191, 461, 487, -497, -29, -220, 179, 63, -95, 213, 414, -235, -187, -108, 74, -73, -70, -63, 311, 13, -290, 17, -38, -180, -47, -254, 201, 116, -247, 102, -109, -185, -36, -310, 107, 73, -91, 126, -121, -99, -37, -253, 43, -15, 53, 101, -91, -3, -37, -136, 12, -44, 81, 53, -45, 31, -24, ]; #[test] pub fn test_fdct_libjpeg() { let mut i1 = INPUT1.clone(); fdct(&mut i1); assert_eq!(i1, OUTPUT1); let mut i2 = INPUT2.clone(); fdct(&mut i2); assert_eq!(i2, OUTPUT2); } } jpeg-encoder-0.6.0/src/huffman.rs000064400000000000000000000214151046102023000147550ustar 00000000000000/* * The default huffman tables are taken from * section K.3 Typical Huffman tables for 8-bit precision luminance and chrominance */ use alloc::vec::Vec; #[derive(Copy, Clone, Debug)] pub enum CodingClass { Dc = 0, Ac = 1, } static DEFAULT_LUMA_DC_CODE_LENGTHS: [u8; 16] = [ 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; static DEFAULT_LUMA_DC_VALUES: [u8; 12] = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, ]; static DEFAULT_CHROMA_DC_CODE_LENGTHS: [u8; 16] = [ 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, ]; static DEFAULT_CHROMA_DC_VALUES: [u8; 12] = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, ]; static DEFAULT_LUMA_AC_CODE_LENGTHS: [u8; 16] = [ 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, ]; static DEFAULT_LUMA_AC_VALUES: [u8; 162] = [ 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, ]; static DEFAULT_CHROMA_AC_CODE_LENGTHS: [u8; 16] = [ 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, ]; static DEFAULT_CHROMA_AC_VALUES: [u8; 162] = [ 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, ]; pub struct HuffmanTable { lookup_table: [(u8, u16); 256], length: [u8; 16], values: Vec, } impl HuffmanTable { pub fn new(length: &[u8; 16], values: &[u8]) -> HuffmanTable { HuffmanTable { lookup_table: create_lookup_table(length, values), length: *length, values: values.to_vec(), } } pub fn default_luma_dc() -> HuffmanTable { Self::new(&DEFAULT_LUMA_DC_CODE_LENGTHS, &DEFAULT_LUMA_DC_VALUES) } pub fn default_luma_ac() -> HuffmanTable { Self::new(&DEFAULT_LUMA_AC_CODE_LENGTHS, &DEFAULT_LUMA_AC_VALUES) } pub fn default_chroma_dc() -> HuffmanTable { Self::new(&DEFAULT_CHROMA_DC_CODE_LENGTHS, &DEFAULT_CHROMA_DC_VALUES) } pub fn default_chroma_ac() -> HuffmanTable { Self::new(&DEFAULT_CHROMA_AC_CODE_LENGTHS, &DEFAULT_CHROMA_AC_VALUES) } /// Generates an optimized huffman table as described in Section K.2 #[allow(clippy::needless_range_loop)] pub fn new_optimized(mut freq: [u32; 257]) -> HuffmanTable { let mut others = [-1i32; 257]; let mut codesize = [0usize; 257]; // Find Huffman code sizes // Figure K.1 loop { let mut v1 = None; let mut v1_min = u32::MAX; // Find the largest value with the least non zero frequency for (i, &f) in freq.iter().enumerate() { if f > 0 && f <= v1_min { v1_min = f; v1 = Some(i); } } let mut v1 = match v1 { Some(v) => v, None => break, }; let mut v2 = None; let mut v2_min = u32::MAX; // Find the next largest value with the least non zero frequency for (i, &f) in freq.iter().enumerate() { if f > 0 && f <= v2_min && i != v1 { v2_min = f; v2 = Some(i); } } let mut v2 = match v2 { Some(v) => v, None => break, }; freq[v1] += freq[v2]; freq[v2] = 0; codesize[v1] += 1; while others[v1] >= 0 { v1 = others[v1] as usize; codesize[v1] += 1; } others[v1] = v2 as i32; codesize[v2] += 1; while others[v2] >= 0 { v2 = others[v2] as usize; codesize[v2] += 1; } } // Find the number of codes of each size // Figure K.2 let mut bits = [0u8; 33]; for &size in codesize.iter() { if size > 0 { bits[size] += 1; } } // Limiting code lengths to 16 bits // Figure K.3 let mut i = 32; while i > 16 { while bits[i] > 0 { let mut j = i - 2; while bits[j] == 0 { j -= 1; } bits[i] -= 2; bits[i - 1] += 1; bits[j + 1] += 2; bits[j] -= 1; } i -= 1; } while bits[i] == 0 { debug_assert!(i > 0, "Error creating codesizes for frequency {:?}", freq); i -= 1; } bits[i] -= 1; // Sorting of input values according to code size // Figure K.4 let mut huffval = [0u8; 256]; let mut k = 0; for i in 1..=32 { for j in 0..=255 { if codesize[j] == i { huffval[k] = j as u8; k += 1; } } } let mut length = [0u8; 16]; for (i, v) in length.iter_mut().enumerate() { *v = bits[i + 1]; } let values = huffval[0..k].to_vec(); HuffmanTable { lookup_table: create_lookup_table(&length, &values), length, values, } } #[inline] pub fn get_for_value(&self, value: u8) -> &(u8, u16) { let res = &self.lookup_table[value as usize]; debug_assert!(res.0 > 0, "Got zero size code for value: {}", value); res } pub fn length(&self) -> &[u8; 16] { &self.length } pub fn values(&self) -> &[u8] { &self.values } } // Create huffman table code sizes as defined in Figure C.1 fn create_sizes(code_lengths: &[u8; 16]) -> [u8; 256] { let mut sizes = [0u8; 256]; let mut k = 0; for (i, &length) in code_lengths.iter().enumerate() { for _ in 0..length { sizes[k] = (i + 1) as u8; k += 1; } } sizes } // Create huffman table codes as defined in Figure C.2 fn create_codes(sizes: &[u8; 256]) -> [u16; 256] { let mut codes = [0u16; 256]; let mut current_code = 0; let mut current_size = sizes[0]; for (&size, code) in sizes.iter().take_while(|s| **s != 0).zip(codes.iter_mut()) { if current_size != size { let size_diff = size - current_size; current_code <<= size_diff as usize; current_size = size; } *code = current_code; current_code += 1; } codes } // Create huffman table codes as defined in Figure C.3 fn create_lookup_table(code_length: &[u8; 16], values: &[u8]) -> [(u8, u16); 256] { let sizes = create_sizes(code_length); let codes = create_codes(&sizes); let mut lookup_table = [(0u8, 0u16); 256]; for (i, &value) in values.iter().enumerate() { lookup_table[value as usize] = (sizes[i], codes[i]); } lookup_table } jpeg-encoder-0.6.0/src/image_buffer.rs000064400000000000000000000325501046102023000157460ustar 00000000000000#![allow(clippy::identity_op)] use alloc::vec::Vec; use crate::encoder::JpegColorType; /// Conversion from RGB to YCbCr #[inline] pub fn rgb_to_ycbcr(r: u8, g: u8, b: u8) -> (u8, u8, u8) { // To avoid floating point math this scales everything by 2^16 which gives // a precision of approx 4 digits. // // Non scaled conversion: // Y = 0.29900 * R + 0.58700 * G + 0.11400 * B // Cb = -0.16874 * R - 0.33126 * G + 0.50000 * B + 128 // Cr = 0.50000 * R - 0.41869 * G - 0.08131 * B + 128 let r = r as i32; let g = g as i32; let b = b as i32; let y = 19595 * r + 38470 * g + 7471 * b; let cb = -11059 * r - 21709 * g + 32768 * b + (128 << 16); let cr = 32768 * r - 27439 * g - 5329 * b + (128 << 16); let y = (y + 0x7FFF) >> 16; let cb = (cb + 0x7FFF) >> 16; let cr = (cr + 0x7FFF) >> 16; (y as u8, cb as u8, cr as u8) } /// Conversion from CMYK to YCCK (YCbCrK) #[inline] pub fn cmyk_to_ycck(c: u8, m: u8, y: u8, k: u8) -> (u8, u8, u8, u8) { let (y, cb, cr) = rgb_to_ycbcr(c, m, y); (y, cb, cr, 255 - k) } /// # Buffer used as input value for image encoding /// /// Image encoding with [Encoder::encode_image](crate::Encoder::encode_image) needs an ImageBuffer /// as input for the image data. For convenience the [Encoder::encode](crate::Encoder::encode) /// function contains implementations for common byte based pixel formats. /// Users that needs other pixel formats or don't have the data available as byte slices /// can create their own buffer implementations. /// /// ## Example: ImageBuffer implementation for RgbImage from the `image` crate /// ```no_compile /// use image::RgbImage; /// use jpeg_encoder::{ImageBuffer, JpegColorType, rgb_to_ycbcr}; /// /// pub struct RgbImageBuffer { /// image: RgbImage, /// } /// /// impl ImageBuffer for RgbImageBuffer { /// fn get_jpeg_color_type(&self) -> JpegColorType { /// // Rgb images are encoded as YCbCr in JFIF files /// JpegColorType::Ycbcr /// } /// /// fn width(&self) -> u16 { /// self.image.width() as u16 /// } /// /// fn height(&self) -> u16 { /// self.image.height() as u16 /// } /// /// fn fill_buffers(&self, y: u16, buffers: &mut [Vec; 4]){ /// for x in 0..self.width() { /// let pixel = self.image.get_pixel(x as u32 ,y as u32); /// /// let (y,cb,cr) = rgb_to_ycbcr(pixel[0], pixel[1], pixel[2]); /// /// // For YCbCr the 4th buffer is not used /// buffers[0].push(y); /// buffers[1].push(cb); /// buffers[2].push(cr); /// } /// } /// } /// /// ``` pub trait ImageBuffer { /// The color type used in the image encoding fn get_jpeg_color_type(&self) -> JpegColorType; /// Width of the image fn width(&self) -> u16; /// Height of the image fn height(&self) -> u16; /// Add color values for the row to color component buffers fn fill_buffers(&self, y: u16, buffers: &mut [Vec; 4]); } pub(crate) struct GrayImage<'a>(pub &'a [u8], pub u16, pub u16); impl<'a> ImageBuffer for GrayImage<'a> { fn get_jpeg_color_type(&self) -> JpegColorType { JpegColorType::Luma } fn width(&self) -> u16 { self.1 } fn height(&self) -> u16 { self.2 } fn fill_buffers(&self, y: u16, buffers: &mut [Vec; 4]) { for x in 0..self.width() { let offset = usize::from(y) * usize::from(self.1) + usize::from(x); buffers[0].push(self.0[offset + 0]); } } } macro_rules! ycbcr_image { ($name:ident, $num_colors:expr, $o1:expr, $o2:expr, $o3:expr) => { pub(crate) struct $name<'a>(pub &'a [u8], pub u16, pub u16); impl<'a> ImageBuffer for $name<'a> { fn get_jpeg_color_type(&self) -> JpegColorType { JpegColorType::Ycbcr } fn width(&self) -> u16 { self.1 } fn height(&self) -> u16 { self.2 } #[inline(always)] fn fill_buffers(&self, y: u16, buffers: &mut [Vec; 4]) { for x in 0..self.width() { let offset = (usize::from(y) * usize::from(self.1) + usize::from(x)) * $num_colors; let (y, cb, cr) = rgb_to_ycbcr( self.0[offset + $o1], self.0[offset + $o2], self.0[offset + $o3], ); buffers[0].push(y); buffers[1].push(cb); buffers[2].push(cr); } } } }; } ycbcr_image!(RgbImage, 3, 0, 1, 2); ycbcr_image!(RgbaImage, 4, 0, 1, 2); ycbcr_image!(BgrImage, 3, 2, 1, 0); ycbcr_image!(BgraImage, 4, 2, 1, 0); pub(crate) struct YCbCrImage<'a>(pub &'a [u8], pub u16, pub u16); impl<'a> ImageBuffer for YCbCrImage<'a> { fn get_jpeg_color_type(&self) -> JpegColorType { JpegColorType::Ycbcr } fn width(&self) -> u16 { self.1 } fn height(&self) -> u16 { self.2 } fn fill_buffers(&self, y: u16, buffers: &mut [Vec; 4]) { for x in 0..self.width() { let offset = (usize::from(y) * usize::from(self.1) + usize::from(x)) * 3; buffers[0].push(self.0[offset + 0]); buffers[1].push(self.0[offset + 1]); buffers[2].push(self.0[offset + 2]); } } } pub(crate) struct CmykImage<'a>(pub &'a [u8], pub u16, pub u16); impl<'a> ImageBuffer for CmykImage<'a> { fn get_jpeg_color_type(&self) -> JpegColorType { JpegColorType::Cmyk } fn width(&self) -> u16 { self.1 } fn height(&self) -> u16 { self.2 } fn fill_buffers(&self, y: u16, buffers: &mut [Vec; 4]) { for x in 0..self.width() { let offset = (usize::from(y) * usize::from(self.1) + usize::from(x)) * 4; buffers[0].push(255 - self.0[offset + 0]); buffers[1].push(255 - self.0[offset + 1]); buffers[2].push(255 - self.0[offset + 2]); buffers[3].push(255 - self.0[offset + 3]); } } } pub(crate) struct CmykAsYcckImage<'a>(pub &'a [u8], pub u16, pub u16); impl<'a> ImageBuffer for CmykAsYcckImage<'a> { fn get_jpeg_color_type(&self) -> JpegColorType { JpegColorType::Ycck } fn width(&self) -> u16 { self.1 } fn height(&self) -> u16 { self.2 } fn fill_buffers(&self, y: u16, buffers: &mut [Vec; 4]) { for x in 0..self.width() { let offset = (usize::from(y) * usize::from(self.1) + usize::from(x)) * 4; let (y, cb, cr, k) = cmyk_to_ycck( self.0[offset + 0], self.0[offset + 1], self.0[offset + 2], self.0[offset + 3], ); buffers[0].push(y); buffers[1].push(cb); buffers[2].push(cr); buffers[3].push(k); } } } pub(crate) struct YcckImage<'a>(pub &'a [u8], pub u16, pub u16); impl<'a> ImageBuffer for YcckImage<'a> { fn get_jpeg_color_type(&self) -> JpegColorType { JpegColorType::Ycck } fn width(&self) -> u16 { self.1 } fn height(&self) -> u16 { self.2 } fn fill_buffers(&self, y: u16, buffers: &mut [Vec; 4]) { for x in 0..self.width() { let offset = (usize::from(y) * usize::from(self.1) + usize::from(x)) * 4; buffers[0].push(self.0[offset + 0]); buffers[1].push(self.0[offset + 1]); buffers[2].push(self.0[offset + 2]); buffers[3].push(self.0[offset + 3]); } } } #[cfg(test)] mod tests { use crate::rgb_to_ycbcr; fn assert_rgb_to_ycbcr(rgb: [u8; 3], ycbcr: [u8; 3]) { let (y, cb, cr) = rgb_to_ycbcr(rgb[0], rgb[1], rgb[2]); assert_eq!([y, cb, cr], ycbcr); } #[test] fn test_rgb_to_ycbcr() { assert_rgb_to_ycbcr([0, 0, 0], [0, 128, 128]); assert_rgb_to_ycbcr([255, 255, 255], [255, 128, 128]); assert_rgb_to_ycbcr([255, 0, 0], [76, 85, 255]); assert_rgb_to_ycbcr([0, 255, 0], [150, 44, 21]); assert_rgb_to_ycbcr([0, 0, 255], [29, 255, 107]); // Values taken from libjpeg for a common image assert_rgb_to_ycbcr([59, 109, 6], [82, 85, 111]); assert_rgb_to_ycbcr([29, 60, 11], [45, 109, 116]); assert_rgb_to_ycbcr([57, 114, 26], [87, 94, 107]); assert_rgb_to_ycbcr([30, 60, 6], [45, 106, 117]); assert_rgb_to_ycbcr([41, 75, 11], [58, 102, 116]); assert_rgb_to_ycbcr([145, 184, 108], [164, 97, 115]); assert_rgb_to_ycbcr([33, 85, 7], [61, 98, 108]); assert_rgb_to_ycbcr([61, 90, 40], [76, 108, 118]); assert_rgb_to_ycbcr([75, 127, 45], [102, 96, 109]); assert_rgb_to_ycbcr([30, 56, 14], [43, 111, 118]); assert_rgb_to_ycbcr([106, 142, 81], [124, 104, 115]); assert_rgb_to_ycbcr([35, 59, 11], [46, 108, 120]); assert_rgb_to_ycbcr([170, 203, 123], [184, 94, 118]); assert_rgb_to_ycbcr([45, 87, 16], [66, 100, 113]); assert_rgb_to_ycbcr([59, 109, 21], [84, 92, 110]); assert_rgb_to_ycbcr([100, 167, 36], [132, 74, 105]); assert_rgb_to_ycbcr([17, 53, 5], [37, 110, 114]); assert_rgb_to_ycbcr([226, 244, 220], [236, 119, 121]); assert_rgb_to_ycbcr([192, 214, 120], [197, 85, 125]); assert_rgb_to_ycbcr([63, 107, 22], [84, 93, 113]); assert_rgb_to_ycbcr([44, 78, 19], [61, 104, 116]); assert_rgb_to_ycbcr([72, 106, 54], [90, 108, 115]); assert_rgb_to_ycbcr([99, 123, 73], [110, 107, 120]); assert_rgb_to_ycbcr([188, 216, 148], [200, 99, 120]); assert_rgb_to_ycbcr([19, 46, 7], [33, 113, 118]); assert_rgb_to_ycbcr([56, 95, 40], [77, 107, 113]); assert_rgb_to_ycbcr([81, 120, 56], [101, 103, 114]); assert_rgb_to_ycbcr([9, 30, 0], [20, 117, 120]); assert_rgb_to_ycbcr([90, 118, 46], [101, 97, 120]); assert_rgb_to_ycbcr([24, 52, 0], [38, 107, 118]); assert_rgb_to_ycbcr([32, 69, 9], [51, 104, 114]); assert_rgb_to_ycbcr([74, 134, 33], [105, 88, 106]); assert_rgb_to_ycbcr([37, 74, 7], [55, 101, 115]); assert_rgb_to_ycbcr([69, 119, 31], [94, 92, 110]); assert_rgb_to_ycbcr([63, 112, 21], [87, 91, 111]); assert_rgb_to_ycbcr([90, 148, 17], [116, 72, 110]); assert_rgb_to_ycbcr([50, 97, 30], [75, 102, 110]); assert_rgb_to_ycbcr([99, 129, 72], [114, 105, 118]); assert_rgb_to_ycbcr([161, 196, 57], [170, 64, 122]); assert_rgb_to_ycbcr([10, 26, 1], [18, 118, 122]); assert_rgb_to_ycbcr([87, 128, 68], [109, 105, 112]); assert_rgb_to_ycbcr([111, 155, 73], [132, 94, 113]); assert_rgb_to_ycbcr([33, 75, 11], [55, 103, 112]); assert_rgb_to_ycbcr([70, 122, 51], [98, 101, 108]); assert_rgb_to_ycbcr([22, 74, 3], [50, 101, 108]); assert_rgb_to_ycbcr([88, 142, 45], [115, 89, 109]); assert_rgb_to_ycbcr([66, 107, 40], [87, 101, 113]); assert_rgb_to_ycbcr([18, 45, 0], [32, 110, 118]); assert_rgb_to_ycbcr([163, 186, 88], [168, 83, 124]); assert_rgb_to_ycbcr([47, 104, 4], [76, 88, 108]); assert_rgb_to_ycbcr([147, 211, 114], [181, 90, 104]); assert_rgb_to_ycbcr([42, 77, 18], [60, 104, 115]); assert_rgb_to_ycbcr([37, 72, 6], [54, 101, 116]); assert_rgb_to_ycbcr([84, 140, 55], [114, 95, 107]); assert_rgb_to_ycbcr([46, 98, 25], [74, 100, 108]); assert_rgb_to_ycbcr([48, 97, 20], [74, 98, 110]); assert_rgb_to_ycbcr([189, 224, 156], [206, 100, 116]); assert_rgb_to_ycbcr([36, 83, 0], [59, 94, 111]); assert_rgb_to_ycbcr([159, 186, 114], [170, 97, 120]); assert_rgb_to_ycbcr([75, 118, 46], [97, 99, 112]); assert_rgb_to_ycbcr([193, 233, 158], [212, 97, 114]); assert_rgb_to_ycbcr([76, 116, 48], [96, 101, 114]); assert_rgb_to_ycbcr([108, 157, 79], [133, 97, 110]); assert_rgb_to_ycbcr([180, 208, 155], [194, 106, 118]); assert_rgb_to_ycbcr([74, 126, 53], [102, 100, 108]); assert_rgb_to_ycbcr([72, 123, 46], [99, 98, 109]); assert_rgb_to_ycbcr([71, 123, 34], [97, 92, 109]); assert_rgb_to_ycbcr([130, 184, 72], [155, 81, 110]); assert_rgb_to_ycbcr([30, 61, 17], [47, 111, 116]); assert_rgb_to_ycbcr([27, 71, 0], [50, 100, 112]); assert_rgb_to_ycbcr([45, 73, 24], [59, 108, 118]); assert_rgb_to_ycbcr([139, 175, 93], [155, 93, 117]); assert_rgb_to_ycbcr([11, 38, 0], [26, 114, 118]); assert_rgb_to_ycbcr([34, 87, 15], [63, 101, 107]); assert_rgb_to_ycbcr([43, 76, 35], [61, 113, 115]); assert_rgb_to_ycbcr([18, 35, 7], [27, 117, 122]); assert_rgb_to_ycbcr([69, 97, 48], [83, 108, 118]); assert_rgb_to_ycbcr([139, 176, 50], [151, 71, 120]); assert_rgb_to_ycbcr([21, 51, 7], [37, 111, 117]); assert_rgb_to_ycbcr([209, 249, 189], [230, 105, 113]); assert_rgb_to_ycbcr([32, 66, 14], [50, 108, 115]); assert_rgb_to_ycbcr([100, 143, 67], [121, 97, 113]); assert_rgb_to_ycbcr([40, 96, 14], [70, 96, 107]); assert_rgb_to_ycbcr([88, 130, 64], [110, 102, 112]); assert_rgb_to_ycbcr([52, 112, 14], [83, 89, 106]); assert_rgb_to_ycbcr([49, 72, 25], [60, 108, 120]); assert_rgb_to_ycbcr([144, 193, 75], [165, 77, 113]); assert_rgb_to_ycbcr([49, 94, 1], [70, 89, 113]); } } jpeg-encoder-0.6.0/src/lib.rs000064400000000000000000000353061046102023000141030ustar 00000000000000//! # JPEG encoder //! //! ## Using the encoder //! ```no_run //! # use jpeg_encoder::EncodingError; //! # pub fn main() -> Result<(), EncodingError> { //! use jpeg_encoder::{Encoder, ColorType}; //! //! // An array with 4 pixels in RGB format. //! let data = [ //! 255,0,0, //! 0,255,0, //! 0,0,255, //! 255,255,255, //! ]; //! //! // Create new encoder that writes to a file with maximum quality (100) //! let mut encoder = Encoder::new_file("some.jpeg", 100)?; //! //! // Encode the data with dimension 2x2 //! encoder.encode(&data, 2, 2, ColorType::Rgb)?; //! # Ok(()) //! # } #![no_std] #![cfg_attr(not(feature = "simd"), forbid(unsafe_code))] #[cfg(feature = "std")] extern crate std; extern crate alloc; extern crate core; #[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))] mod avx2; mod encoder; mod error; mod fdct; mod huffman; mod image_buffer; mod marker; mod quantization; mod writer; pub use encoder::{ColorType, Encoder, JpegColorType, SamplingFactor}; pub use error::EncodingError; pub use image_buffer::{cmyk_to_ycck, rgb_to_ycbcr, ImageBuffer}; pub use quantization::QuantizationTableType; pub use writer::{Density, JfifWrite}; #[cfg(test)] mod tests { use crate::image_buffer::rgb_to_ycbcr; use crate::{ColorType, Encoder, QuantizationTableType, SamplingFactor}; use jpeg_decoder::{Decoder, ImageInfo, PixelFormat}; use alloc::boxed::Box; use alloc::vec; use alloc::vec::Vec; fn create_test_img_rgb() -> (Vec, u16, u16) { // Ensure size which which ensures an odd MCU count per row to test chroma subsampling let width = 258; let height = 128; let mut data = Vec::with_capacity(width * height * 3); for y in 0..height { for x in 0..width { let x = x.min(255); data.push(x as u8); data.push((y * 2) as u8); data.push(((x + y * 2) / 2) as u8); } } (data, width as u16, height as u16) } fn create_test_img_rgba() -> (Vec, u16, u16) { // Ensure size which which ensures an odd MCU count per row to test chroma subsampling let width = 258; let height = 128; let mut data = Vec::with_capacity(width * height * 3); for y in 0..height { for x in 0..width { let x = x.min(255); data.push(x as u8); data.push((y * 2) as u8); data.push(((x + y * 2) / 2) as u8); data.push(x as u8); } } (data, width as u16, height as u16) } fn create_test_img_gray() -> (Vec, u16, u16) { let width = 258; let height = 128; let mut data = Vec::with_capacity(width * height); for y in 0..height { for x in 0..width { let x = x.min(255); let (y, _, _) = rgb_to_ycbcr(x as u8, (y * 2) as u8, ((x + y * 2) / 2) as u8); data.push(y); } } (data, width as u16, height as u16) } fn create_test_img_cmyk() -> (Vec, u16, u16) { let width = 258; let height = 192; let mut data = Vec::with_capacity(width * height * 4); for y in 0..height { for x in 0..width { let x = x.min(255); data.push(x as u8); data.push((y * 3 / 2) as u8); data.push(((x + y * 3 / 2) / 2) as u8); data.push((255 - (x + y) / 2) as u8); } } (data, width as u16, height as u16) } fn decode(data: &[u8]) -> (Vec, ImageInfo) { let mut decoder = Decoder::new(data); (decoder.decode().unwrap(), decoder.info().unwrap()) } fn check_result( data: Vec, width: u16, height: u16, result: &mut Vec, pixel_format: PixelFormat, ) { let (img, info) = decode(&result); assert_eq!(info.pixel_format, pixel_format); assert_eq!(info.width, width); assert_eq!(info.height, height); assert_eq!(img.len(), data.len()); for (i, (&v1, &v2)) in data.iter().zip(img.iter()).enumerate() { let diff = (v1 as i16 - v2 as i16).abs(); assert!( diff < 20, "Large color diff at index: {}: {} vs {}", i, v1, v2 ); } } #[test] fn test_gray_100() { let (data, width, height) = create_test_img_gray(); let mut result = Vec::new(); let encoder = Encoder::new(&mut result, 100); encoder .encode(&data, width, height, ColorType::Luma) .unwrap(); check_result(data, width, height, &mut result, PixelFormat::L8); } #[test] fn test_rgb_100() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let encoder = Encoder::new(&mut result, 100); encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); check_result(data, width, height, &mut result, PixelFormat::RGB24); } #[test] fn test_rgb_80() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let encoder = Encoder::new(&mut result, 80); encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); check_result(data, width, height, &mut result, PixelFormat::RGB24); } #[test] fn test_rgba_80() { let (data, width, height) = create_test_img_rgba(); let mut result = Vec::new(); let encoder = Encoder::new(&mut result, 80); encoder .encode(&data, width, height, ColorType::Rgba) .unwrap(); let (data, width, height) = create_test_img_rgb(); check_result(data, width, height, &mut result, PixelFormat::RGB24); } #[test] fn test_rgb_custom_q_table() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let mut encoder = Encoder::new(&mut result, 100); let table = QuantizationTableType::Custom(Box::new([ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ])); encoder.set_quantization_tables(table.clone(), table); encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); check_result(data, width, height, &mut result, PixelFormat::RGB24); } #[test] fn test_rgb_2_2() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let mut encoder = Encoder::new(&mut result, 100); encoder.set_sampling_factor(SamplingFactor::F_2_2); encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); check_result(data, width, height, &mut result, PixelFormat::RGB24); } #[test] fn test_rgb_2_1() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let mut encoder = Encoder::new(&mut result, 100); encoder.set_sampling_factor(SamplingFactor::F_2_1); encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); check_result(data, width, height, &mut result, PixelFormat::RGB24); } #[test] fn test_rgb_4_1() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let mut encoder = Encoder::new(&mut result, 100); encoder.set_sampling_factor(SamplingFactor::F_4_1); encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); check_result(data, width, height, &mut result, PixelFormat::RGB24); } #[test] fn test_rgb_1_1() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let mut encoder = Encoder::new(&mut result, 100); encoder.set_sampling_factor(SamplingFactor::F_1_1); encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); check_result(data, width, height, &mut result, PixelFormat::RGB24); } #[test] fn test_rgb_1_4() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let mut encoder = Encoder::new(&mut result, 100); encoder.set_sampling_factor(SamplingFactor::F_1_4); encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); check_result(data, width, height, &mut result, PixelFormat::RGB24); } #[test] fn test_rgb_progressive() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let mut encoder = Encoder::new(&mut result, 100); encoder.set_sampling_factor(SamplingFactor::F_2_1); encoder.set_progressive(true); encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); check_result(data, width, height, &mut result, PixelFormat::RGB24); } #[test] fn test_rgb_optimized() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let mut encoder = Encoder::new(&mut result, 100); encoder.set_sampling_factor(SamplingFactor::F_2_2); encoder.set_optimized_huffman_tables(true); encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); check_result(data, width, height, &mut result, PixelFormat::RGB24); } #[test] fn test_rgb_optimized_progressive() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let mut encoder = Encoder::new(&mut result, 100); encoder.set_sampling_factor(SamplingFactor::F_2_1); encoder.set_progressive(true); encoder.set_optimized_huffman_tables(true); encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); check_result(data, width, height, &mut result, PixelFormat::RGB24); } #[test] fn test_cmyk() { let (data, width, height) = create_test_img_cmyk(); let mut result = Vec::new(); let encoder = Encoder::new(&mut result, 100); encoder .encode(&data, width, height, ColorType::Cmyk) .unwrap(); check_result(data, width, height, &mut result, PixelFormat::CMYK32); } #[test] fn test_ycck() { let (data, width, height) = create_test_img_cmyk(); let mut result = Vec::new(); let encoder = Encoder::new(&mut result, 100); encoder .encode(&data, width, height, ColorType::CmykAsYcck) .unwrap(); check_result(data, width, height, &mut result, PixelFormat::CMYK32); } #[test] fn test_restart_interval() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let mut encoder = Encoder::new(&mut result, 100); encoder.set_restart_interval(32); const DRI_DATA: &[u8; 6] = b"\xFF\xDD\0\x04\0\x20"; encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); assert!(result .as_slice() .windows(DRI_DATA.len()) .any(|w| w == DRI_DATA)); check_result(data, width, height, &mut result, PixelFormat::RGB24); } #[test] fn test_restart_interval_4_1() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let mut encoder = Encoder::new(&mut result, 100); encoder.set_sampling_factor(SamplingFactor::F_4_1); encoder.set_restart_interval(32); const DRI_DATA: &[u8; 6] = b"\xFF\xDD\0\x04\0\x20"; encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); assert!(result .as_slice() .windows(DRI_DATA.len()) .any(|w| w == DRI_DATA)); check_result(data, width, height, &mut result, PixelFormat::RGB24); } #[test] fn test_restart_interval_progressive() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let mut encoder = Encoder::new(&mut result, 85); encoder.set_progressive(true); encoder.set_restart_interval(32); const DRI_DATA: &[u8; 6] = b"\xFF\xDD\0\x04\0\x20"; encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); assert!(result .as_slice() .windows(DRI_DATA.len()) .any(|w| w == DRI_DATA)); check_result(data, width, height, &mut result, PixelFormat::RGB24); } #[test] fn test_app_segment() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let mut encoder = Encoder::new(&mut result, 100); encoder.add_app_segment(15, b"HOHOHO\0").unwrap(); encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); let segment_data = b"\xEF\0\x09HOHOHO\0"; assert!(result .as_slice() .windows(segment_data.len()) .any(|w| w == segment_data)); } #[test] fn test_icc_profile() { let (data, width, height) = create_test_img_rgb(); let mut result = Vec::new(); let mut encoder = Encoder::new(&mut result, 100); let mut icc = Vec::with_capacity(128 * 1024); for i in 0..128 * 1024 { icc.push((i % 255) as u8); } encoder.add_icc_profile(&icc).unwrap(); encoder .encode(&data, width, height, ColorType::Rgb) .unwrap(); const MARKER: &[u8; 12] = b"ICC_PROFILE\0"; assert!(result.as_slice().windows(MARKER.len()).any(|w| w == MARKER)); let mut decoder = Decoder::new(result.as_slice()); decoder.decode().unwrap(); let icc_out = match decoder.icc_profile() { Some(icc) => icc, None => panic!("Missing icc profile"), }; assert_eq!(icc, icc_out); } #[test] fn test_rgb_optimized_missing_table_frequency() { let data = vec![0xfb, 0x15, 0x15]; let mut result = Vec::new(); let mut encoder = Encoder::new(&mut result, 100); encoder.set_sampling_factor(SamplingFactor::F_2_2); encoder.set_optimized_huffman_tables(true); encoder.encode(&data, 1, 1, ColorType::Rgb).unwrap(); check_result(data, 1, 1, &mut result, PixelFormat::RGB24); } } jpeg-encoder-0.6.0/src/marker.rs000064400000000000000000000055201046102023000146110ustar 00000000000000#![allow(clippy::upper_case_acronyms)] // Table B.1 #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Marker { ZERO, /// Start Of Frame markers SOF(SOFType), /// Reserved for JPEG extensions JPG, /// Define Huffman table(s) DHT, /// Define arithmetic coding conditioning(s) DAC, /// Restart with modulo 8 count `m` RST(u8), /// Start of image SOI, /// End of image EOI, /// Start of scan SOS, /// Define quantization table(s) DQT, /// Define number of lines DNL, /// Define restart interval DRI, /// Define hierarchical progression DHP, /// Expand reference component(s) EXP, /// Reserved for application segments APP(u8), /// Reserved for JPEG extensions JPGn(u8), /// Comment COM, /// For temporary private use in arithmetic coding TEM, /// Reserved RES, /// Fill byte FILL, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum SOFType { /// SOF(0) BaselineDCT, /// SOF(1) ExtendedSequentialDCT, /// SOF(2) ProgressiveDCT, /// SOF(3) Lossless, /// SOF(5) DifferentialSequentialDCT, /// SOF(6) DifferentialProgressiveDCT, /// SOF(7) DifferentialLossless, /// SOF(9) ExtendedSequentialDCTArithmetic, /// SOF(10) ProgressiveDCTArithmetic, /// SOF(11) LosslessArithmeticCoding, /// SOF(13) DifferentialSequentialDCTArithmetic, /// SOF(14) DifferentialProgressiveDCTArithmetic, /// SOF(15) DifferentialLosslessArithmetic, } impl From for u8 { fn from(marker: Marker) -> Self { use self::{Marker::*, SOFType::*}; match marker { ZERO => 0x00, TEM => 0x01, RES => 0x02, SOF(BaselineDCT) => 0xC0, SOF(ExtendedSequentialDCT) => 0xC1, SOF(ProgressiveDCT) => 0xC2, SOF(Lossless) => 0xC3, DHT => 0xC4, SOF(DifferentialSequentialDCT) => 0xC5, SOF(DifferentialProgressiveDCT) => 0xC6, SOF(DifferentialLossless) => 0xC7, JPG => 0xC8, SOF(ExtendedSequentialDCTArithmetic) => 0xC9, SOF(ProgressiveDCTArithmetic) => 0xCA, SOF(LosslessArithmeticCoding) => 0xCB, DAC => 0xCC, SOF(DifferentialSequentialDCTArithmetic) => 0xCD, SOF(DifferentialProgressiveDCTArithmetic) => 0xCE, SOF(DifferentialLosslessArithmetic) => 0xCF, RST(v) => 0xD0 + v, SOI => 0xD8, EOI => 0xD9, SOS => 0xDA, DQT => 0xDB, DNL => 0xDC, DRI => 0xDD, DHP => 0xDE, EXP => 0xDF, APP(v) => 0xE0 + v, JPGn(v) => 0xF0 + v, COM => 0xFE, FILL => 0xFF, } } } jpeg-encoder-0.6.0/src/quantization.rs000064400000000000000000000307341046102023000160630ustar 00000000000000use alloc::boxed::Box; use core::num::NonZeroU16; /// # Quantization table used for encoding /// /// Tables are based on tables from mozjpeg #[derive(Debug, Clone)] pub enum QuantizationTableType { /// Sample quantization tables given in Annex K (Clause K.1) of Recommendation ITU-T T.81 (1992) | ISO/IEC 10918-1:1994. Default, /// Flat Flat, /// Custom, tuned for MS-SSIM CustomMsSsim, /// Custom, tuned for PSNR-HVS CustomPsnrHvs, /// ImageMagick table by N. Robidoux /// /// From ImageMagick, /// Relevance of human vision to JPEG-DCT compression (1992) Klein, Silverstein and Carney. KleinSilversteinCarney, /// DCTune perceptual optimization of compressed dental X-Rays (1997) Watson, Taylor, Borthwick DentalXRays, /// A visual detection model for DCT coefficient quantization (12/9/93) Ahumada, Watson, Peterson VisualDetectionModel, /// An improved detection model for DCT coefficient quantization (1993) Peterson, Ahumada and Watson ImprovedDetectionModel, /// A user supplied quantization table Custom(Box<[u16; 64]>), } impl QuantizationTableType { fn index(&self) -> usize { use QuantizationTableType::*; match self { Default => 0, Flat => 1, CustomMsSsim => 2, CustomPsnrHvs => 3, ImageMagick => 4, KleinSilversteinCarney => 5, DentalXRays => 6, VisualDetectionModel => 7, ImprovedDetectionModel => 8, Custom(_) => panic!("Custom types not supported"), } } } // Tables are based on mozjpeg jcparam.c static DEFAULT_LUMA_TABLES: [[u16; 64]; 9] = [ [ // Annex K 16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, 14, 13, 16, 24, 40, 57, 69, 56, 14, 17, 22, 29, 51, 87, 80, 62, 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104, 113, 92, 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99, ], [ // Flat 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, ], [ // Custom, tuned for MS-SSIM 12, 17, 20, 21, 30, 34, 56, 63, 18, 20, 20, 26, 28, 51, 61, 55, 19, 20, 21, 26, 33, 58, 69, 55, 26, 26, 26, 30, 46, 87, 86, 66, 31, 33, 36, 40, 46, 96, 100, 73, 40, 35, 46, 62, 81, 100, 111, 91, 46, 66, 76, 86, 102, 121, 120, 101, 68, 90, 90, 96, 113, 102, 105, 103, ], [ // Custom, tuned for PSNR-HVS 9, 10, 12, 14, 27, 32, 51, 62, 11, 12, 14, 19, 27, 44, 59, 73, 12, 14, 18, 25, 42, 59, 79, 78, 17, 18, 25, 42, 61, 92, 87, 92, 23, 28, 42, 75, 79, 112, 112, 99, 40, 42, 59, 84, 88, 124, 132, 111, 42, 64, 78, 95, 105, 126, 125, 99, 70, 75, 100, 102, 116, 100, 107, 98, ], [ // ImageMagick table by N. Robidoux // From http://www.imagemagick.org/discourse-server/viewtopic.php?f=22&t=20333&p=98008#p98008 16, 16, 16, 18, 25, 37, 56, 85, 16, 17, 20, 27, 34, 40, 53, 75, 16, 20, 24, 31, 43, 62, 91, 135, 18, 27, 31, 40, 53, 74, 106, 156, 25, 34, 43, 53, 69, 94, 131, 189, 37, 40, 62, 74, 94, 124, 169, 238, 56, 53, 91, 106, 131, 169, 226, 311, 85, 75, 135, 156, 189, 238, 311, 418, ], [ // Relevance of human vision to JPEG-DCT compression (1992) Klein, Silverstein and Carney. 10, 12, 14, 19, 26, 38, 57, 86, 12, 18, 21, 28, 35, 41, 54, 76, 14, 21, 25, 32, 44, 63, 92, 136, 19, 28, 32, 41, 54, 75, 107, 157, 26, 35, 44, 54, 70, 95, 132, 190, 38, 41, 63, 75, 95, 125, 170, 239, 57, 54, 92, 107, 132, 170, 227, 312, 86, 76, 136, 157, 190, 239, 312, 419, ], [ // DCTune perceptual optimization of compressed dental X-Rays (1997) Watson, Taylor, Borthwick 7, 8, 10, 14, 23, 44, 95, 241, 8, 8, 11, 15, 25, 47, 102, 255, 10, 11, 13, 19, 31, 58, 127, 255, 14, 15, 19, 27, 44, 83, 181, 255, 23, 25, 31, 44, 72, 136, 255, 255, 44, 47, 58, 83, 136, 255, 255, 255, 95, 102, 127, 181, 255, 255, 255, 255, 241, 255, 255, 255, 255, 255, 255, 255, ], [ // A visual detection model for DCT coefficient quantization (12/9/93) Ahumada, Watson, Peterson 15, 11, 11, 12, 15, 19, 25, 32, 11, 13, 10, 10, 12, 15, 19, 24, 11, 10, 14, 14, 16, 18, 22, 27, 12, 10, 14, 18, 21, 24, 28, 33, 15, 12, 16, 21, 26, 31, 36, 42, 19, 15, 18, 24, 31, 38, 45, 53, 25, 19, 22, 28, 36, 45, 55, 65, 32, 24, 27, 33, 42, 53, 65, 77, ], [ // An improved detection model for DCT coefficient quantization (1993) Peterson, Ahumada and Watson 14, 10, 11, 14, 19, 25, 34, 45, 10, 11, 11, 12, 15, 20, 26, 33, 11, 11, 15, 18, 21, 25, 31, 38, 14, 12, 18, 24, 28, 33, 39, 47, 19, 15, 21, 28, 36, 43, 51, 59, 25, 20, 25, 33, 43, 54, 64, 74, 34, 26, 31, 39, 51, 64, 77, 91, 45, 33, 38, 47, 59, 74, 91, 108, ], ]; // Tables are based on mozjpeg jcparam.c static DEFAULT_CHROMA_TABLES: [[u16; 64]; 9] = [ [ // Annex K 17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, 24, 26, 56, 99, 99, 99, 99, 99, 47, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, ], [ // Flat 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, ], [ // Custom, tuned for MS-SSIM 8, 12, 15, 15, 86, 96, 96, 98, 13, 13, 15, 26, 90, 96, 99, 98, 12, 15, 18, 96, 99, 99, 99, 99, 17, 16, 90, 96, 99, 99, 99, 99, 96, 96, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, ], [ //Custom, tuned for PSNR-HVS 9, 10, 17, 19, 62, 89, 91, 97, 12, 13, 18, 29, 84, 91, 88, 98, 14, 19, 29, 93, 95, 95, 98, 97, 20, 26, 84, 88, 95, 95, 98, 94, 26, 86, 91, 93, 97, 99, 98, 99, 99, 100, 98, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 97, 97, 99, 99, 99, 99, 97, 99, ], [ // ImageMagick table by N. Robidoux // From http://www.imagemagick.org/discourse-server/viewtopic.php?f=22&t=20333&p=98008#p98008 16, 16, 16, 18, 25, 37, 56, 85, 16, 17, 20, 27, 34, 40, 53, 75, 16, 20, 24, 31, 43, 62, 91, 135, 18, 27, 31, 40, 53, 74, 106, 156, 25, 34, 43, 53, 69, 94, 131, 189, 37, 40, 62, 74, 94, 124, 169, 238, 56, 53, 91, 106, 131, 169, 226, 311, 85, 75, 135, 156, 189, 238, 311, 418, ], [ // Relevance of human vision to JPEG-DCT compression (1992) Klein, Silverstein and Carney. 10, 12, 14, 19, 26, 38, 57, 86, 12, 18, 21, 28, 35, 41, 54, 76, 14, 21, 25, 32, 44, 63, 92, 136, 19, 28, 32, 41, 54, 75, 107, 157, 26, 35, 44, 54, 70, 95, 132, 190, 38, 41, 63, 75, 95, 125, 170, 239, 57, 54, 92, 107, 132, 170, 227, 312, 86, 76, 136, 157, 190, 239, 312, 419, ], [ // DCTune perceptual optimization of compressed dental X-Rays (1997) Watson, Taylor, Borthwick 7, 8, 10, 14, 23, 44, 95, 241, 8, 8, 11, 15, 25, 47, 102, 255, 10, 11, 13, 19, 31, 58, 127, 255, 14, 15, 19, 27, 44, 83, 181, 255, 23, 25, 31, 44, 72, 136, 255, 255, 44, 47, 58, 83, 136, 255, 255, 255, 95, 102, 127, 181, 255, 255, 255, 255, 241, 255, 255, 255, 255, 255, 255, 255, ], [ // A visual detection model for DCT coefficient quantization (12/9/93) Ahumada, Watson, Peterson 15, 11, 11, 12, 15, 19, 25, 32, 11, 13, 10, 10, 12, 15, 19, 24, 11, 10, 14, 14, 16, 18, 22, 27, 12, 10, 14, 18, 21, 24, 28, 33, 15, 12, 16, 21, 26, 31, 36, 42, 19, 15, 18, 24, 31, 38, 45, 53, 25, 19, 22, 28, 36, 45, 55, 65, 32, 24, 27, 33, 42, 53, 65, 77, ], [ // An improved detection model for DCT coefficient quantization (1993) Peterson, Ahumada and Watson 14, 10, 11, 14, 19, 25, 34, 45, 10, 11, 11, 12, 15, 20, 26, 33, 11, 11, 15, 18, 21, 25, 31, 38, 14, 12, 18, 24, 28, 33, 39, 47, 19, 15, 21, 28, 36, 43, 51, 59, 25, 20, 25, 33, 43, 54, 64, 74, 34, 26, 31, 39, 51, 64, 77, 91, 45, 33, 38, 47, 59, 74, 91, 108, ], ]; const SHIFT: u32 = 2 * 8 - 1; fn compute_reciprocal(divisor: u32) -> (i32, i32) { if divisor <= 1 { return (1, 0); } let mut reciprocals = (1 << SHIFT) / divisor; let fractional = (1 << SHIFT) % divisor; // Correction for rounding errors in division let mut correction = divisor / 2; if fractional != 0 { if fractional <= correction { correction += 1; } else { reciprocals += 1; } } (reciprocals as i32, correction as i32) } pub struct QuantizationTable { table: [NonZeroU16; 64], reciprocals: [i32; 64], corrections: [i32; 64], } impl QuantizationTable { pub fn new_with_quality( table: &QuantizationTableType, quality: u8, luma: bool, ) -> QuantizationTable { let table = match table { QuantizationTableType::Custom(table) => Self::get_user_table(table), table => { let table = if luma { &DEFAULT_LUMA_TABLES[table.index()] } else { &DEFAULT_CHROMA_TABLES[table.index()] }; Self::get_with_quality(table, quality) } }; let mut reciprocals = [0i32; 64]; let mut corrections = [0i32; 64]; for i in 0..64 { let (reciprocal, correction) = compute_reciprocal(table[i].get() as u32); reciprocals[i] = reciprocal; corrections[i] = correction; } QuantizationTable { table, reciprocals, corrections, } } fn get_user_table(table: &[u16; 64]) -> [NonZeroU16; 64] { let mut q_table = [NonZeroU16::new(1).unwrap(); 64]; for (i, &v) in table.iter().enumerate() { q_table[i] = match NonZeroU16::new(v.max(1).min(2 << 10) << 3) { Some(v) => v, None => panic!("Invalid quantization table value: {}", v), }; } q_table } fn get_with_quality(table: &[u16; 64], quality: u8) -> [NonZeroU16; 64] { let quality = quality.max(1).min(100) as u32; let scale = if quality < 50 { 5000 / quality } else { 200 - quality * 2 }; let mut q_table = [NonZeroU16::new(1).unwrap(); 64]; for (i, &v) in table.iter().enumerate() { let v = v as u32; let v = (v * scale + 50) / 100; let v = v.max(1).min(255) as u16; // Table values are premultiplied with 8 because dct is scaled by 8 q_table[i] = NonZeroU16::new(v << 3).unwrap(); } q_table } #[inline] pub fn get(&self, index: usize) -> u8 { (self.table[index].get() >> 3) as u8 } #[inline] pub fn quantize(&self, in_value: i16, index: usize) -> i16 { let value = in_value as i32; let reciprocal = self.reciprocals[index]; let corrections = self.corrections[index]; let abs_value = value.abs(); let mut product = (abs_value + corrections) * reciprocal; product >>= SHIFT; if value != abs_value { product *= -1; } product as i16 } } #[cfg(test)] mod tests { use crate::quantization::{QuantizationTable, QuantizationTableType}; #[test] fn test_new_100() { let q = QuantizationTable::new_with_quality(&QuantizationTableType::Default, 100, true); for &v in &q.table { let v = v.get(); assert_eq!(v, 1 << 3); } let q = QuantizationTable::new_with_quality(&QuantizationTableType::Default, 100, false); for &v in &q.table { let v = v.get(); assert_eq!(v, 1 << 3); } } #[test] fn test_new_100_quantize() { let q = QuantizationTable::new_with_quality(&QuantizationTableType::Default, 100, true); for i in -255..255 { assert_eq!(i, q.quantize(i << 3, 0)); } } } jpeg-encoder-0.6.0/src/writer.rs000064400000000000000000000302411046102023000146420ustar 00000000000000use crate::encoder::Component; use crate::huffman::{CodingClass, HuffmanTable}; use crate::marker::{Marker, SOFType}; use crate::quantization::QuantizationTable; use crate::EncodingError; /// Density settings #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Density { /// No pixel density is set, which means "1 pixel per pixel" None, /// Horizontal and vertical dots per inch (dpi) Inch { x: u16, y: u16 }, /// Horizontal and vertical dots per centimeters Centimeter { x: u16, y: u16 }, } /// Zig-zag sequence of quantized DCT coefficients /// /// Figure A.6 pub static ZIGZAG: [u8; 64] = [ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, ]; const BUFFER_SIZE: usize = core::mem::size_of::() * 8; /// A no_std alternative for `std::io::Write` /// /// An implementation of a subset of `std::io::Write` necessary to use the encoder without `std`. /// This trait is implemented for `std::io::Write` if the `std` feature is enabled. pub trait JfifWrite { /// Writes the whole buffer. The behavior must be identical to std::io::Write::write_all /// # Errors /// /// Return an error if the data can't be written fn write_all(&mut self, buf: &[u8]) -> Result<(), EncodingError>; } #[cfg(not(feature = "std"))] impl JfifWrite for &mut W { fn write_all(&mut self, buf: &[u8]) -> Result<(), EncodingError> { (**self).write_all(buf) } } #[cfg(not(feature = "std"))] impl JfifWrite for alloc::vec::Vec { fn write_all(&mut self, buf: &[u8]) -> Result<(), EncodingError> { self.extend_from_slice(buf); Ok(()) } } #[cfg(feature = "std")] impl JfifWrite for W { #[inline(always)] fn write_all(&mut self, buf: &[u8]) -> Result<(), EncodingError> { self.write_all(buf)?; Ok(()) } } pub(crate) struct JfifWriter { w: W, bit_buffer: usize, free_bits: i8, } impl JfifWriter { pub fn new(w: W) -> Self { JfifWriter { w, bit_buffer: 0, free_bits: BUFFER_SIZE as i8, } } #[inline(always)] pub fn write(&mut self, buf: &[u8]) -> Result<(), EncodingError> { self.w.write_all(buf) } #[inline(always)] pub fn write_u8(&mut self, value: u8) -> Result<(), EncodingError> { self.w.write_all(&[value]) } #[inline(always)] pub fn write_u16(&mut self, value: u16) -> Result<(), EncodingError> { self.w.write_all(&value.to_be_bytes()) } pub fn finalize_bit_buffer(&mut self) -> Result<(), EncodingError> { self.write_bits(0x7F, 7)?; self.flush_bit_buffer()?; self.bit_buffer = 0; self.free_bits = BUFFER_SIZE as i8; Ok(()) } pub fn flush_bit_buffer(&mut self) -> Result<(), EncodingError> { while self.free_bits <= (BUFFER_SIZE as i8 - 8) { self.flush_byte_from_bit_buffer(self.free_bits)?; self.free_bits += 8; } Ok(()) } #[inline(always)] fn flush_byte_from_bit_buffer(&mut self, free_bits: i8) -> Result<(), EncodingError> { let value = (self.bit_buffer >> (BUFFER_SIZE as i8 - 8 - free_bits)) & 0xFF; self.write_u8(value as u8)?; if value == 0xFF { self.write_u8(0x00)?; } Ok(()) } #[inline(always)] #[allow(overflowing_literals)] fn write_bit_buffer(&mut self) -> Result<(), EncodingError> { if (self.bit_buffer & 0x8080808080808080 & !(self.bit_buffer.wrapping_add(0x0101010101010101))) != 0 { for i in 0..(BUFFER_SIZE / 8) { self.flush_byte_from_bit_buffer((i * 8) as i8)?; } Ok(()) } else { self.w.write_all(&self.bit_buffer.to_be_bytes()) } } pub fn write_bits(&mut self, value: u32, size: u8) -> Result<(), EncodingError> { let size = size as i8; let value = value as usize; let free_bits = self.free_bits - size; if free_bits < 0 { self.bit_buffer = (self.bit_buffer << (size + free_bits)) | (value >> -free_bits); self.write_bit_buffer()?; self.bit_buffer = value; self.free_bits = free_bits + BUFFER_SIZE as i8; } else { self.free_bits = free_bits; self.bit_buffer = (self.bit_buffer << size) | value; } Ok(()) } pub fn write_marker(&mut self, marker: Marker) -> Result<(), EncodingError> { self.write(&[0xFF, marker.into()]) } pub fn write_segment(&mut self, marker: Marker, data: &[u8]) -> Result<(), EncodingError> { self.write_marker(marker)?; self.write_u16(data.len() as u16 + 2)?; self.write(data)?; Ok(()) } pub fn write_header(&mut self, density: &Density) -> Result<(), EncodingError> { self.write_marker(Marker::APP(0))?; self.write_u16(16)?; self.write(b"JFIF\0")?; self.write(&[0x01, 0x02])?; match *density { Density::None => { self.write_u8(0x00)?; self.write_u16(1)?; self.write_u16(1)?; } Density::Inch { x, y } => { self.write_u8(0x01)?; self.write_u16(x)?; self.write_u16(y)?; } Density::Centimeter { x, y } => { self.write_u8(0x02)?; self.write_u16(x)?; self.write_u16(y)?; } } self.write(&[0x00, 0x00]) } /// Append huffman table segment /// /// - `class`: 0 for DC or 1 for AC /// - `dest`: 0 for luma or 1 for chroma tables /// /// Layout: /// ```txt /// |--------|---------------|--------------------------|--------------------|--------| /// | 0xFFC4 | 16 bit length | 4 bit class / 4 bit dest | 16 byte num codes | values | /// |--------|---------------|--------------------------|--------------------|--------| /// ``` /// pub fn write_huffman_segment( &mut self, class: CodingClass, destination: u8, table: &HuffmanTable, ) -> Result<(), EncodingError> { assert!(destination < 4, "Bad destination: {}", destination); self.write_marker(Marker::DHT)?; self.write_u16(2 + 1 + 16 + table.values().len() as u16)?; self.write_u8(((class as u8) << 4) | destination)?; self.write(table.length())?; self.write(table.values())?; Ok(()) } /// Append a quantization table /// /// - `precision`: 0 which means 1 byte per value. /// - `dest`: 0 for luma or 1 for chroma tables /// /// Layout: /// ```txt /// |--------|---------------|------------------------------|--------|--------|-----|--------| /// | 0xFFDB | 16 bit length | 4 bit precision / 4 bit dest | V(0,0) | V(0,1) | ... | V(7,7) | /// |--------|---------------|------------------------------|--------|--------|-----|--------| /// ``` /// pub fn write_quantization_segment( &mut self, destination: u8, table: &QuantizationTable, ) -> Result<(), EncodingError> { assert!(destination < 4, "Bad destination: {}", destination); self.write_marker(Marker::DQT)?; self.write_u16(2 + 1 + 64)?; self.write_u8(destination)?; for &v in ZIGZAG.iter() { self.write_u8(table.get(v as usize))?; } Ok(()) } pub fn write_dri(&mut self, restart_interval: u16) -> Result<(), EncodingError> { self.write_marker(Marker::DRI)?; self.write_u16(4)?; self.write_u16(restart_interval) } #[inline] pub fn huffman_encode(&mut self, val: u8, table: &HuffmanTable) -> Result<(), EncodingError> { let &(size, code) = table.get_for_value(val); self.write_bits(code as u32, size) } #[inline] pub fn huffman_encode_value( &mut self, size: u8, symbol: u8, value: u16, table: &HuffmanTable, ) -> Result<(), EncodingError> { let &(num_bits, code) = table.get_for_value(symbol); let mut temp = value as u32; temp |= (code as u32) << size; let size = size + num_bits; self.write_bits(temp, size) } pub fn write_block( &mut self, block: &[i16; 64], prev_dc: i16, dc_table: &HuffmanTable, ac_table: &HuffmanTable, ) -> Result<(), EncodingError> { self.write_dc(block[0], prev_dc, dc_table)?; self.write_ac_block(block, 1, 64, ac_table) } pub fn write_dc( &mut self, value: i16, prev_dc: i16, dc_table: &HuffmanTable, ) -> Result<(), EncodingError> { let diff = value - prev_dc; let (size, value) = get_code(diff); self.huffman_encode_value(size, size, value, dc_table)?; Ok(()) } pub fn write_ac_block( &mut self, block: &[i16; 64], start: usize, end: usize, ac_table: &HuffmanTable, ) -> Result<(), EncodingError> { let mut zero_run = 0; for &value in &block[start..end] { if value == 0 { zero_run += 1; } else { while zero_run > 15 { self.huffman_encode(0xF0, ac_table)?; zero_run -= 16; } let (size, value) = get_code(value); let symbol = (zero_run << 4) | size; self.huffman_encode_value(size, symbol, value, ac_table)?; zero_run = 0; } } if zero_run > 0 { self.huffman_encode(0x00, ac_table)?; } Ok(()) } pub fn write_frame_header( &mut self, width: u16, height: u16, components: &[Component], progressive: bool, ) -> Result<(), EncodingError> { if progressive { self.write_marker(Marker::SOF(SOFType::ProgressiveDCT))?; } else { self.write_marker(Marker::SOF(SOFType::BaselineDCT))?; } self.write_u16(2 + 1 + 2 + 2 + 1 + (components.len() as u16) * 3)?; // Precision self.write_u8(8)?; self.write_u16(height)?; self.write_u16(width)?; self.write_u8(components.len() as u8)?; for component in components.iter() { self.write_u8(component.id)?; self.write_u8( (component.horizontal_sampling_factor << 4) | component.vertical_sampling_factor, )?; self.write_u8(component.quantization_table)?; } Ok(()) } pub fn write_scan_header( &mut self, components: &[&Component], spectral: Option<(u8, u8)>, ) -> Result<(), EncodingError> { self.write_marker(Marker::SOS)?; self.write_u16(2 + 1 + (components.len() as u16) * 2 + 3)?; self.write_u8(components.len() as u8)?; for component in components.iter() { self.write_u8(component.id)?; self.write_u8((component.dc_huffman_table << 4) | component.ac_huffman_table)?; } let (spectral_start, spectral_end) = spectral.unwrap_or((0, 63)); // Start of spectral or predictor selection self.write_u8(spectral_start)?; // End of spectral selection self.write_u8(spectral_end)?; // Successive approximation bit position high and low self.write_u8(0)?; Ok(()) } } #[inline] pub(crate) fn get_code(value: i16) -> (u8, u16) { let temp = value - (value.is_negative() as i16); let temp2 = value.abs(); /* * Doing this instead of 16 - temp2.leading_zeros() * Gives the compiler the information that leadings_zeros * is always called on a non zero value, which removes a branch on x86 */ let num_bits = 15 - (temp2 << 1 | 1).leading_zeros() as u16; let coefficient = temp & ((1 << num_bits as usize) - 1); (num_bits as u8, coefficient as u16) }