├── reference_app ├── .gitignore ├── CMakeLists.txt └── main.cpp ├── codecov.yml ├── .gitignore ├── ci ├── test_images │ ├── screenshot1.png │ ├── screenshot2.png │ ├── screenshot3.png │ ├── screenshot4.png │ └── screenshot5.png ├── after_success.sh ├── test.sh ├── func_tests.sh ├── install.sh └── test_helper.py ├── .github └── CODEOWNERS ├── src ├── processing │ ├── mod.rs │ ├── maybe_parallel.rs │ ├── variable_chunks_iterator.rs │ ├── overlapping_chunks_iterator.rs │ ├── double_overlapping_chunks_iterator.rs │ └── image.rs ├── config.rs ├── quant │ ├── test.rs │ └── mod.rs ├── lifting │ ├── mod.rs │ ├── test.rs │ ├── linear.rs │ └── cubic.rs ├── errors.rs ├── lib.rs ├── header │ ├── builder.rs │ ├── mod.rs │ └── test.rs ├── bits │ ├── test.rs │ └── mod.rs ├── color_transform │ ├── test.rs │ └── mod.rs ├── compress │ ├── mod.rs │ └── test.rs └── encode │ └── mod.rs ├── examples ├── compare.rs ├── decompress.rs └── compress.rs ├── LICENSE-MIT ├── benches ├── quant_benchmark.rs ├── lifting_benchmark.rs ├── bits_benchmark.rs ├── compress_benchmark.rs └── decompress_benchmark.rs ├── .travis.yml ├── Cargo.toml ├── tests └── test.rs ├── README.md └── LICENSE-APACHE /reference_app/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "**/test.rs" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.idea/ 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /ci/test_images/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devolutions/gfwx-rs/HEAD/ci/test_images/screenshot1.png -------------------------------------------------------------------------------- /ci/test_images/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devolutions/gfwx-rs/HEAD/ci/test_images/screenshot2.png -------------------------------------------------------------------------------- /ci/test_images/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devolutions/gfwx-rs/HEAD/ci/test_images/screenshot3.png -------------------------------------------------------------------------------- /ci/test_images/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devolutions/gfwx-rs/HEAD/ci/test_images/screenshot4.png -------------------------------------------------------------------------------- /ci/test_images/screenshot5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devolutions/gfwx-rs/HEAD/ci/test_images/screenshot5.png -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # File auto-generated and managed by Devops 2 | /.github/workflows @devolutions/devops 3 | /.github/randy.yml @devolutions/devops 4 | /.github/CODEOWNERS @devolutions/devops 5 | /.github/scripts/ @devolutions/devops 6 | /.github/dependabot.yml @devolutions/security-managers 7 | -------------------------------------------------------------------------------- /reference_app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(reference_test_app) 3 | find_package(OpenCV REQUIRED) 4 | 5 | if(NOT CMAKE_BUILD_TYPE) 6 | set(CMAKE_BUILD_TYPE Release) 7 | endif() 8 | 9 | set(CMAKE_CXX_FLAGS "-Wall -Wextra") 10 | set(CMAKE_CXX_FLAGS_DEBUG "-g") 11 | set(CMAKE_CXX_FLAGS_RELEASE "-O3") 12 | set(CMAKE_CXX_STANDARD 11) 13 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 14 | set(CMAKE_CXX_EXTENSIONS OFF) 15 | 16 | add_executable(reference_test_app main.cpp) 17 | target_link_libraries(reference_test_app ${OpenCV_LIBS}) 18 | -------------------------------------------------------------------------------- /src/processing/mod.rs: -------------------------------------------------------------------------------- 1 | mod double_overlapping_chunks_iterator; 2 | mod maybe_parallel; 3 | mod overlapping_chunks_iterator; 4 | mod variable_chunks_iterator; 5 | 6 | pub mod image; 7 | 8 | #[cfg(test)] 9 | mod test; 10 | 11 | pub use self::double_overlapping_chunks_iterator::{ 12 | DoubleOverlappingChunks, DoubleOverlappingChunksIterator, 13 | }; 14 | pub use self::maybe_parallel::{ 15 | process_maybe_parallel_for_each, process_maybe_parallel_map_collect, 16 | }; 17 | pub use self::overlapping_chunks_iterator::OverlappingChunksIterator; 18 | pub use self::variable_chunks_iterator::VariableChunksIterator; 19 | -------------------------------------------------------------------------------- /ci/after_success.sh: -------------------------------------------------------------------------------- 1 | if [ $TARGET = $CODECOV_TARGET ]; then 2 | wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && 3 | tar xzf master.tar.gz && 4 | cd kcov-master && 5 | mkdir build && 6 | cd build && 7 | cmake .. && 8 | make && 9 | make install DESTDIR=../../kcov-build && 10 | cd ../.. && 11 | rm -rf kcov-master && 12 | for file in target/debug/gfwx-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && 13 | bash <(curl -s https://codecov.io/bash) && 14 | echo "Uploaded code coverage" 15 | fi -------------------------------------------------------------------------------- /ci/test.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | 3 | main() { 4 | cross build --target $TARGET --lib 5 | 6 | if [ ! -z $DISABLE_TESTS ]; then 7 | return 8 | fi 9 | 10 | cross build --target $TARGET --examples 11 | cross build --target $TARGET --benches 12 | 13 | if [ $TARGET = $CODECOV_TARGET ]; then 14 | cargo test 15 | cargo test --release 16 | else 17 | cross test --target $TARGET 18 | cross test --target $TARGET --release 19 | fi 20 | 21 | cross bench -- --test 22 | 23 | bash ci/func_tests.sh ci/test_images 24 | } 25 | 26 | # we don't run the "test phase" when doing deploys 27 | if [ -z $TRAVIS_TAG ]; then 28 | main 29 | fi 30 | -------------------------------------------------------------------------------- /ci/func_tests.sh: -------------------------------------------------------------------------------- 1 | SCRIPT_DIR=$(dirname $(realpath -s "$0")) 2 | 3 | build_ref_app() { 4 | mkdir ref_build 5 | cd ref_build && \ 6 | cmake ${SCRIPT_DIR}/../reference_app/ && \ 7 | make && \ 8 | cp reference_test_app ../ && \ 9 | cd .. && \ 10 | rm -r ref_build 11 | } 12 | 13 | build_examples() { 14 | cargo build --release --examples --manifest-path ${SCRIPT_DIR}/../Cargo.toml && \ 15 | cp ${SCRIPT_DIR}/../target/release/examples/compress ./ && \ 16 | cp ${SCRIPT_DIR}/../target/release/examples/decompress ./ && \ 17 | cp ${SCRIPT_DIR}/../target/release/examples/compare ./ 18 | } 19 | 20 | IMAGES_DIR=$(pwd)/$1 21 | 22 | mkdir /tmp/gfwx 23 | pushd /tmp/gfwx && \ 24 | build_ref_app && \ 25 | build_examples && \ 26 | ${SCRIPT_DIR}/test_helper.py ${IMAGES_DIR} 27 | popd 28 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | pub struct Config; 2 | 3 | const DEFAULT_MULTITHREADING_FACTORS: MultithreadingFactors = MultithreadingFactors { 4 | linear_horizontal_lifting: 128 * 128, 5 | linear_vertical_lifting: 128 * 128, 6 | cubic_horizontal_lifting: 64 * 64, 7 | cubic_vertical_lifting: 64 * 64, 8 | quantization: 96 * 96, 9 | compress: 128 * 128, 10 | }; 11 | 12 | pub struct MultithreadingFactors { 13 | pub linear_horizontal_lifting: usize, 14 | pub linear_vertical_lifting: usize, 15 | pub cubic_horizontal_lifting: usize, 16 | pub cubic_vertical_lifting: usize, 17 | pub quantization: usize, 18 | pub compress: usize, 19 | } 20 | 21 | impl Config { 22 | pub fn multithreading_factors() -> &'static MultithreadingFactors { 23 | &DEFAULT_MULTITHREADING_FACTORS 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/compare.rs: -------------------------------------------------------------------------------- 1 | use std::{env, error::Error, fmt}; 2 | 3 | fn main() -> Result<(), Box> { 4 | let (file1, file2) = if env::args().count() == 3 { 5 | (env::args().nth(1).unwrap(), env::args().nth(2).unwrap()) 6 | } else { 7 | panic!("Usage: {} image1 image2", env::args().nth(0).unwrap()); 8 | }; 9 | 10 | let image1 = image::open(&file1)?.raw_pixels(); 11 | let image2 = image::open(&file2)?.raw_pixels(); 12 | 13 | if image1.len() == image2.len() && image1 == image2 { 14 | Ok(()) 15 | } else { 16 | Err(Box::new(DifferentImages(file1, file2))) 17 | } 18 | } 19 | 20 | #[derive(Debug)] 21 | struct DifferentImages(String, String); 22 | 23 | impl fmt::Display for DifferentImages { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | write!(f, "Images {} and {} are different", self.0, self.1) 26 | } 27 | } 28 | 29 | impl Error for DifferentImages { 30 | fn description(&self) -> &str { 31 | "Passed images has different content" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /benches/quant_benchmark.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use gfwx::quant::{dequantize, quantize}; 3 | 4 | fn quantization_benchmark(c: &mut Criterion) { 5 | c.bench_function_over_inputs( 6 | "quantization", 7 | |b, &&size| { 8 | let mut image: Vec<_> = (0..size * size).map(|x| x as i16).collect(); 9 | let mut image: Vec<_> = image.chunks_mut(size).collect(); 10 | b.iter(move || quantize(&mut image, 75, 50, 90)); 11 | }, 12 | &[32, 64, 128, 256, 512, 1024], 13 | ); 14 | } 15 | 16 | fn dequantization_benchmark(c: &mut Criterion) { 17 | c.bench_function_over_inputs( 18 | "dequantization", 19 | |b, &&size| { 20 | let mut image: Vec<_> = (0..size * size).map(|x| x as i16).collect(); 21 | let mut image: Vec<_> = image.chunks_mut(size).collect(); 22 | b.iter(move || dequantize(&mut image, 75, 50, 90)); 23 | }, 24 | &[32, 64, 128, 256, 512, 1024], 25 | ); 26 | } 27 | 28 | criterion_group!(benches, quantization_benchmark, dequantization_benchmark); 29 | 30 | criterion_main!(benches); 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Based on the "trust" template v0.1.2 2 | # https://github.com/japaric/trust/tree/v0.1.2 3 | 4 | dist: trusty 5 | language: rust 6 | services: docker 7 | sudo: required 8 | 9 | env: 10 | global: 11 | - CRATE_NAME=gfwx-rs 12 | - CODECOV_TARGET=x86_64-unknown-linux-gnu 13 | 14 | matrix: 15 | include: 16 | - env: TARGET=armv7-linux-androideabi DISABLE_TESTS=1 17 | 18 | - env: TARGET=armv7s-apple-ios DISABLE_TESTS=1 19 | os: osx 20 | 21 | - env: TARGET=x86_64-unknown-linux-gnu 22 | 23 | - env: TARGET=x86_64-apple-darwin 24 | os: osx 25 | 26 | - env: TARGET=x86_64-pc-windows-gnu 27 | 28 | addons: 29 | apt: 30 | packages: 31 | - realpath 32 | 33 | before_install: 34 | - set -e 35 | - rustup self update 36 | 37 | install: 38 | - sh ci/install.sh 39 | - source ~/.cargo/env || true 40 | 41 | script: 42 | - bash ci/test.sh 43 | 44 | after_success: 45 | - bash ci/after_success.sh 46 | 47 | after_script: set +e 48 | 49 | cache: 50 | cargo: true 51 | directories: 52 | - opencv 53 | 54 | before_cache: 55 | # Travis can't cache files that are not readable by "others" 56 | - chmod -R a+r $HOME/.cargo 57 | 58 | notifications: 59 | email: 60 | on_success: never -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gfwx" 3 | version = "0.3.0" 4 | readme = "README.md" 5 | license = "MIT/Apache-2.0" 6 | homepage = "https://github.com/devolutions/gfwx-rs" 7 | repository = "https://github.com/devolutions/gfwx-rs" 8 | authors = ["Vlad Aleksashin "] 9 | keywords = ["gfwx", "image", "codec"] 10 | description = "GFWX: Good, Fast Wavelet Codec (Rust)" 11 | edition = "2018" 12 | 13 | exclude = [ 14 | ".*", 15 | "examples/*", 16 | "ci/*", 17 | "reference_app/*", 18 | ] 19 | 20 | [lib] 21 | bench = false 22 | 23 | [dev-dependencies] 24 | criterion = "0.2.6" 25 | image = "0.21" 26 | clap = "2.32" 27 | time = "0.3" 28 | 29 | [dependencies] 30 | byteorder = "1.2" 31 | num-traits = "0.2" 32 | num-derive = "0.2" 33 | rayon = { version = "1.0", optional = true } 34 | 35 | [features] 36 | default = ["rayon", "adaptive_multithreading"] 37 | adaptive_multithreading = [] 38 | 39 | [[bench]] 40 | name = "lifting_benchmark" 41 | harness = false 42 | 43 | [[bench]] 44 | name = "quant_benchmark" 45 | harness = false 46 | 47 | [[bench]] 48 | name = "bits_benchmark" 49 | harness = false 50 | 51 | [[bench]] 52 | name = "compress_benchmark" 53 | harness = false 54 | 55 | [[bench]] 56 | name = "decompress_benchmark" 57 | harness = false 58 | -------------------------------------------------------------------------------- /src/processing/maybe_parallel.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "rayon")] 2 | use rayon::prelude::*; 3 | 4 | #[cfg(feature = "rayon")] 5 | #[inline(always)] 6 | pub fn process_maybe_parallel_for_each<'a, T, I, A>(items: I, action: A, hint_do_parallel: bool) 7 | where 8 | T: 'a + Send, 9 | I: Iterator + Send, 10 | A: Fn(I::Item) + Sync + Send, 11 | { 12 | if cfg!(not(feature = "adaptive_multithreading")) || hint_do_parallel { 13 | items.par_bridge().for_each(action); 14 | } else { 15 | items.for_each(action); 16 | } 17 | } 18 | 19 | #[cfg(not(feature = "rayon"))] 20 | #[inline(always)] 21 | pub fn process_maybe_parallel_for_each<'a, T, I, A>(items: I, action: A, _hint_do_parallel: bool) 22 | where 23 | T: 'a, 24 | I: Iterator, 25 | A: Fn(I::Item), 26 | { 27 | items.for_each(action); 28 | } 29 | 30 | #[cfg(feature = "rayon")] 31 | #[inline(always)] 32 | pub fn process_maybe_parallel_map_collect<'a, T, I, A, R>( 33 | items: I, 34 | action: A, 35 | hint_do_parallel: bool, 36 | ) -> Vec 37 | where 38 | T: 'a + Send, 39 | I: Iterator + Send, 40 | A: Fn(I::Item) -> R + Sync + Send, 41 | R: Send, 42 | { 43 | if cfg!(not(feature = "adaptive_multithreading")) || hint_do_parallel { 44 | items.par_bridge().map(action).collect() 45 | } else { 46 | items.map(action).collect() 47 | } 48 | } 49 | 50 | #[cfg(not(feature = "rayon"))] 51 | #[inline(always)] 52 | pub fn process_maybe_parallel_map_collect<'a, T, I, A, R>( 53 | items: I, 54 | action: A, 55 | _hint_do_parallel: bool, 56 | ) -> Vec 57 | where 58 | T: 'a + Send, 59 | I: Iterator + Send, 60 | A: Fn(I::Item) -> R + Sync + Send, 61 | R: Send, 62 | { 63 | items.map(action).collect() 64 | } 65 | -------------------------------------------------------------------------------- /src/processing/variable_chunks_iterator.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | pub struct VariableChunksIterator<'a, 'b, T> { 4 | slice: &'a [T], 5 | chunk_sizes: &'b [usize], 6 | } 7 | 8 | impl<'a, 'b, T> VariableChunksIterator<'a, 'b, T> { 9 | pub fn new(slice: &'a [T], chunk_sizes: &'b [usize]) -> VariableChunksIterator<'a, 'b, T> { 10 | VariableChunksIterator { slice, chunk_sizes } 11 | } 12 | 13 | fn get_next_chunk_size(&mut self) -> Option { 14 | if self.chunk_sizes.is_empty() { 15 | return None; 16 | } 17 | 18 | let mut chunk_sizes: &[usize] = &[]; 19 | mem::swap(&mut self.chunk_sizes, &mut chunk_sizes); 20 | let (next_chunk_size, chunk_sizes) = chunk_sizes.split_first().unwrap(); 21 | self.chunk_sizes = chunk_sizes; 22 | Some(*next_chunk_size) 23 | } 24 | 25 | fn get_next_chunk(&mut self, size: usize) -> Option<&'a [T]> { 26 | if self.slice.is_empty() { 27 | None 28 | } else { 29 | let mut slice: &[T] = &[]; 30 | mem::swap(&mut self.slice, &mut slice); 31 | 32 | // for last chunk - return remainder 33 | if slice.len() <= size { 34 | return Some(slice); 35 | } 36 | 37 | let (chunk, remainder) = slice.split_at(size); 38 | self.slice = remainder; 39 | 40 | Some(chunk) 41 | } 42 | } 43 | } 44 | 45 | impl<'a, 'b, T> Iterator for VariableChunksIterator<'a, 'b, T> { 46 | type Item = &'a [T]; 47 | 48 | fn next(&mut self) -> Option { 49 | let next_chunk_size = if let Some(value) = self.get_next_chunk_size() { 50 | value 51 | } else { 52 | return None; 53 | }; 54 | 55 | self.get_next_chunk(next_chunk_size) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/quant/test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn test_quantize() { 5 | let mut image = vec![ 6 | 1, 0, 254, 0, 33, 0, 225, 0, 198, 247, 0, 0, 1, 0, 43, 127, 6, 120, 187, 133, 0, 0, 64, 35, 7 | 8, 29, 143, 55, 52, 34, 0, 0, 43, 127, 226, 10, 148, 115, 225, 0, 4, 35, 59, 37, 161, 22, 8 | 100, 28, 245, 1, 0, 127, 222, 10, 155, 131, 229, 0, 1, 0, 2, 36, 128, 254, 3, 101, 225, 0, 9 | 57, 1, 1, 6, 123, 6, 194, 255, 1, 0, 1, 0, 10 | ]; 11 | 12 | let expected = vec![ 13 | 1, 0, 31, 0, 8, 0, 28, 0, 198, 15, 0, 0, 0, 0, 2, 7, 0, 7, 11, 8, 0, 0, 8, 2, 1, 1, 17, 3, 14 | 6, 2, 0, 0, 2, 7, 14, 0, 9, 7, 14, 0, 1, 2, 7, 2, 40, 1, 12, 1, 61, 0, 0, 7, 13, 0, 9, 8, 15 | 14, 0, 0, 0, 0, 2, 16, 15, 0, 6, 28, 0, 7, 0, 0, 0, 7, 0, 12, 15, 0, 0, 0, 0, 16 | ]; 17 | 18 | { 19 | let mut image: Vec<_> = image.chunks_mut(10).collect(); 20 | quantize(&mut image, 512, 0, 8192); 21 | } 22 | assert_eq!(image, expected); 23 | } 24 | 25 | #[test] 26 | fn test_dequantize() { 27 | let mut image = vec![ 28 | 1, 0, 254, 0, 33, 0, 225, 0, 198, 247, 0, 1, 0, 43, 127, 6, 120, 187, 133, 0, 0, 64, 35, 8, 29 | 29, 143, 55, 52, 34, 0, 0, 43, 127, 226, 10, 148, 115, 225, 0, 0, 35, 59, 37, 161, 22, 100, 30 | 28, 245, 1, 0, 127, 222, 10, 155, 131, 229, 0, 1, 0, 0, 36, 128, 254, 3, 101, 225, 0, 57, 31 | 1, 0, 6, 123, 6, 194, 255, 1, 0, 1, 0, 0, 32 | ]; 33 | 34 | let expected = vec![ 35 | 1, 0, 2036, 0, 134, 0, 1804, 0, 198, 3960, 0, 24, 0, 696, 2040, 104, 1928, 3000, 2136, 0, 36 | 0, 1032, 284, 136, 236, 2296, 444, 840, 276, 0, 0, 696, 2040, 3624, 168, 2376, 1848, 3608, 37 | 0, 0, 142, 952, 300, 2584, 90, 1608, 228, 3928, 6, 0, 2040, 3560, 168, 2488, 2104, 3672, 0, 38 | 24, 0, 0, 292, 2056, 2036, 56, 812, 3608, 0, 920, 12, 0, 104, 1976, 104, 3112, 4088, 24, 0, 39 | 24, 0, 0, 40 | ]; 41 | 42 | { 43 | let mut image: Vec<_> = image.chunks_mut(10).collect(); 44 | dequantize(&mut image, 512, 0, 8192); 45 | } 46 | assert_eq!(image, expected); 47 | } 48 | -------------------------------------------------------------------------------- /src/lifting/mod.rs: -------------------------------------------------------------------------------- 1 | mod cubic; 2 | mod linear; 3 | 4 | #[cfg(test)] 5 | mod test; 6 | 7 | pub use self::cubic::{lift_cubic, unlift_cubic}; 8 | pub use self::linear::{lift_linear, unlift_linear}; 9 | 10 | use crate::processing::process_maybe_parallel_for_each; 11 | 12 | fn lift( 13 | image: &mut [&mut [i16]], 14 | config_factor: usize, 15 | horizontal_lift: unsafe fn(&mut [i16], usize), 16 | vertical_lift: unsafe fn(&mut [&mut [i16]], usize), 17 | ) { 18 | let mut step = 1; 19 | let hint_do_parallel = get_hint_do_parallel(&image, config_factor); 20 | 21 | while step < image.len() || step < image[0].len() { 22 | if step < image[0].len() { 23 | process_maybe_parallel_for_each( 24 | image.iter_mut().step_by(step), 25 | |col| unsafe { horizontal_lift(col, step) }, 26 | hint_do_parallel, 27 | ); 28 | } 29 | 30 | if step < image.len() { 31 | unsafe { vertical_lift(image, step) }; 32 | } 33 | 34 | step *= 2; 35 | } 36 | } 37 | 38 | fn unlift( 39 | image: &mut [&mut [i16]], 40 | config_factor: usize, 41 | horizontal_unlift: unsafe fn(&mut [i16], usize), 42 | vertical_unlift: unsafe fn(&mut [&mut [i16]], usize), 43 | ) { 44 | let hint_do_parallel = get_hint_do_parallel(&image, config_factor); 45 | 46 | let mut step = 1; 47 | while 2 * step < image.len() || 2 * step < image[0].len() { 48 | step *= 2; 49 | } 50 | 51 | while step > 0 { 52 | if step < image.len() { 53 | unsafe { vertical_unlift(image, step) }; 54 | } 55 | 56 | if step < image[0].len() { 57 | process_maybe_parallel_for_each( 58 | image.iter_mut().step_by(step), 59 | |mut col| unsafe { horizontal_unlift(&mut col, step) }, 60 | hint_do_parallel, 61 | ); 62 | } 63 | 64 | step /= 2; 65 | } 66 | } 67 | 68 | fn get_hint_do_parallel(image: &[&mut [i16]], config_factor: usize) -> bool { 69 | image.len() * image[0].len() > config_factor 70 | } 71 | -------------------------------------------------------------------------------- /benches/lifting_benchmark.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use gfwx::lifting::{lift_cubic, lift_linear, unlift_cubic, unlift_linear}; 3 | 4 | fn linear_lifting_benchmark(c: &mut Criterion) { 5 | c.bench_function_over_inputs( 6 | "linear_lifting", 7 | |b, &&size| { 8 | let mut image: Vec<_> = (0..size * size).map(|x| x as i16).collect(); 9 | let mut image: Vec<_> = image.chunks_mut(size).collect(); 10 | b.iter(move || lift_linear(&mut image)); 11 | }, 12 | &[32, 64, 128, 256, 512, 1024], 13 | ); 14 | } 15 | 16 | fn linear_unlifting_benchmark(c: &mut Criterion) { 17 | c.bench_function_over_inputs( 18 | "linear_unlifting", 19 | |b, &&size| { 20 | let mut image: Vec<_> = (0..size * size).map(|x| x as i16).collect(); 21 | let mut image: Vec<_> = image.chunks_mut(size).collect(); 22 | b.iter(move || unlift_linear(&mut image)); 23 | }, 24 | &[32, 64, 128, 256, 512, 1024], 25 | ); 26 | } 27 | 28 | fn cubic_lifting_benchmark(c: &mut Criterion) { 29 | c.bench_function_over_inputs( 30 | "cubic_lifting", 31 | |b, &&size| { 32 | let mut image: Vec<_> = (0..size * size).map(|x| x as i16).collect(); 33 | let mut image: Vec<_> = image.chunks_mut(size).collect(); 34 | b.iter(move || lift_cubic(&mut image)); 35 | }, 36 | &[32, 64, 128, 256, 512, 1024], 37 | ); 38 | } 39 | 40 | fn cubic_unlifting_benchmark(c: &mut Criterion) { 41 | c.bench_function_over_inputs( 42 | "cubic_unlifting", 43 | |b, &&size| { 44 | let mut image: Vec<_> = (0..size * size).map(|x| x as i16).collect(); 45 | let mut image: Vec<_> = image.chunks_mut(size).collect(); 46 | b.iter(move || unlift_cubic(&mut image)); 47 | }, 48 | &[32, 64, 128, 256, 512, 1024], 49 | ); 50 | } 51 | 52 | criterion_group!( 53 | benches, 54 | linear_lifting_benchmark, 55 | linear_unlifting_benchmark, 56 | cubic_lifting_benchmark, 57 | cubic_unlifting_benchmark, 58 | ); 59 | 60 | criterion_main!(benches); 61 | -------------------------------------------------------------------------------- /src/processing/overlapping_chunks_iterator.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | pub struct OverlappingChunksIterator<'a, T> { 4 | head: &'a [T], 5 | remainder: &'a mut [T], 6 | } 7 | 8 | impl<'a, T> OverlappingChunksIterator<'a, T> { 9 | pub fn from_slice(slice: &'a mut [T], step: usize) -> OverlappingChunksIterator<'a, T> { 10 | if slice.len() <= step { 11 | // valid, but no elements will be generated by iterator 12 | OverlappingChunksIterator { 13 | head: &mut [], 14 | remainder: &mut [], 15 | } 16 | } else { 17 | // normal behavior 18 | let (head, remainder) = slice.split_at_mut(step); 19 | OverlappingChunksIterator { head, remainder } 20 | } 21 | } 22 | } 23 | 24 | impl<'a, T> Iterator for OverlappingChunksIterator<'a, T> { 25 | type Item = (&'a [T], &'a mut [T], &'a [T]); 26 | 27 | fn next(&mut self) -> Option { 28 | let left_slice = self.head; 29 | 30 | if self.remainder.is_empty() { 31 | return None; 32 | } 33 | 34 | // no right nighbour chunk 35 | if self.remainder.len() <= left_slice.len() { 36 | let mut middle_slice: &'a mut [T] = &mut []; 37 | mem::swap(&mut middle_slice, &mut self.remainder); 38 | return Some((left_slice, middle_slice, &[])); 39 | } 40 | 41 | let mut remainder: &mut [T] = &mut []; 42 | mem::swap(&mut remainder, &mut self.remainder); 43 | 44 | let (middle_slice, mut remainder) = remainder.split_at_mut(left_slice.len()); 45 | 46 | // right slice is incomplete, but iterator is effectively finished 47 | if remainder.len() <= left_slice.len() { 48 | let mut right_slice: &'a mut [T] = &mut []; 49 | mem::swap(&mut right_slice, &mut remainder); 50 | return Some((left_slice, middle_slice, right_slice)); 51 | } 52 | 53 | let (right_slice, remainder) = remainder.split_at_mut(left_slice.len()); 54 | 55 | self.head = right_slice; 56 | self.remainder = remainder; 57 | 58 | Some((left_slice, middle_slice, right_slice)) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, fmt, io}; 2 | 3 | #[derive(Debug)] 4 | pub enum CompressError { 5 | IOErr(io::Error), 6 | Overflow, 7 | Malformed, 8 | } 9 | 10 | #[derive(Debug)] 11 | pub enum DecompressError { 12 | IOErr(io::Error), 13 | Unsupported, 14 | Underflow, 15 | Malformed, 16 | TypeMismatch, 17 | } 18 | 19 | impl From for CompressError { 20 | fn from(err: io::Error) -> Self { 21 | CompressError::IOErr(err) 22 | } 23 | } 24 | 25 | impl fmt::Display for CompressError { 26 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | match self { 28 | CompressError::IOErr(e) => write!(f, "{}", e), 29 | CompressError::Overflow => write!(f, "Buffer is too small"), 30 | CompressError::Malformed => write!(f, "Invalid arguments"), 31 | } 32 | } 33 | } 34 | 35 | impl Error for CompressError {} 36 | 37 | impl From for DecompressError { 38 | fn from(err: io::Error) -> Self { 39 | DecompressError::IOErr(err) 40 | } 41 | } 42 | 43 | impl fmt::Display for DecompressError { 44 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 45 | match self { 46 | DecompressError::IOErr(e) => write!(f, "{}", e), 47 | DecompressError::Unsupported => write!(f, "Unsupported image format"), 48 | DecompressError::Malformed => write!(f, "Invalid arguments"), 49 | DecompressError::Underflow => write!(f, "Buffer underflow detected"), 50 | DecompressError::TypeMismatch => write!(f, "Image data doesn't match the header"), 51 | } 52 | } 53 | } 54 | 55 | impl Error for DecompressError {} 56 | 57 | #[derive(Debug)] 58 | pub enum HeaderErr { 59 | IOErr(io::Error), 60 | WrongMagic, 61 | WrongValue(String), 62 | } 63 | 64 | impl From for HeaderErr { 65 | fn from(err: io::Error) -> HeaderErr { 66 | HeaderErr::IOErr(err) 67 | } 68 | } 69 | 70 | impl fmt::Display for HeaderErr { 71 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 72 | match self { 73 | HeaderErr::IOErr(e) => write!(f, "{}", e), 74 | HeaderErr::WrongMagic => write!(f, "Header doesn't contain GFWX magic"), 75 | HeaderErr::WrongValue(e) => write!(f, "Invalid filed value in header: {}", e), 76 | } 77 | } 78 | } 79 | 80 | impl Error for HeaderErr {} 81 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(rust_2018_idioms)] 2 | 3 | #[macro_use] 4 | extern crate num_derive; 5 | 6 | pub mod color_transform; 7 | pub mod compress; 8 | pub mod config; 9 | pub mod encode; 10 | pub mod errors; 11 | pub mod header; 12 | pub mod processing; 13 | 14 | // this 3 modules are public for criterion benchmarks 15 | pub mod bits; 16 | pub mod lifting; 17 | pub mod quant; 18 | 19 | pub use crate::color_transform::{ 20 | interleaved_to_planar, planar_to_interleaved, ChannelTransform, ChannelTransformBuilder, 21 | ColorTransformProgram, 22 | }; 23 | pub use crate::compress::{compress_aux_data, decompress_aux_data}; 24 | pub use crate::errors::{CompressError, DecompressError}; 25 | pub use crate::header::{ 26 | Encoder, Filter, Header, HeaderBuilder, Intent, Quantization, BLOCK_DEFAULT, BLOCK_MAX, 27 | QUALITY_MAX, 28 | }; 29 | 30 | pub fn compress_simple( 31 | image: &[u8], 32 | header: &Header, 33 | color_transform: &ColorTransformProgram, 34 | mut buffer: &mut [u8], 35 | ) -> Result { 36 | let original_len = buffer.len(); 37 | header.encode(&mut buffer)?; 38 | let is_chroma = color_transform.encode( 39 | header.channels as usize * header.layers as usize, 40 | &mut buffer, 41 | )?; 42 | let service_len = original_len - buffer.len(); 43 | 44 | let mut aux_data = vec![0i16; header.get_image_size()]; 45 | color_transform.transform_and_to_planar(&image, &header, &mut aux_data); 46 | 47 | Ok(service_len + compress_aux_data(&mut aux_data, &header, &is_chroma, &mut buffer)?) 48 | } 49 | 50 | pub fn decompress_simple( 51 | mut data: &[u8], 52 | header: &Header, 53 | downsampling: usize, 54 | test: bool, 55 | mut buffer: &mut [u8], 56 | ) -> Result { 57 | let mut is_chroma = vec![false; header.layers as usize * header.channels as usize]; 58 | let color_transform = ColorTransformProgram::decode(&mut data, &mut is_chroma)?; 59 | 60 | let mut aux_data = vec![0i16; header.get_downsampled_image_size(downsampling)]; 61 | let next_point_of_interest = 62 | decompress_aux_data(data, &header, &is_chroma, downsampling, test, &mut aux_data)?; 63 | 64 | color_transform.detransform_and_to_interleaved( 65 | &mut aux_data, 66 | &header, 67 | header.get_downsampled_channel_size(downsampling), 68 | &mut buffer, 69 | ); 70 | 71 | Ok(next_point_of_interest) 72 | } 73 | -------------------------------------------------------------------------------- /src/quant/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use crate::processing::process_maybe_parallel_for_each; 3 | 4 | #[cfg(test)] 5 | mod test; 6 | 7 | pub fn quantize(image: &mut [&mut [i16]], mut quality: i32, min_quality: i32, max_quality: i32) { 8 | let mut skip = 1; 9 | let hint_do_parallel = 10 | image.len() * image[0].len() > Config::multithreading_factors().quantization; 11 | 12 | while skip < image.len() && skip < image[0].len() { 13 | let q = min_quality.max(1).max(quality); 14 | 15 | if q >= max_quality { 16 | break; 17 | } 18 | 19 | process_maybe_parallel_for_each( 20 | image.iter_mut().enumerate().step_by(skip), 21 | |(y, column)| { 22 | let x_step = if (y & skip) != 0 { skip } else { 2 * skip }; 23 | for x in column.iter_mut().skip(x_step - skip).step_by(x_step) { 24 | *x = (i32::from(*x) * q).wrapping_div(max_quality) as i16; 25 | } 26 | }, 27 | hint_do_parallel, 28 | ); 29 | 30 | skip *= 2; 31 | quality = max_quality.min(2 * quality); 32 | } 33 | } 34 | 35 | pub fn dequantize(image: &mut [&mut [i16]], mut quality: i32, min_quality: i32, max_quality: i32) { 36 | let mut skip = 1; 37 | let hint_do_parallel = 38 | image.len() * image[0].len() > Config::multithreading_factors().quantization; 39 | 40 | while skip < image.len() && skip < image[0].len() { 41 | let q = min_quality.max(1).max(quality); 42 | 43 | if q >= max_quality { 44 | break; 45 | } 46 | 47 | process_maybe_parallel_for_each( 48 | image.iter_mut().enumerate().step_by(skip), 49 | |(y, column)| { 50 | let x_step = if (y & skip) != 0 { skip } else { 2 * skip }; 51 | for x in column.iter_mut().skip(x_step - skip).step_by(x_step) { 52 | *x = if *x < 0 { 53 | (i32::from(*x) * max_quality - (max_quality / 2)).wrapping_div(q) as i16 54 | } else if *x > 0 { 55 | (i32::from(*x) * max_quality + (max_quality / 2)).wrapping_div(q) as i16 56 | } else { 57 | (i32::from(*x) * max_quality).wrapping_div(q) as i16 58 | } 59 | } 60 | }, 61 | hint_do_parallel, 62 | ); 63 | 64 | skip *= 2; 65 | quality = max_quality.min(2 * quality); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/header/builder.rs: -------------------------------------------------------------------------------- 1 | use super::{Encoder, Filter, Header, HeaderErr, Intent, Quantization}; 2 | use std::fmt::Display; 3 | 4 | pub struct HeaderBuilder { 5 | pub width: u32, 6 | pub height: u32, 7 | pub layers: u16, 8 | pub channels: u16, 9 | pub quality: u16, 10 | pub chroma_scale: u8, 11 | pub block_size: u8, 12 | pub filter: Filter, 13 | pub encoder: Encoder, 14 | pub intent: Intent, 15 | pub metadata_size: u32, 16 | } 17 | 18 | impl HeaderBuilder { 19 | pub fn build(self) -> Result { 20 | let width = check_range(self.width, 0, 1 << 30, "Width")?; 21 | let height = check_range(self.height, 0, 1 << 30, "Height")?; 22 | let bit_depth = 8; 23 | let channel_size = width as usize * height as usize; 24 | 25 | let layer_size = channel_size 26 | .checked_mul(self.layers as usize) 27 | .ok_or_else(|| HeaderErr::WrongValue(String::from("layer size is too large")))?; 28 | let image_size = layer_size 29 | .checked_mul(self.channels as usize) 30 | .ok_or_else(|| HeaderErr::WrongValue(String::from("Image size is too large")))?; 31 | let _bit_depth_image_size = image_size 32 | .checked_mul((bit_depth as usize + 7) / 8) 33 | .ok_or_else(|| { 34 | HeaderErr::WrongValue(String::from("Image size and bit_depth are too large")) 35 | })?; 36 | 37 | Ok(Header { 38 | version: 1, 39 | width, 40 | height, 41 | layers: self.layers, 42 | channels: self.channels, 43 | bit_depth, 44 | is_signed: false, 45 | quality: check_range(self.quality, 0, 1025, "Quality")?, 46 | chroma_scale: self.chroma_scale, 47 | block_size: check_range(self.block_size, 0, 31, "Block size")?, 48 | filter: self.filter, 49 | quantization: Quantization::Scalar, 50 | encoder: self.encoder, 51 | intent: self.intent, 52 | metadata_size: self.metadata_size, 53 | channel_size, 54 | image_size, 55 | }) 56 | } 57 | } 58 | 59 | fn check_range(value: T, min: T, max: T, name: &str) -> Result 60 | where 61 | T: PartialOrd + Display + Copy, 62 | { 63 | if min < value && value < max { 64 | Ok(value) 65 | } else { 66 | Err(HeaderErr::WrongValue(format!( 67 | "{} must be in range ({}..{})", 68 | name, min, max 69 | ))) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /benches/bits_benchmark.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 4 | use gfwx::bits::{BitsIOReader, BitsIOWriter, BitsReader, BitsWriter}; 5 | 6 | struct BenchWriter { 7 | buffer: [u8; 4], 8 | } 9 | 10 | impl BenchWriter { 11 | fn new() -> BenchWriter { 12 | BenchWriter { buffer: [0; 4] } 13 | } 14 | } 15 | 16 | impl io::Write for BenchWriter { 17 | fn write(&mut self, value: &[u8]) -> io::Result { 18 | for (b, v) in self.buffer.iter_mut().zip(value.iter()) { 19 | *b = *v; 20 | } 21 | Ok(self.buffer.len()) 22 | } 23 | 24 | fn flush(&mut self) -> io::Result<()> { 25 | Ok(()) 26 | } 27 | } 28 | 29 | struct BenchReader { 30 | value: u8, 31 | } 32 | 33 | impl BenchReader { 34 | fn new() -> BenchReader { 35 | BenchReader { value: 42 } 36 | } 37 | } 38 | 39 | impl io::Read for BenchReader { 40 | fn read(&mut self, buffer: &mut [u8]) -> io::Result { 41 | for v in buffer.iter_mut() { 42 | *v = self.value; 43 | } 44 | 45 | Ok(buffer.len()) 46 | } 47 | } 48 | 49 | fn bits_writer_benchmark(c: &mut Criterion) { 50 | c.bench_function("bits_writer", |b| { 51 | let mut buff = BenchWriter::new(); 52 | let mut stream = BitsIOWriter::new(&mut buff); 53 | b.iter(|| { 54 | for _ in 0..12 { 55 | black_box(stream.put_bits(15, 4).unwrap()); 56 | } 57 | stream.flush_write_word().unwrap(); 58 | }) 59 | }); 60 | } 61 | 62 | fn bits_reader_benchmark(c: &mut Criterion) { 63 | c.bench_function("bits_reader", |b| { 64 | let mut source = BenchReader::new(); 65 | let mut stream = BitsIOReader::new(&mut source); 66 | b.iter(|| { 67 | for _ in 0..15 { 68 | black_box(stream.get_bits(4).unwrap()); 69 | } 70 | stream.flush_read_word(); 71 | }) 72 | }); 73 | } 74 | 75 | fn bits_zeros_benchmark(c: &mut Criterion) { 76 | c.bench_function("bits_zeros", |b| { 77 | let mut source = BenchReader::new(); 78 | let mut stream = BitsIOReader::new(&mut source); 79 | b.iter(|| { 80 | for _ in 0..15 { 81 | black_box(stream.get_zeros(4).unwrap()); 82 | } 83 | stream.flush_read_word(); 84 | }) 85 | }); 86 | } 87 | 88 | criterion_group!( 89 | benches, 90 | bits_writer_benchmark, 91 | bits_reader_benchmark, 92 | bits_zeros_benchmark 93 | ); 94 | 95 | criterion_main!(benches); 96 | -------------------------------------------------------------------------------- /benches/compress_benchmark.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion, ParameterizedBenchmark, Throughput}; 2 | use gfwx::compress_aux_data; 3 | 4 | macro_rules! compress_benchmark { 5 | ($name:ident, $filter:expr, $mode:expr) => { 6 | fn $name(c: &mut Criterion) { 7 | let channels = 3; 8 | c.bench( 9 | stringify!($name), 10 | ParameterizedBenchmark::new( 11 | stringify!($name), 12 | move |b, &&size| { 13 | let builder = gfwx::HeaderBuilder { 14 | width: size as u32, 15 | height: size as u32, 16 | layers: 1, 17 | channels, 18 | quality: 124, 19 | chroma_scale: 8, 20 | block_size: gfwx::BLOCK_DEFAULT, 21 | filter: $filter, 22 | encoder: $mode, 23 | intent: gfwx::Intent::RGB, 24 | metadata_size: 0, 25 | }; 26 | let header = builder.build().unwrap(); 27 | 28 | let mut aux_data: Vec<_> = (0..header.get_image_size()) 29 | .map(|x| (x % 256) as i16) 30 | .collect(); 31 | let mut compressed = vec![0; 2 * aux_data.len()]; 32 | b.iter(move || { 33 | compress_aux_data(&mut aux_data, &header, &[false; 3], &mut compressed) 34 | .unwrap() 35 | }); 36 | }, 37 | &[128, 256, 512, 1024], 38 | ) 39 | .throughput(move |&elems| { 40 | Throughput::Bytes((elems * elems * channels as i32) as u32) 41 | }), 42 | ); 43 | } 44 | }; 45 | } 46 | 47 | compress_benchmark!( 48 | compress_linear_contextual, 49 | gfwx::Filter::Linear, 50 | gfwx::Encoder::Contextual 51 | ); 52 | compress_benchmark!( 53 | compress_cubic_contextual, 54 | gfwx::Filter::Cubic, 55 | gfwx::Encoder::Contextual 56 | ); 57 | compress_benchmark!( 58 | compress_cubic_fast, 59 | gfwx::Filter::Cubic, 60 | gfwx::Encoder::Fast 61 | ); 62 | compress_benchmark!( 63 | compress_cubic_turbo, 64 | gfwx::Filter::Cubic, 65 | gfwx::Encoder::Turbo 66 | ); 67 | 68 | criterion_group!( 69 | benches, 70 | compress_linear_contextual, 71 | compress_cubic_contextual, 72 | compress_cubic_fast, 73 | compress_cubic_turbo, 74 | ); 75 | 76 | criterion_main!(benches); 77 | -------------------------------------------------------------------------------- /ci/install.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | 3 | install_opencv_linux() { 4 | sudo apt-get -yq install build-essential cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev 5 | git clone --branch '3.4.4' --depth 1 -q https://github.com/opencv/opencv.git opencv || true 6 | if [ ! -d opencv/build ] ; then 7 | mkdir opencv/build 8 | fi 9 | cd opencv/build && \ 10 | cmake -D BUILD_LIST=imgcodecs -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local .. >/dev/null && \ 11 | make -s -j $(nproc) && \ 12 | sudo make -s install 13 | cd ../.. 14 | } 15 | 16 | main() { 17 | local target= 18 | if [ $TRAVIS_OS_NAME = linux ]; then 19 | target=x86_64-unknown-linux-musl 20 | sort=sort 21 | else 22 | target=x86_64-apple-darwin 23 | sort=gsort # for `sort --sort-version`, from brew's coreutils. 24 | fi 25 | 26 | # Install OpenCV 27 | if [ -z $DISABLE_TESTS ]; then 28 | if [ $TRAVIS_OS_NAME = linux ]; then 29 | install_opencv_linux 30 | else 31 | brew install glog >/dev/null 32 | brew install opencv >/dev/null 33 | fi 34 | fi 35 | 36 | # Builds for iOS are done on OSX, but require the specific target to be 37 | # installed. 38 | case $TARGET in 39 | aarch64-apple-ios) 40 | rustup target install aarch64-apple-ios 41 | ;; 42 | armv7-apple-ios) 43 | rustup target install armv7-apple-ios 44 | ;; 45 | armv7s-apple-ios) 46 | rustup target install armv7s-apple-ios 47 | ;; 48 | i386-apple-ios) 49 | rustup target install i386-apple-ios 50 | ;; 51 | x86_64-apple-ios) 52 | rustup target install x86_64-apple-ios 53 | ;; 54 | esac 55 | 56 | # Install tools for codecov 57 | if [ $TARGET = $CODECOV_TARGET ]; then 58 | sudo apt-get -yq --no-install-suggests --no-install-recommends install libcurl4-openssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev 59 | fi 60 | 61 | # This fetches latest stable release 62 | local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \ 63 | | cut -d/ -f3 \ 64 | | grep -E '^v[0.1.0-9.]+$' \ 65 | | $sort --version-sort \ 66 | | tail -n1) 67 | curl -LSfs https://japaric.github.io/trust/install.sh | \ 68 | sh -s -- \ 69 | --force \ 70 | --git japaric/cross \ 71 | --tag $tag \ 72 | --target $target 73 | } 74 | 75 | main 76 | -------------------------------------------------------------------------------- /reference_app/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "gfwx.hpp" 8 | 9 | int main(int argc, char const * argv[]) 10 | { 11 | if (argc < 6) { 12 | std::cerr << "Usage: " << argv[0] << 13 | " INPUT OUTPUT.GFWX quality filter encoder"; 14 | return 1; 15 | } 16 | 17 | std::string input = argv[1]; 18 | std::string gfwxOutput = argv[2]; 19 | std::string qualityStr = argv[3]; 20 | std::string filterStr = argv[4]; 21 | std::string encoderStr = argv[5]; 22 | 23 | cv::Mat image = cv::imread(input, cv::IMREAD_COLOR); 24 | 25 | int layers = 1; // just one image layer 26 | int channels = 3; // 3 interleaved channels 27 | int bitDepth = GFWX::BitDepthAuto; // BitDepthAuto selects 8 or 16 based on type 28 | int chromaScale = 8; // chroma quality is divided by this number 29 | int blockSize = GFWX::BlockDefault; // probably fine 30 | int quantization = GFWX::QuantizationScalar; // only one choice here anyway 31 | int intent = GFWX::IntentBGR; // opencv uses BGR instead of RGB 32 | 33 | int quality = -1; 34 | try { 35 | quality = std::stoi(qualityStr); 36 | if (quality == 0 || quality > 1024) { 37 | throw std::invalid_argument("wrong value"); 38 | } 39 | } catch (const std::invalid_argument &) { 40 | std::cerr << "Wrong quality value" << std::endl; 41 | return 1; 42 | } 43 | 44 | std::cout << filterStr << std::endl; 45 | int filter = -1; 46 | if (filterStr == "linear") { 47 | filter = GFWX::FilterLinear; 48 | } else if (filterStr == "cubic") { 49 | filter = GFWX::FilterCubic; 50 | } else { 51 | std::cerr << "Wrong filter value" << std::endl; 52 | return 1; 53 | } 54 | 55 | std::cout << encoderStr << std::endl; 56 | int encoder = -1; 57 | if (encoderStr == "fast") { 58 | encoder = GFWX::EncoderFast; 59 | } else if (encoderStr == "turbo") { 60 | encoder = GFWX::EncoderTurbo; 61 | } else if (encoderStr == "contextual") { 62 | encoder = GFWX::EncoderContextual; 63 | } else { 64 | std::cerr << "Wrong encoder value" << std::endl; 65 | return 1; 66 | } 67 | 68 | std::cout << "quality: " << quality << std::endl; 69 | std::cout << "filter: " << filterStr << std::endl; 70 | std::cout << "encoder: " << encoderStr << std::endl; 71 | std::cout << "intent: BGR" << std::endl; 72 | 73 | GFWX::Header header(image.size().width, image.size().height, layers, channels, bitDepth, quality, 74 | chromaScale, blockSize, filter, quantization, encoder, intent); 75 | 76 | std::vector buffer(std::max((size_t)256, image.total() * image.elemSize() * 2)); 77 | ptrdiff_t size = GFWX::compress(image.ptr(), header, &buffer[0], buffer.size(), 0, 0, 0); 78 | std::ofstream(gfwxOutput, std::ios::binary).write((char*)&buffer[0], size); 79 | 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /benches/decompress_benchmark.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion, ParameterizedBenchmark, Throughput}; 2 | use gfwx::{compress_aux_data, decompress_aux_data}; 3 | 4 | macro_rules! decompress_benchmark { 5 | ($name:ident, $filter:expr, $mode:expr) => { 6 | fn $name(c: &mut Criterion) { 7 | let channels = 3; 8 | c.bench( 9 | stringify!($name), 10 | ParameterizedBenchmark::new( 11 | stringify!($name), 12 | move |b, &&size| { 13 | let builder = gfwx::HeaderBuilder { 14 | width: size as u32, 15 | height: size as u32, 16 | layers: 1, 17 | channels, 18 | quality: 124, 19 | chroma_scale: 8, 20 | block_size: gfwx::BLOCK_DEFAULT, 21 | filter: $filter, 22 | encoder: $mode, 23 | intent: gfwx::Intent::RGB, 24 | metadata_size: 0, 25 | }; 26 | let header = builder.build().unwrap(); 27 | 28 | let mut aux_data: Vec<_> = (0..header.get_image_size()) 29 | .map(|x| (x % 256) as i16) 30 | .collect(); 31 | let mut compressed = vec![0; 2 * aux_data.len()]; 32 | compress_aux_data(&mut aux_data, &header, &[false; 3], &mut compressed) 33 | .unwrap(); 34 | let mut decompressed = vec![0; aux_data.len()]; 35 | b.iter(move || { 36 | decompress_aux_data( 37 | &compressed, 38 | &header, 39 | &[false; 3], 40 | 0, 41 | false, 42 | &mut decompressed, 43 | ) 44 | .unwrap() 45 | }); 46 | }, 47 | &[128, 256, 512, 1024], 48 | ) 49 | .throughput(move |&elems| { 50 | Throughput::Bytes((elems * elems * channels as i32) as u32) 51 | }), 52 | ); 53 | } 54 | }; 55 | } 56 | 57 | decompress_benchmark!( 58 | decompress_linear_contextual, 59 | gfwx::Filter::Linear, 60 | gfwx::Encoder::Contextual 61 | ); 62 | decompress_benchmark!( 63 | decompress_cubic_contextual, 64 | gfwx::Filter::Cubic, 65 | gfwx::Encoder::Contextual 66 | ); 67 | decompress_benchmark!( 68 | decompress_cubic_fast, 69 | gfwx::Filter::Cubic, 70 | gfwx::Encoder::Fast 71 | ); 72 | decompress_benchmark!( 73 | decompress_cubic_turbo, 74 | gfwx::Filter::Cubic, 75 | gfwx::Encoder::Turbo 76 | ); 77 | 78 | criterion_group!( 79 | benches, 80 | decompress_linear_contextual, 81 | decompress_cubic_contextual, 82 | decompress_cubic_fast, 83 | decompress_cubic_turbo, 84 | ); 85 | 86 | criterion_main!(benches); 87 | -------------------------------------------------------------------------------- /src/bits/test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn test_bits_write() { 5 | let expected: Vec = vec![ 6 | 244, 23, 0, 0, 2, 0, 126, 0, 58, 0, 0, 160, 0, 160, 4, 0, 0, 230, 80, 13, 0, 32, 121, 14, 7 | ]; 8 | let mut output = vec![]; 9 | { 10 | let mut stream = BitsIOWriter::new(&mut output); 11 | stream.put_bits(185, 27).unwrap(); 12 | stream.put_bits(61, 3).unwrap(); 13 | stream.put_bits(63, 17).unwrap(); 14 | stream.put_bits(42, 21).unwrap(); 15 | stream.put_bits(29, 27).unwrap(); 16 | stream.put_bits(37, 20).unwrap(); 17 | stream.put_bits(213, 25).unwrap(); 18 | stream.put_bits(230, 12).unwrap(); 19 | stream.put_bits(115, 19).unwrap(); 20 | stream.put_bits(201, 8).unwrap(); 21 | stream.flush_write_word().unwrap(); 22 | } 23 | assert_eq!(output, expected); 24 | } 25 | 26 | #[test] 27 | fn test_bits_write_overflow_detection() { 28 | let mut output = [0u8; 7]; 29 | let mut slice: &mut [u8] = &mut output; 30 | 31 | let mut stream = BitsIOWriter::new(&mut slice); 32 | stream.put_bits(185, 27).unwrap(); 33 | stream.put_bits(61, 3).unwrap(); 34 | stream.flush_write_word().unwrap(); 35 | stream.put_bits(3, 6).unwrap(); 36 | assert!(stream.flush_write_word().is_err()); 37 | } 38 | 39 | #[test] 40 | fn test_bits_read() { 41 | let input: [u8; 20] = [ 42 | 0, 0, 223, 1, 0, 37, 0, 93, 162, 128, 29, 0, 0, 122, 208, 1, 0, 0, 0, 39, 43 | ]; 44 | let mut output: Vec = vec![]; 45 | let expected: Vec = vec![239, 2, 186, 148, 59, 162, 0, 29, 244, 156]; 46 | { 47 | let mut slice: &[u8] = &input; 48 | let mut stream = BitsIOReader::new(&mut slice); 49 | output.push(stream.get_bits(15).unwrap()); 50 | output.push(stream.get_bits(2).unwrap()); 51 | output.push(stream.get_bits(24).unwrap()); 52 | output.push(stream.get_bits(17).unwrap()); 53 | output.push(stream.get_bits(23).unwrap()); 54 | output.push(stream.get_bits(15).unwrap()); 55 | output.push(stream.get_bits(1).unwrap()); 56 | output.push(stream.get_bits(11).unwrap()); 57 | output.push(stream.get_bits(13).unwrap()); 58 | output.push(stream.get_bits(17).unwrap()); 59 | } 60 | assert_eq!(output, expected); 61 | } 62 | 63 | #[test] 64 | fn test_bits_read_underflow_detection() { 65 | let output = [1, 2, 3, 4, 5, 6u8]; 66 | let mut slice: &[u8] = &output; 67 | 68 | let mut stream = BitsIOReader::new(&mut slice); 69 | stream.get_bits(27).unwrap(); 70 | stream.get_bits(3).unwrap(); 71 | stream.flush_read_word(); 72 | assert!(stream.get_bits(4).is_err()); 73 | } 74 | 75 | #[test] 76 | fn test_zeros() { 77 | // In Little endian: 0xb10001000, 0b00101100, 0b10000001, 0b11110001_u8 78 | let input = [0b11110001_u8, 0b10000001, 0b00101100, 0b10001000]; 79 | let mut slice: &[u8] = &input; 80 | let mut stream = BitsIOReader::new(&mut slice); 81 | assert_eq!(stream.get_zeros(11).unwrap(), 0); 82 | assert_eq!(stream.get_zeros(1).unwrap(), 1); 83 | assert_eq!(stream.get_zeros(5).unwrap(), 2); 84 | assert_eq!(stream.get_zeros(6).unwrap(), 5); 85 | assert_eq!(stream.get_zeros(2).unwrap(), 1); 86 | assert_eq!(stream.get_zeros(2).unwrap(), 0); 87 | assert_eq!(stream.get_zeros(3).unwrap(), 2); 88 | assert_eq!(stream.get_zeros(10).unwrap(), 6); 89 | assert_eq!(stream.get_zeros(2).unwrap(), 0); 90 | assert_eq!(stream.get_zeros(2).unwrap(), 0); 91 | assert_eq!(stream.get_zeros(2).unwrap(), 0); 92 | assert_eq!(stream.get_zeros(2).unwrap(), 0); 93 | assert_eq!(stream.get_zeros(5).unwrap(), 3); 94 | } 95 | -------------------------------------------------------------------------------- /src/lifting/test.rs: -------------------------------------------------------------------------------- 1 | use super::cubic::*; 2 | use super::linear::*; 3 | 4 | #[test] 5 | fn test_cubic() { 6 | assert_eq!(cubic(255, 0, 0, 255), 0); 7 | assert_eq!(cubic(128, 0, 84, 32), 37); 8 | assert_eq!(cubic(250, 10, 12, 243), 10); 9 | } 10 | 11 | #[test] 12 | fn test_lift_cubic() { 13 | let mut image = vec![ 14 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 15 | 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 16 | 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 17 | ]; 18 | 19 | let expected = vec![ 20 | 1, 0, 0, 0, 1, 0, 1, 0, 6, -9, 0, 0, 0, 0, 0, -1, 1, 8, -2, 5, 0, 0, 0, 0, 2, 1, 7, -2, -2, 21 | 2, 0, 0, 0, -1, 1, 8, -3, 3, 1, 0, 4, 0, 1, 1, 2, -2, -3, 0, -5, 1, 0, 0, 1, 8, -3, 3, 1, 22 | 0, 0, 0, 2, 0, 4, -1, -6, 1, 7, 0, 5, 1, 1, 6, -7, 6, 2, -1, 1, 0, 1, 0, 23 | ]; 24 | 25 | { 26 | let mut image: Vec<_> = image.chunks_mut(10).collect(); 27 | lift_cubic(&mut image); 28 | } 29 | assert_eq!(image, expected); 30 | } 31 | 32 | #[test] 33 | fn test_unlift_cubic() { 34 | let mut image = vec![ 35 | 1, 0, 0, 0, 1, 0, 1, 0, 6, -9, 0, 0, 0, 0, 0, -1, 1, 8, -2, 5, 0, 0, 0, 0, 2, 1, 7, -2, -2, 36 | 2, 0, 0, 0, -1, 1, 8, -3, 3, 1, 0, 4, 0, 1, 1, 2, -2, -3, 0, -5, 1, 0, 0, 1, 8, -3, 3, 1, 37 | 0, 0, 0, 2, 0, 4, -1, -6, 1, 7, 0, 5, 1, 1, 6, -7, 6, 2, -1, 1, 0, 1, 0, 38 | ]; 39 | 40 | let expected = vec![ 41 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 42 | 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 43 | 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 44 | ]; 45 | 46 | { 47 | let mut image: Vec<_> = image.chunks_mut(10).collect(); 48 | unlift_cubic(&mut image); 49 | } 50 | assert_eq!(image, expected); 51 | } 52 | 53 | #[test] 54 | fn test_linear_lift() { 55 | let mut image = vec![ 56 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 57 | 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 58 | 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 59 | ]; 60 | let expected = vec![ 61 | 1, 0, 0, 0, 1, 0, 1, 0, 6, -9, 0, 0, 0, 0, 0, 0, 2, 7, -2, 5, 0, 0, 0, 0, 2, 1, 7, -3, -2, 62 | 2, 0, 0, 0, 0, 2, 7, -3, 2, 1, 0, 4, 0, 1, 1, 2, -3, -2, 0, -5, 1, 0, 0, 2, 7, -3, 2, 1, 0, 63 | 0, 0, 2, 1, 3, -2, -6, 0, 6, 0, 4, 1, 1, 5, -7, 5, 2, 0, 1, 0, 1, 0, 64 | ]; 65 | 66 | { 67 | let mut image: Vec<_> = image.chunks_mut(10).collect(); 68 | lift_linear(&mut image); 69 | } 70 | assert_eq!(image, expected); 71 | } 72 | 73 | #[test] 74 | fn test_linear_unlift() { 75 | let mut image = vec![ 76 | 1, 0, 0, 0, 1, 0, 1, 0, 6, -9, 0, 0, 0, 0, 0, 0, 2, 7, -2, 5, 0, 0, 0, 0, 2, 1, 7, -3, -2, 77 | 2, 0, 0, 0, 0, 2, 7, -3, 2, 1, 0, 4, 0, 1, 1, 2, -3, -2, 0, -5, 1, 0, 0, 2, 7, -3, 2, 1, 0, 78 | 0, 0, 2, 1, 3, -2, -6, 0, 6, 0, 4, 1, 1, 5, -7, 5, 2, 0, 1, 0, 1, 0, 79 | ]; 80 | let expected = vec![ 81 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 82 | 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 83 | 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 84 | ]; 85 | 86 | { 87 | let mut image: Vec<_> = image.chunks_mut(10).collect(); 88 | unlift_linear(&mut image); 89 | } 90 | assert_eq!(image, expected); 91 | } 92 | -------------------------------------------------------------------------------- /examples/decompress.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, fs, io, io::prelude::*, path::Path}; 2 | use core::ops::Sub; 3 | 4 | use image::DynamicImage::*; 5 | use time::Instant; 6 | 7 | fn main() -> Result<(), Box> { 8 | let matches = get_matches(); 9 | let input_file = matches.value_of("INPUT").unwrap(); 10 | let output_file = matches.value_of("OUTPUT").unwrap(); 11 | let downsampling = matches.value_of("downsampling").unwrap().parse().unwrap(); 12 | 13 | let mut input = fs::File::open(input_file)?; 14 | let header = gfwx::Header::decode(&mut input)?; 15 | 16 | let mut compressed = Vec::new(); 17 | input.read_to_end(&mut compressed)?; 18 | 19 | let mut decompressed = vec![0; header.get_decompress_buffer_size(downsampling)]; 20 | 21 | let decompress_start = Instant::now(); 22 | gfwx::decompress_simple(&compressed, &header, downsampling, false, &mut decompressed)?; 23 | let decompress_end = Instant::now(); 24 | 25 | println!( 26 | "Decompression took {} microseconds", 27 | decompress_end 28 | .sub(decompress_start) 29 | .whole_microseconds() 30 | ); 31 | 32 | save_image( 33 | decompressed, 34 | header.intent, 35 | header.get_downsampled_width(downsampling) as u32, 36 | header.get_downsampled_height(downsampling) as u32, 37 | &output_file, 38 | )?; 39 | 40 | Ok(()) 41 | } 42 | 43 | fn get_matches() -> clap::ArgMatches<'static> { 44 | clap::App::new("gfwx-rs test app") 45 | .version("1.0") 46 | .about("test app for gfwx-rs library") 47 | .arg( 48 | clap::Arg::with_name("INPUT") 49 | .help("Sets the input image file to use") 50 | .required(true) 51 | .index(1), 52 | ) 53 | .arg( 54 | clap::Arg::with_name("OUTPUT") 55 | .help("Sets the output file to write decompressed image") 56 | .required(true) 57 | .index(2), 58 | ) 59 | .arg( 60 | clap::Arg::with_name("downsampling") 61 | .help("Sets the downsampling scale for decompression") 62 | .short("d") 63 | .long("downsampling") 64 | .takes_value(true) 65 | .default_value("0") 66 | .validator(|v| { 67 | v.parse::().map_err(|e| e.to_string())?; 68 | Ok(()) 69 | }), 70 | ) 71 | .get_matches() 72 | } 73 | 74 | fn save_image( 75 | decompressed: Vec, 76 | intent: gfwx::Intent, 77 | width: u32, 78 | height: u32, 79 | path: impl AsRef, 80 | ) -> io::Result<()> { 81 | let decompressed_image = match intent { 82 | gfwx::Intent::RGB => { 83 | ImageRgb8(image::RgbImage::from_raw(width, height, decompressed).unwrap()) 84 | } 85 | gfwx::Intent::RGBA => { 86 | ImageRgba8(image::RgbaImage::from_raw(width, height, decompressed).unwrap()) 87 | } 88 | gfwx::Intent::BGR => ImageBgr8( 89 | image::ImageBuffer::, Vec>::from_raw(width, height, decompressed) 90 | .unwrap(), 91 | ), 92 | gfwx::Intent::BGRA => ImageBgra8( 93 | image::ImageBuffer::, Vec>::from_raw(width, height, decompressed) 94 | .unwrap(), 95 | ), 96 | _ => { 97 | return Err(io::Error::new( 98 | io::ErrorKind::InvalidData, 99 | "Unsupported image intent", 100 | )) 101 | } 102 | }; 103 | match intent { 104 | gfwx::Intent::BGR => decompressed_image.to_rgb().save(&path), 105 | gfwx::Intent::BGRA => decompressed_image.to_rgba().save(&path), 106 | _ => decompressed_image.save(&path), 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/processing/double_overlapping_chunks_iterator.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | pub struct DoubleOverlappingChunks<'a, T> { 4 | pub prev_left: &'a [T], 5 | pub left: &'a [T], 6 | pub middle: &'a mut [T], 7 | pub right: &'a [T], 8 | pub next_right: &'a [T], 9 | } 10 | 11 | pub struct DoubleOverlappingChunksIterator<'a, T> { 12 | prev_left: &'a [T], 13 | left: &'a [T], 14 | middle: &'a mut [T], 15 | right: &'a [T], 16 | remainder: &'a mut [T], 17 | } 18 | 19 | impl<'a, T> DoubleOverlappingChunksIterator<'a, T> { 20 | pub fn from_slice(slice: &'a mut [T], step: usize) -> DoubleOverlappingChunksIterator<'a, T> { 21 | if slice.len() <= step { 22 | // valid, but no elements will be generated by iterator 23 | return DoubleOverlappingChunksIterator { 24 | prev_left: &[], 25 | left: &[], 26 | middle: &mut [], 27 | right: &[], 28 | remainder: &mut [], 29 | }; 30 | } 31 | 32 | let (left, remainder) = slice.split_at_mut(step); 33 | 34 | if remainder.len() <= step { 35 | return DoubleOverlappingChunksIterator { 36 | prev_left: &[], 37 | left, 38 | middle: remainder, 39 | right: &[], 40 | remainder: &mut [], 41 | }; 42 | } 43 | 44 | let (middle, remainder) = remainder.split_at_mut(step); 45 | 46 | if remainder.len() <= step { 47 | return DoubleOverlappingChunksIterator { 48 | prev_left: &[], 49 | left, 50 | middle, 51 | right: remainder, 52 | remainder: &mut [], 53 | }; 54 | } 55 | 56 | let (right, remainder) = remainder.split_at_mut(step); 57 | 58 | DoubleOverlappingChunksIterator { 59 | prev_left: &[], 60 | left, 61 | middle, 62 | right, 63 | remainder, 64 | } 65 | } 66 | } 67 | 68 | impl<'a, T> Iterator for DoubleOverlappingChunksIterator<'a, T> { 69 | type Item = DoubleOverlappingChunks<'a, T>; 70 | 71 | fn next(&mut self) -> Option { 72 | let step = self.left.len(); 73 | 74 | if self.middle.is_empty() { 75 | return None; 76 | } 77 | 78 | let mut middle: &'a mut [T] = &mut []; 79 | mem::swap(&mut self.middle, &mut middle); 80 | 81 | if self.remainder.is_empty() { 82 | return Some(DoubleOverlappingChunks { 83 | prev_left: self.prev_left, 84 | left: self.left, 85 | middle, 86 | right: self.right, 87 | next_right: &[], 88 | }); 89 | } 90 | 91 | let mut remainder: &'a mut [T] = &mut []; 92 | mem::swap(&mut self.remainder, &mut remainder); 93 | 94 | let (next_middle, remainder) = if remainder.len() <= step { 95 | let empty_slice: &'a mut [T] = &mut []; 96 | (remainder, empty_slice) 97 | } else { 98 | remainder.split_at_mut(step) 99 | }; 100 | 101 | let (next_right, remainder) = if remainder.len() <= step { 102 | let empty_slice: &'a mut [T] = &mut []; 103 | (remainder, empty_slice) 104 | } else { 105 | remainder.split_at_mut(step) 106 | }; 107 | 108 | let result = Some(DoubleOverlappingChunks { 109 | prev_left: self.prev_left, 110 | left: self.left, 111 | middle, 112 | right: self.right, 113 | next_right: next_right as &'a [T], 114 | }); 115 | 116 | self.prev_left = self.left; 117 | self.left = self.right; 118 | self.right = next_right; 119 | self.middle = next_middle; 120 | self.remainder = remainder; 121 | 122 | result 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/lifting/linear.rs: -------------------------------------------------------------------------------- 1 | use super::{get_hint_do_parallel, lift, unlift}; 2 | use crate::config::Config; 3 | use crate::processing::{process_maybe_parallel_for_each, OverlappingChunksIterator}; 4 | 5 | #[inline(always)] 6 | pub fn lift_linear(mut image: &mut [&mut [i16]]) { 7 | lift( 8 | &mut image, 9 | Config::multithreading_factors().linear_horizontal_lifting, 10 | horizontal_lift, 11 | vertical_lift, 12 | ); 13 | } 14 | 15 | #[inline(always)] 16 | pub fn unlift_linear(mut image: &mut [&mut [i16]]) { 17 | unlift( 18 | &mut image, 19 | Config::multithreading_factors().linear_horizontal_lifting, 20 | horizontal_unlift, 21 | vertical_unlift, 22 | ); 23 | } 24 | 25 | #[inline(always)] 26 | unsafe fn horizontal_lifting_base( 27 | column: &mut [i16], 28 | step: usize, 29 | step_multiplier: usize, 30 | divider: i16, 31 | ) { 32 | let mut x = step * step_multiplier; 33 | while x < column.len() - step { 34 | let a = *column.get_unchecked_mut(x - step); 35 | let b = *column.get_unchecked_mut(x + step); 36 | *column.get_unchecked_mut(x) += (a + b) / (divider * 2); 37 | x += step * 2; 38 | } 39 | 40 | if x < column.len() { 41 | *column.get_unchecked_mut(x) += 42 | (i32::from(*column.get_unchecked_mut(x - step)) / i32::from(divider)) as i16; 43 | } 44 | } 45 | 46 | #[inline(always)] 47 | unsafe fn horizontal_lift(mut column: &mut [i16], step: usize) { 48 | horizontal_lifting_base(&mut column, step, 1, -1); 49 | horizontal_lifting_base(&mut column, step, 2, 2); 50 | } 51 | 52 | #[inline(always)] 53 | unsafe fn horizontal_unlift(mut column: &mut [i16], step: usize) { 54 | horizontal_lifting_base(&mut column, step, 2, -2); 55 | horizontal_lifting_base(&mut column, step, 1, 1); 56 | } 57 | 58 | #[inline(always)] 59 | unsafe fn vertical_lifting_base( 60 | left: &[&mut [i16]], 61 | middle: &mut [&mut [i16]], 62 | right: &[&mut [i16]], 63 | step: usize, 64 | divider: i16, 65 | ) { 66 | let middle_value = middle.get_unchecked_mut(0); 67 | 68 | let mut x = 0; 69 | while x < middle_value.len() { 70 | let c1 = *left.get_unchecked(0).get_unchecked(x); 71 | let c2 = if let Some(right_value) = right.first() { 72 | *right_value.get_unchecked(x) 73 | } else { 74 | c1 75 | }; 76 | 77 | *middle_value.get_unchecked_mut(x) += (c1 + c2) / divider; 78 | 79 | x += step; 80 | } 81 | } 82 | 83 | #[inline(always)] 84 | unsafe fn vertical_lift(mut image: &mut [&mut [i16]], step: usize) { 85 | let config_factor = Config::multithreading_factors().linear_vertical_lifting; 86 | let hint_do_parallel = get_hint_do_parallel(&image, config_factor); 87 | 88 | process_maybe_parallel_for_each( 89 | OverlappingChunksIterator::from_slice(&mut image, step), 90 | |(left, middle, right)| { 91 | vertical_lifting_base(left, middle, right, step, -2); 92 | }, 93 | hint_do_parallel, 94 | ); 95 | 96 | process_maybe_parallel_for_each( 97 | OverlappingChunksIterator::from_slice(&mut image[step..], step), 98 | |(left, middle, right)| { 99 | vertical_lifting_base(left, middle, right, step, 4); 100 | }, 101 | hint_do_parallel, 102 | ); 103 | } 104 | 105 | #[inline(always)] 106 | unsafe fn vertical_unlift(mut image: &mut [&mut [i16]], step: usize) { 107 | let config_factor = Config::multithreading_factors().linear_vertical_lifting; 108 | let hint_do_parallel = get_hint_do_parallel(&image, config_factor); 109 | 110 | process_maybe_parallel_for_each( 111 | OverlappingChunksIterator::from_slice(&mut image[step..], step), 112 | |(left, middle, right)| { 113 | vertical_lifting_base(left, middle, right, step, -4); 114 | }, 115 | hint_do_parallel, 116 | ); 117 | 118 | process_maybe_parallel_for_each( 119 | OverlappingChunksIterator::from_slice(&mut image, step), 120 | |(left, middle, right)| { 121 | vertical_lifting_base(left, middle, right, step, 2); 122 | }, 123 | hint_do_parallel, 124 | ); 125 | } 126 | -------------------------------------------------------------------------------- /src/bits/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; 4 | 5 | #[cfg(test)] 6 | mod test; 7 | 8 | pub trait BitsWriter { 9 | fn put_bits(&mut self, x: u32, bits: u32) -> io::Result<()>; 10 | fn flush_write_word(&mut self) -> io::Result<()>; 11 | } 12 | 13 | pub trait BitsReader { 14 | fn get_bits(&mut self, bits: u32) -> io::Result; 15 | fn get_zeros(&mut self, max_zeros: u32) -> io::Result; 16 | fn flush_read_word(&mut self); 17 | } 18 | 19 | pub struct BitsIOWriter<'a, W: WriteBytesExt> { 20 | write_stream: &'a mut W, 21 | write_cache: u32, 22 | index_bits: u32, 23 | } 24 | 25 | impl<'a, W: WriteBytesExt> BitsIOWriter<'a, W> { 26 | pub fn new(stream: &'a mut W) -> Self { 27 | BitsIOWriter { 28 | write_stream: stream, 29 | write_cache: 0, 30 | index_bits: 0, 31 | } 32 | } 33 | } 34 | 35 | impl BitsWriter for BitsIOWriter<'_, W> { 36 | fn put_bits(&mut self, x: u32, bits: u32) -> io::Result<()> { 37 | let mut new_bits = self.index_bits + bits; 38 | 39 | if new_bits < 32 { 40 | self.write_cache = (self.write_cache << bits) | x; 41 | } else { 42 | new_bits -= 32; 43 | 44 | self.write_stream.write_u32::( 45 | (self.write_cache << (bits - new_bits)) | (x >> new_bits), 46 | )?; 47 | 48 | self.write_cache = x; 49 | } 50 | 51 | self.index_bits = new_bits; 52 | 53 | Ok(()) 54 | } 55 | 56 | fn flush_write_word(&mut self) -> io::Result<()> { 57 | let index_bits = self.index_bits; 58 | self.put_bits(0, (32 - index_bits) % 32)?; 59 | 60 | Ok(()) 61 | } 62 | } 63 | 64 | #[derive(Debug)] 65 | pub struct BitsIOReader<'a, R: ReadBytesExt> { 66 | read_stream: &'a mut R, 67 | read_cache: u32, 68 | index_bits: u32, 69 | cache_filled: bool, 70 | } 71 | 72 | impl<'a, R: ReadBytesExt> BitsIOReader<'a, R> { 73 | pub fn new(stream: &'a mut R) -> Self { 74 | BitsIOReader { 75 | read_cache: 0xff_ff_ff_ff, 76 | read_stream: stream, 77 | index_bits: 0, 78 | cache_filled: false, 79 | } 80 | } 81 | 82 | fn next_u32(&mut self) -> io::Result { 83 | match self.read_stream.read_u32::() { 84 | Ok(n) => { 85 | self.cache_filled = true; 86 | Ok(n) 87 | } 88 | Err(e) => { 89 | self.cache_filled = false; 90 | Err(e) 91 | } 92 | } 93 | } 94 | } 95 | 96 | impl<'a, R: ReadBytesExt> BitsReader for BitsIOReader<'a, R> { 97 | fn get_bits(&mut self, bits: u32) -> io::Result { 98 | let mut new_bits = self.index_bits + bits; 99 | if !self.cache_filled { 100 | self.read_cache = self.next_u32()?; 101 | } 102 | 103 | let mut x = self.read_cache << self.index_bits; 104 | 105 | if new_bits >= 32 { 106 | if new_bits != 32 { 107 | let prev_index_bits = self.index_bits; 108 | self.read_cache = self.next_u32()?; 109 | x |= self.read_cache >> (32 - prev_index_bits); 110 | } else { 111 | // read whole word 112 | self.cache_filled = false; 113 | } 114 | new_bits -= 32; 115 | } 116 | self.index_bits = new_bits; 117 | 118 | Ok(x >> (32 - bits)) 119 | } 120 | 121 | fn get_zeros(&mut self, max_zeros: u32) -> io::Result { 122 | let mut new_bits = self.index_bits; 123 | if !self.cache_filled { 124 | self.read_cache = self.next_u32()?; 125 | } 126 | 127 | let mut b = self.read_cache; 128 | let mut x = 0; 129 | loop { 130 | if new_bits == 31 { 131 | let lsb_set = (b & 1u32) != 0; 132 | if !lsb_set { 133 | x += 1; 134 | } 135 | if lsb_set || x == max_zeros { 136 | self.index_bits = 0; 137 | self.cache_filled = false; 138 | return Ok(x); 139 | } 140 | 141 | self.read_cache = self.next_u32()?; 142 | b = self.read_cache; 143 | new_bits = 0; 144 | continue; 145 | } 146 | 147 | let msb_set = ((b << new_bits) & (1u32 << 31)) != 0; 148 | if !msb_set { 149 | x += 1; 150 | } 151 | if msb_set || x == max_zeros { 152 | self.index_bits = new_bits + 1; 153 | return Ok(x); 154 | } 155 | 156 | new_bits += 1; 157 | } 158 | } 159 | 160 | fn flush_read_word(&mut self) { 161 | self.cache_filled = false; 162 | self.index_bits = 0; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | use gfwx::*; 2 | 3 | #[test] 4 | fn test_compress_simple() { 5 | let builder = HeaderBuilder { 6 | width: 12, 7 | height: 8, 8 | layers: 1, 9 | channels: 3, 10 | quality: 124, 11 | chroma_scale: 8, 12 | block_size: BLOCK_DEFAULT, 13 | filter: Filter::Linear, 14 | encoder: Encoder::Contextual, 15 | intent: Intent::Generic, 16 | metadata_size: 0, 17 | }; 18 | let header = builder.build().unwrap(); 19 | 20 | let image = (0..header.get_image_size()) 21 | .map(|i| (i % 256) as u8) 22 | .collect::>(); 23 | 24 | let mut buffer = vec![0u8; image.len() * 4]; 25 | 26 | let mut color_transform_program = ColorTransformProgram::new(); 27 | 28 | color_transform_program 29 | .add_channel_transform( 30 | ChannelTransformBuilder::with_dest_channel(0) 31 | .add_channel_factor(1, -1) 32 | .set_chroma() 33 | .build(), 34 | ) 35 | .add_channel_transform( 36 | ChannelTransformBuilder::with_dest_channel(2) 37 | .add_channel_factor(1, -1) 38 | .set_chroma() 39 | .build(), 40 | ) 41 | .add_channel_transform( 42 | ChannelTransformBuilder::with_dest_channel(1) 43 | .add_channel_factor(0, 1) 44 | .add_channel_factor(2, 1) 45 | .set_denominator(4) 46 | .build(), 47 | ); 48 | 49 | let gfwx_size = 50 | compress_simple(&image, &header, &color_transform_program, &mut buffer).unwrap(); 51 | 52 | let expected = vec![ 53 | 71, 70, 87, 88, 1, 0, 0, 0, 12, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 229, 96, 15, 7, 0, 2, 0, 54 | 0, 0, 0, 0, 0, 183, 119, 85, 151, 246, 114, 119, 85, 0, 128, 50, 233, 1, 0, 0, 0, 1, 0, 0, 55 | 0, 1, 0, 0, 0, 0, 0, 0, 194, 160, 58, 0, 196, 0, 0, 0, 198, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 56 | 0, 0, 0, 0, 0, 204, 208, 54, 0, 128, 0, 0, 176, 1, 0, 0, 0, 204, 1, 0, 0, 0, 3, 0, 0, 0, 1, 57 | 0, 0, 0, 0, 0, 0, 162, 161, 80, 56, 198, 78, 228, 244, 1, 0, 0, 0, 44, 0, 0, 0, 162, 2, 0, 58 | 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 142, 1, 182, 138, 0, 0, 0, 64, 128, 203, 213, 138, 17, 0, 68, 59 | 2, 86, 128, 21, 48, 0, 0, 80, 1, 0, 0, 192, 133, 60 | ]; 61 | 62 | assert_eq!(expected.as_slice(), &buffer[..gfwx_size]); 63 | } 64 | 65 | #[test] 66 | fn test_decompress_simple() { 67 | let builder = HeaderBuilder { 68 | width: 12, 69 | height: 8, 70 | layers: 1, 71 | channels: 3, 72 | quality: 124, 73 | chroma_scale: 8, 74 | block_size: BLOCK_DEFAULT, 75 | filter: Filter::Linear, 76 | encoder: Encoder::Contextual, 77 | intent: Intent::Generic, 78 | metadata_size: 0, 79 | }; 80 | let header = builder.build().unwrap(); 81 | 82 | // contains color transform from test_compress_simple test 83 | let input = vec![ 84 | 183, 119, 85, 151, 246, 114, 119, 85, 0, 128, 50, 233, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 85 | 0, 0, 0, 194, 160, 58, 0, 196, 0, 0, 0, 198, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 86 | 204, 208, 54, 0, 128, 0, 0, 176, 1, 0, 0, 0, 204, 1, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 0, 87 | 0, 162, 161, 80, 56, 198, 78, 228, 244, 1, 0, 0, 0, 44, 0, 0, 0, 162, 2, 0, 0, 0, 4, 0, 0, 88 | 0, 1, 0, 0, 0, 142, 1, 182, 138, 0, 0, 0, 64, 128, 203, 213, 138, 17, 0, 68, 2, 86, 128, 89 | 21, 48, 0, 0, 80, 1, 0, 0, 192, 133, 90 | ]; 91 | 92 | let expected = vec![ 93 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 20, 21, 22, 23, 94 | 24, 25, 26, 27, 28, 29, 30, 31, 29, 30, 31, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 95 | 49, 50, 51, 51, 52, 53, 54, 55, 56, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 65, 66, 96 | 67, 72, 73, 74, 74, 75, 76, 77, 78, 79, 81, 82, 83, 85, 86, 87, 87, 88, 89, 90, 91, 92, 92, 97 | 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 101, 102, 103, 108, 109, 110, 110, 111, 98 | 112, 113, 114, 115, 117, 118, 119, 121, 122, 123, 124, 125, 126, 126, 127, 128, 128, 129, 99 | 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 137, 138, 139, 144, 145, 146, 146, 147, 100 | 148, 149, 150, 151, 153, 154, 155, 158, 159, 160, 160, 161, 162, 162, 163, 164, 164, 165, 101 | 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 173, 174, 175, 179, 180, 181, 187, 183, 102 | 184, 173, 189, 190, 183, 191, 192, 193, 194, 195, 195, 196, 197, 198, 199, 200, 200, 201, 103 | 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 209, 210, 211, 215, 216, 217, 227, 219, 104 | 220, 197, 228, 229, 212, 228, 229, 227, 228, 229, 230, 231, 232, 234, 235, 236, 237, 238, 105 | 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 246, 247, 248, 252, 253, 254, 255, 0, 0, 106 | 0, 2, 3, 2, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 107 | 27, 28, 29, 27, 28, 29, 108 | ]; 109 | 110 | let mut actual = vec![0u8; expected.len()]; 111 | 112 | decompress_simple(&input, &header, 0, false, &mut actual).unwrap(); 113 | 114 | assert_eq!(expected.len(), actual.len()); 115 | assert_eq!(expected, actual); 116 | } 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gfwx-rs 2 | 3 | [![crates.io](https://img.shields.io/crates/v/gfwx.svg)](https://crates.io/crates/gfwx) 4 | [![docs](https://docs.rs/gfwx/badge.svg)](https://docs.rs/gfwx) 5 | [![build](https://travis-ci.com/vaffeine/gfwx-rs.svg?branch=master)](https://travis-ci.com/vaffeine/gfwx-rs) 6 | [![codecov](https://codecov.io/gh/vaffeine/gfwx-rs/branch/master/graph/badge.svg)](https://codecov.io/gh/vaffeine/gfwx-rs/branch/master) 7 | 8 | Implementation of [GFWX](http://www.gfwx.org/) image compression algorithm developed by Graham Fyffe. 9 | Library uses [rayon](https://github.com/rayon-rs/rayon) for parallelization as a default feature. 10 | 11 | ## Getting Started 12 | 13 | ### Prerequisites 14 | 15 | To use the library you need to have Rust installed on your machine. The library works on stable Rust branch and doesn't require nightly. 16 | 17 | ### Using 18 | 19 | gfwx-rs is available on crates.io. The recommended way to use it is to add a line into your Cargo.toml such as: 20 | ```toml 21 | [dependencies] 22 | gfwx = "0.2" 23 | ``` 24 | 25 | or, if you don't want to use rayon: 26 | ```toml 27 | [dependencies] 28 | gfwx = { version = "0.2", default-features = false } 29 | ``` 30 | 31 | Basic usage for compression: 32 | 33 | ```rust 34 | extern crate gfwx; 35 | 36 | fn main() { 37 | let image = ...; 38 | 39 | let builder = gfwx::HeaderBuilder { 40 | width: image.width(), 41 | height: image.height(), 42 | layers: 1, 43 | channels: image.channels(), 44 | quality: gfwx::QUALITY_MAX, 45 | chroma_scale: 8, 46 | block_size: gfwx::BLOCK_DEFAULT, 47 | filter: gfwx::Filter::Linear, 48 | encoder: gfwx::Encoder::Turbo, 49 | intent: gfwx::Intent::RGBA, 50 | metadata_size: 0, 51 | }; 52 | let header = builder.build().unwrap(); 53 | 54 | let buffer = vec![0; 2 * image.len()]; // 2 times original size should always be enough 55 | header.encode(&mut buffer)?; 56 | let gfwx_size = gfwx::compress_simple( 57 | image.as_slice(), 58 | &header, 59 | &gfwx::ColorTransformProgram::new(), // no color transform 60 | &mut buffer, 61 | ).unwrap(); 62 | buffer.truncate(gfwx_size); 63 | } 64 | ``` 65 | 66 | Basic usage for decompression: 67 | 68 | ```rust 69 | extern crate gfwx; 70 | 71 | fn main() { 72 | let mut compressed = ...; 73 | 74 | let header = gfwx::Header::decode(&mut compressed).unwrap(); 75 | 76 | let mut decompressed = vec![0; header.get_estimated_decompress_buffer_size()]; 77 | let next_point_of_interest = gfwx::decompress_simple( 78 | &mut compressed, 79 | &header, 80 | 0, // no downsamping 81 | false, // do not test, full decompress 82 | &mut decompressed, 83 | ).unwrap(); 84 | 85 | ... 86 | } 87 | ``` 88 | 89 | ## Running the tests 90 | 91 | ### Unit tests 92 | 93 | To run unit tests: 94 | ```bash 95 | cargo test 96 | ``` 97 | 98 | There are also tests for the case when build should fail. You can run them with 99 | ```bash 100 | cargo test --features test_build_fails 101 | ``` 102 | 103 | ### Functional tests 104 | To run functional tests, that use actual images, you can use `ci/func_tests.sh`: 105 | ```bash 106 | ci/func_tests.sh ci/test_images/ 107 | ``` 108 | 109 | This command will build reference application, build examples and run functional tests 110 | using prepared images in `ci/test_images/` folder in the `/tmp/gfwx` directory 111 | (so working directory stays clean). 112 | 113 | ### Benchmarks 114 | 115 | There are also [criterion](https://github.com/japaric/criterion.rs) benchmarks which you can run with 116 | ```bash 117 | cargo bench 118 | ``` 119 | 120 | ### Examples 121 | 122 | Examples folder contains 3 applications: 123 | 1. `compress` - compresses an input image to gfwx 124 | 2. `decompress` - decompresses a gfwx file 125 | 3. `compare` - compares two images excluding metadata. Useful for comparing the input image and the decompressed one, because they may have the same "pixels" but different metadata, which means these files will have different checksum 126 | 127 | ## Features 128 | 129 | Library supports all features of original implementation except: 130 | - It only supports u8 data, when original implementation supports 8-bit and 16-bit data both signed and unsigned 131 | - Bayer mode is not supported 132 | 133 | However, original implementation supports only channels in interleaved format (for example, [R1, G1, B1, R2, B2, G2, ...]) and always transform channels to planar format. 134 | This is not suitable for color spaces which already use planar channel format (for example, YUV420). 135 | 136 | For this type of data, our library provides low-level `compress_aux_data` and `decompress_aux_data` functions. 137 | This functions do not encode header, execute and encode ColorTransformProgram and accept 16-bit image data in planar channels format with boost already applied. 138 | 139 | These functions are a little bit more complex to use, but provide more flexibility in case you need only image data compression and decompression. 140 | You can manually encode the header with `Header::encode()`, encode `ColorTransformProgram` with `ColorTransformProgram::encode()` 141 | and execute it and apply boost with `ColorTransformProgram::transform()` (for planar channels) and `ColorTransformProgram::transform_and_to_planar()` (for interleaved channels). 142 | Also, instead of using `ColorTransformProgram` you can use `interleaved_to_planar()` and `planar_to_interleaved()` that also can skip some channels during transformation (for example, skip Alpha channel in RGBA). 143 | 144 | You can find a complete example on how to use these functions in `examples/test_app.rs` or by looking into `compress_simple` and `decompress_simple` implementation in `src/lib.rs`. 145 | -------------------------------------------------------------------------------- /ci/test_helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | """ 4 | Script to automate testing of the library using binaries 5 | from examples directory and reference application 6 | """ 7 | 8 | import argparse 9 | import filecmp 10 | import os 11 | import shutil 12 | import subprocess 13 | import sys 14 | 15 | OUT_DIR = os.path.join(os.getcwd(), 'gfwx_out') 16 | 17 | 18 | def get_pictures(directory): 19 | for root, _, files in os.walk(directory): 20 | for f in files: 21 | if os.path.splitext(f)[-1].lower() == '.png': 22 | yield os.path.join(os.path.relpath(root, directory), f) 23 | 24 | 25 | def mkdir_if_not_exist(path): 26 | try: 27 | os.makedirs(os.path.dirname(path)) 28 | except OSError: 29 | pass 30 | 31 | 32 | def get_args(): 33 | parser = argparse.ArgumentParser() 34 | parser.add_argument('input_dir', type=str) 35 | return parser.parse_args() 36 | 37 | 38 | class Params(object): 39 | def __init__(self, name, quality, gfwx_filter, encoder, downsampling): 40 | self.name = name 41 | self.quality = quality 42 | self.filter = gfwx_filter 43 | self.encoder = encoder 44 | self.downsampling = downsampling 45 | 46 | 47 | def compress(image, params): 48 | output = os.path.join( 49 | OUT_DIR, '{}.gfwx'.format( 50 | os.path.splitext(os.path.basename(image))[0])) 51 | mkdir_if_not_exist(output) 52 | gfwx_params = [ 53 | os.path.join(os.getcwd(), 'compress'), 54 | image, 55 | output, 56 | '--quality', 57 | str(params.quality), 58 | '--filter', 59 | params.filter, 60 | '--encoder', 61 | params.encoder, 62 | '--intent', 63 | 'bgr', 64 | ] 65 | try: 66 | subprocess.check_output(gfwx_params) 67 | return (True, ) 68 | except subprocess.CalledProcessError: 69 | return (False, 'failed to compress image') 70 | 71 | 72 | def decompress(image, params): 73 | gfwx = os.path.join( 74 | OUT_DIR, '{}.gfwx'.format( 75 | os.path.splitext(os.path.basename(image))[0])) 76 | mkdir_if_not_exist(gfwx) 77 | output = os.path.join(OUT_DIR, os.path.basename(image)) 78 | mkdir_if_not_exist(output) 79 | gfwx_params = [ 80 | os.path.join(os.getcwd(), 'decompress'), 81 | gfwx, 82 | output, 83 | '--downsampling', 84 | str(params.downsampling), 85 | ] 86 | try: 87 | subprocess.check_output(gfwx_params) 88 | return (True, ) 89 | except subprocess.CalledProcessError: 90 | return (False, 'failed to decompress the image') 91 | 92 | 93 | def compare_with_reference(image, params): 94 | orig_gfwx_path = os.path.join( 95 | OUT_DIR, '{}.orig_gfwx'.format( 96 | os.path.splitext(os.path.basename(image))[0])) 97 | mkdir_if_not_exist(orig_gfwx_path) 98 | 99 | try: 100 | subprocess.check_output([ 101 | os.path.join(os.getcwd(), 'reference_test_app'), 102 | image, 103 | orig_gfwx_path, 104 | str(params.quality), 105 | params.filter, 106 | params.encoder, 107 | ]) 108 | except subprocess.CalledProcessError: 109 | return (False, "reference failed to compress the image") 110 | 111 | gfwx_path = os.path.join( 112 | OUT_DIR, '{}.gfwx'.format( 113 | os.path.splitext(os.path.basename(image))[0])) 114 | same = filecmp.cmp(orig_gfwx_path, gfwx_path, shallow=False) 115 | if same: 116 | return (True, ) 117 | else: 118 | return (False, "reference produced different output") 119 | 120 | 121 | def compare_with_decompressed(image, params): 122 | decompressed = os.path.join(OUT_DIR, os.path.basename(image)) 123 | try: 124 | subprocess.check_output([ 125 | os.path.join(os.getcwd(), 'compare'), 126 | image, 127 | decompressed, 128 | ]) 129 | return (True, ) 130 | except subprocess.CalledProcessError: 131 | return (False, "compressed and decompressed images are different") 132 | 133 | 134 | def test(test_case, image, params): 135 | result = test_case(image, params) 136 | if not result[0]: 137 | print('{}: {}'.format(os.path.basename(image), result[1])) 138 | err_path = os.path.join(os.getcwd(), 'gfwx_fails', 139 | os.path.basename(os.path.basename(image))) 140 | mkdir_if_not_exist(err_path) 141 | shutil.copy(image, err_path) 142 | return False 143 | else: 144 | return True 145 | 146 | 147 | def main(): 148 | tests = [ 149 | (Params('lossless_linear_turbo', 1024, 'linear', 'turbo', 0), [ 150 | compress, decompress, compare_with_reference, 151 | compare_with_decompressed 152 | ]), 153 | (Params('losy_cubic_fast', 124, 'cubic', 'fast', 0), 154 | [compress, decompress, compare_with_reference]), 155 | (Params('losy_cubic_context', 64, 'cubic', 'contextual', 0), 156 | [compress, decompress, compare_with_reference]), 157 | ] 158 | input_dir = get_args().input_dir 159 | 160 | test_failed = False 161 | for params, test_cases in tests: 162 | print('==== Case: {}'.format(params.name)) 163 | for image in get_pictures(input_dir): 164 | for case in test_cases: 165 | if test(case, os.path.join(input_dir, image), params): 166 | test_failed = True 167 | continue 168 | 169 | print('\t{}: OK'.format(image)) 170 | 171 | if test_failed: 172 | return 1 173 | else: 174 | return 0 175 | 176 | 177 | if __name__ == '__main__': 178 | sys.exit(main()) 179 | -------------------------------------------------------------------------------- /src/lifting/cubic.rs: -------------------------------------------------------------------------------- 1 | use super::{get_hint_do_parallel, lift, unlift}; 2 | use crate::config::Config; 3 | use crate::processing::{ 4 | process_maybe_parallel_for_each, DoubleOverlappingChunks, DoubleOverlappingChunksIterator, 5 | }; 6 | 7 | #[inline(always)] 8 | pub fn lift_cubic(mut image: &mut [&mut [i16]]) { 9 | lift( 10 | &mut image, 11 | Config::multithreading_factors().cubic_horizontal_lifting, 12 | horizontal_lift, 13 | vertical_lift, 14 | ); 15 | } 16 | 17 | #[inline(always)] 18 | pub fn unlift_cubic(mut image: &mut [&mut [i16]]) { 19 | unlift( 20 | &mut image, 21 | Config::multithreading_factors().cubic_horizontal_lifting, 22 | horizontal_unlift, 23 | vertical_unlift, 24 | ); 25 | } 26 | 27 | #[inline(always)] 28 | unsafe fn horizontal_lifting_base( 29 | column: &mut [i16], 30 | step: usize, 31 | c0_column_start: usize, 32 | c2_step_multiplier: usize, 33 | step_start_multiplier: usize, 34 | divider: i32, 35 | ) { 36 | let mut c0 = *column.get_unchecked(c0_column_start); 37 | let mut c1 = c0; 38 | let mut c2 = if c2_step_multiplier * step < column.len() { 39 | *column.get_unchecked(c2_step_multiplier * step) 40 | } else { 41 | c0 42 | }; 43 | 44 | let mut x = step_start_multiplier * step; 45 | if column.len() > 3 * step { 46 | while x < column.len() - 3 * step { 47 | let c3 = *column.get_unchecked(3 * step + x); 48 | *column.get_unchecked_mut(x) += (cubic(c0, c1, c2, c3) / divider) as i16; 49 | c0 = c1; 50 | c1 = c2; 51 | c2 = c3; 52 | x += step * 2; 53 | } 54 | } 55 | while x < column.len() { 56 | *column.get_unchecked_mut(x) += (cubic(c0, c1, c2, c2) / divider) as i16; 57 | c0 = c1; 58 | c1 = c2; 59 | x += step * 2; 60 | } 61 | } 62 | 63 | #[inline(always)] 64 | unsafe fn vertical_lifting_base( 65 | chunks: &mut DoubleOverlappingChunks<'_, &mut [i16]>, 66 | step: usize, 67 | divider: i32, 68 | ) { 69 | let mut x = 0; 70 | while x < chunks.middle.get_unchecked(0).len() { 71 | let c1 = *chunks.left.get_unchecked(0).get_unchecked(x); 72 | let c2 = if let Some(right_value) = chunks.right.first() { 73 | *right_value.get_unchecked(x) 74 | } else { 75 | c1 76 | }; 77 | let c0 = if let Some(prev_left_value) = chunks.prev_left.first() { 78 | *prev_left_value.get_unchecked(x) 79 | } else { 80 | c1 81 | }; 82 | let c3 = if let Some(next_right_value) = chunks.next_right.first() { 83 | *next_right_value.get_unchecked(x) 84 | } else { 85 | c2 86 | }; 87 | 88 | *chunks.middle.get_unchecked_mut(0).get_unchecked_mut(x) += 89 | (cubic(c0, c1, c2, c3) / divider) as i16; 90 | x += step; 91 | } 92 | } 93 | 94 | #[inline(always)] 95 | unsafe fn horizontal_lift(mut column: &mut [i16], step: usize) { 96 | horizontal_lifting_base(&mut column, step, 0, 2, 1, -1); 97 | horizontal_lifting_base(&mut column, step, step, 3, 2, 2); 98 | } 99 | 100 | #[inline(always)] 101 | unsafe fn horizontal_unlift(mut column: &mut [i16], step: usize) { 102 | horizontal_lifting_base(&mut column, step, step, 3, 2, -2); 103 | horizontal_lifting_base(&mut column, step, 0, 2, 1, 1); 104 | } 105 | 106 | #[inline(always)] 107 | unsafe fn vertical_lift(mut image: &mut [&mut [i16]], step: usize) { 108 | let config_factor = Config::multithreading_factors().cubic_vertical_lifting; 109 | let hint_do_parallel = get_hint_do_parallel(&image, config_factor); 110 | process_maybe_parallel_for_each( 111 | DoubleOverlappingChunksIterator::from_slice(&mut image, step), 112 | |mut chunks| { 113 | vertical_lifting_base(&mut chunks, step, -1); 114 | }, 115 | hint_do_parallel, 116 | ); 117 | process_maybe_parallel_for_each( 118 | DoubleOverlappingChunksIterator::from_slice(&mut image[step..], step), 119 | |mut chunks| { 120 | vertical_lifting_base(&mut chunks, step, 2); 121 | }, 122 | hint_do_parallel, 123 | ); 124 | } 125 | 126 | #[inline(always)] 127 | unsafe fn vertical_unlift(mut image: &mut [&mut [i16]], step: usize) { 128 | let config_factor = Config::multithreading_factors().cubic_vertical_lifting; 129 | let hint_do_parallel = get_hint_do_parallel(&image, config_factor); 130 | process_maybe_parallel_for_each( 131 | DoubleOverlappingChunksIterator::from_slice(&mut image[step..], step), 132 | |mut chunks| { 133 | vertical_lifting_base(&mut chunks, step, -2); 134 | }, 135 | hint_do_parallel, 136 | ); 137 | process_maybe_parallel_for_each( 138 | DoubleOverlappingChunksIterator::from_slice(&mut image, step), 139 | |mut chunks| { 140 | vertical_lifting_base(&mut chunks, step, 1); 141 | }, 142 | hint_do_parallel, 143 | ); 144 | } 145 | 146 | fn median(mut a: i32, mut b: i32, mut c: i32) -> i32 { 147 | use std::mem; 148 | 149 | if a > b { 150 | mem::swap(&mut a, &mut b); 151 | } 152 | if b > c { 153 | mem::swap(&mut b, &mut c); 154 | } 155 | if a > b { 156 | mem::swap(&mut a, &mut b); 157 | } 158 | 159 | b 160 | } 161 | 162 | fn round_fraction(num: i32, denom: i32) -> i32 { 163 | if num < 0 { 164 | (num - denom / 2) / denom 165 | } else { 166 | (num + denom / 2) / denom 167 | } 168 | } 169 | 170 | pub fn cubic(c0: i16, c1: i16, c2: i16, c3: i16) -> i32 { 171 | let c0 = i32::from(c0); 172 | let c1 = i32::from(c1); 173 | let c2 = i32::from(c2); 174 | let c3 = i32::from(c3); 175 | let num = -c0 + 9 * (c1 + c2) - c3; 176 | median(round_fraction(num, 16), c1, c2) 177 | } 178 | -------------------------------------------------------------------------------- /examples/compress.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, fs, io::prelude::*, path::Path}; 2 | use core::ops::Sub; 3 | 4 | use image::DynamicImage::*; 5 | use time::Instant; 6 | 7 | fn main() -> Result<(), Box> { 8 | let matches = get_matches(); 9 | let input_file = matches.value_of("INPUT").unwrap(); 10 | let output_gfwx = matches.value_of("OUTPUT").unwrap(); 11 | let quality = matches.value_of("quality").unwrap().parse().unwrap(); 12 | let block_size = matches.value_of("block-size").unwrap().parse().unwrap(); 13 | let filter = match matches.value_of("filter").unwrap() { 14 | "linear" => gfwx::Filter::Linear, 15 | "cubic" => gfwx::Filter::Cubic, 16 | _ => panic!("clap betrayed us"), 17 | }; 18 | let encoder = match matches.value_of("encoder").unwrap() { 19 | "fast" => gfwx::Encoder::Fast, 20 | "turbo" => gfwx::Encoder::Turbo, 21 | "contextual" => gfwx::Encoder::Contextual, 22 | _ => panic!("clap betrayed us again"), 23 | }; 24 | 25 | let file_path = Path::new(&input_file); 26 | let image = image::open(&file_path)?; 27 | let (width, height, image, channels, intent) = 28 | into_raw_image(image, matches.value_of("intent")); 29 | 30 | let builder = gfwx::HeaderBuilder { 31 | width, 32 | height, 33 | layers: 1, 34 | channels, 35 | quality, 36 | chroma_scale: 8, 37 | block_size, 38 | filter, 39 | encoder, 40 | intent, 41 | metadata_size: 0, 42 | }; 43 | let header = builder.build().unwrap(); 44 | 45 | let mut compressed = vec![0; 2 * image.len()]; 46 | 47 | let compress_start = Instant::now(); 48 | let gfwx_size = gfwx::compress_simple( 49 | &image, 50 | &header, 51 | &gfwx::ColorTransformProgram::new(), 52 | &mut compressed, 53 | )?; 54 | let compress_end = Instant::now(); 55 | 56 | println!( 57 | "Compression took {} microseconds", 58 | compress_end 59 | .sub(compress_start) 60 | .whole_microseconds() 61 | ); 62 | 63 | let mut f = fs::File::create(output_gfwx)?; 64 | f.write_all(&compressed[0..gfwx_size])?; 65 | 66 | Ok(()) 67 | } 68 | 69 | fn get_matches() -> clap::ArgMatches<'static> { 70 | clap::App::new("gfwx-rs test app") 71 | .version("1.0") 72 | .about("test app for gfwx-rs library") 73 | .arg( 74 | clap::Arg::with_name("INPUT") 75 | .help("Sets the input image file to use") 76 | .required(true) 77 | .index(1), 78 | ) 79 | .arg( 80 | clap::Arg::with_name("OUTPUT") 81 | .help("Sets the output file to write compressed gfwx") 82 | .required(true) 83 | .index(2), 84 | ) 85 | .arg( 86 | clap::Arg::with_name("quality") 87 | .help("Sets the quality of compression, ranges from 1 to 1024") 88 | .short("q") 89 | .long("quality") 90 | .takes_value(true) 91 | .default_value("1024") 92 | .validator(|v| { 93 | let v = v.parse::().map_err(|e| e.to_string())?; 94 | if v > 0 && v <= 1024 { 95 | Ok(()) 96 | } else { 97 | Err("Quality must be in range (1..=1024)".to_string()) 98 | } 99 | }), 100 | ) 101 | .arg( 102 | clap::Arg::with_name("block-size") 103 | .help("Sets the block size for compression, ranges from 1 to 30") 104 | .long("block-size") 105 | .takes_value(true) 106 | .default_value("7") 107 | .validator(|v| { 108 | let v = v.parse::().map_err(|e| e.to_string())?; 109 | if v > 0 && v <= 30 { 110 | Ok(()) 111 | } else { 112 | Err("Block size must be in range (1..=30)".to_string()) 113 | } 114 | }), 115 | ) 116 | .arg( 117 | clap::Arg::with_name("filter") 118 | .help("Set the filter for lifting scheme") 119 | .short("f") 120 | .long("filter") 121 | .takes_value(true) 122 | .default_value("linear") 123 | .possible_values(&["linear", "cubic"]), 124 | ) 125 | .arg( 126 | clap::Arg::with_name("encoder") 127 | .help("Set the encoder mode") 128 | .short("e") 129 | .long("encoder") 130 | .takes_value(true) 131 | .default_value("turbo") 132 | .possible_values(&["turbo", "fast", "contextual"]), 133 | ) 134 | .arg( 135 | clap::Arg::with_name("intent") 136 | .help("Set the image intentional color space. If not specified, original image intent is used") 137 | .short("i") 138 | .long("intent") 139 | .takes_value(true) 140 | .possible_values(&["rgb", "rgba", "bgr", "bgra"]), 141 | ) 142 | .get_matches() 143 | } 144 | 145 | fn into_raw_image( 146 | image: image::DynamicImage, 147 | user_intent: Option<&str>, 148 | ) -> (u32, u32, Vec, u16, gfwx::Intent) { 149 | match user_intent { 150 | Some(v) => match v { 151 | "rgb" => { 152 | let i = image.to_rgb(); 153 | (i.width(), i.height(), i.into_raw(), 3, gfwx::Intent::RGB) 154 | } 155 | "rgba" => { 156 | let i = image.to_rgba(); 157 | (i.width(), i.height(), i.into_raw(), 4, gfwx::Intent::RGBA) 158 | } 159 | "bgr" => { 160 | let i = image.to_bgr(); 161 | (i.width(), i.height(), i.into_raw(), 3, gfwx::Intent::BGR) 162 | } 163 | "bgra" => { 164 | let i = image.to_bgra(); 165 | (i.width(), i.height(), i.into_raw(), 4, gfwx::Intent::BGRA) 166 | } 167 | _ => panic!("clap?"), 168 | }, 169 | None => match image { 170 | ImageRgb8(i) => (i.width(), i.height(), i.into_raw(), 3, gfwx::Intent::RGB), 171 | ImageBgr8(i) => (i.width(), i.height(), i.into_raw(), 3, gfwx::Intent::BGR), 172 | ImageRgba8(i) => (i.width(), i.height(), i.into_raw(), 4, gfwx::Intent::RGBA), 173 | ImageBgra8(i) => (i.width(), i.height(), i.into_raw(), 4, gfwx::Intent::BGRA), 174 | _ => panic!("unsupported image color space"), 175 | }, 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/processing/image.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | marker::PhantomData, 3 | ops::{Index, IndexMut}, 4 | slice, 5 | }; 6 | 7 | pub struct Image<'a, T> { 8 | data: &'a mut [T], 9 | size: (usize, usize), 10 | channels: usize, 11 | } 12 | 13 | impl<'a, T> Image<'a, T> { 14 | pub fn from_slice(data: &'a mut [T], size: (usize, usize), channels: usize) -> Image<'a, T> { 15 | Image { 16 | data, 17 | size, 18 | channels, 19 | } 20 | } 21 | 22 | pub fn into_chunks_mut(self, chunk_size: usize, step: usize) -> ImageChunkIteratorMut<'a, T> { 23 | let (width, height) = self.size; 24 | 25 | ImageChunkIteratorMut { 26 | data: self.data, 27 | state: ImageChunkIteratorState { 28 | chunk_size, 29 | step, 30 | x: 0, 31 | y: 0, 32 | channel: 0, 33 | width, 34 | height, 35 | channels: self.channels, 36 | }, 37 | } 38 | } 39 | } 40 | 41 | struct ImageChunkIteratorState { 42 | chunk_size: usize, 43 | step: usize, 44 | pub x: usize, 45 | pub y: usize, 46 | channel: usize, 47 | width: usize, 48 | height: usize, 49 | channels: usize, 50 | } 51 | 52 | type Range = (usize, usize); 53 | 54 | impl ImageChunkIteratorState { 55 | pub fn next_chunk(&mut self) -> Option<(Range, Range, usize, usize)> { 56 | if self.channel >= self.channels { 57 | return None; 58 | } 59 | 60 | let (x, y) = (self.x, self.y); 61 | let channel = self.channel; 62 | 63 | self.x += self.chunk_size; 64 | 65 | if self.x >= self.width { 66 | self.x = 0; 67 | self.y += self.chunk_size; 68 | } 69 | 70 | if self.y >= self.height { 71 | self.y = 0; 72 | self.channel += 1; 73 | } 74 | 75 | Some(( 76 | (x, (x + self.chunk_size).min(self.width)), 77 | (y, (y + self.chunk_size).min(self.height)), 78 | channel, 79 | channel * self.width * self.height, 80 | )) 81 | } 82 | } 83 | 84 | pub struct ImageChunkIteratorMut<'a, T> { 85 | data: &'a mut [T], 86 | state: ImageChunkIteratorState, 87 | } 88 | 89 | pub struct ImageChunkMut<'a, T> { 90 | data_ptr: *mut T, 91 | data_len: usize, 92 | phantom_data: PhantomData<&'a T>, 93 | image_width: usize, 94 | channel_size: usize, 95 | channel_start: usize, 96 | 97 | pub x_range: (usize, usize), 98 | pub y_range: (usize, usize), 99 | pub channel: usize, 100 | pub step: usize, 101 | } 102 | 103 | impl ImageChunkMut<'_, T> { 104 | pub unsafe fn get_unchecked(&self, y: usize, x: usize) -> &T { 105 | let index = self.channel_start + y * self.image_width + x; 106 | 107 | debug_assert!( 108 | self.is_owned_zone(y, x) || !self.is_writeable_zone(y, x), 109 | "Access to mutable neighbour image chunk parts is not permitted!" 110 | ); 111 | debug_assert!( 112 | index < self.channel_start + self.channel_size, 113 | "Cross-channel indexing is not permitted!" 114 | ); 115 | debug_assert!(index < self.data_len); 116 | 117 | &*self.data_ptr.add(index) 118 | } 119 | 120 | pub unsafe fn get_unchecked_mut(&mut self, y: usize, x: usize) -> &mut T { 121 | let index = self.channel_start + y * self.image_width + x; 122 | 123 | debug_assert!( 124 | self.is_owned_zone(y, x) && self.is_writeable_zone(y, x), 125 | "Write to immutable image chunk parts is not permitted!", 126 | ); 127 | debug_assert!( 128 | index < self.channel_start + self.channel_size, 129 | "Cross-channel indexing is not permitted!" 130 | ); 131 | debug_assert!(index < self.data_len); 132 | 133 | &mut *self.data_ptr.add(index) 134 | } 135 | 136 | fn is_writeable_zone(&self, y: usize, x: usize) -> bool { 137 | if y == self.y_range.0 && x == self.x_range.0 { 138 | return true; 139 | } 140 | 141 | if y % self.step == 0 { 142 | let x_step = if (y & self.step) != 0 { 143 | self.step 144 | } else { 145 | self.step * 2 146 | }; 147 | if (x + (x_step - self.step)) % x_step == 0 { 148 | return true; 149 | } 150 | } 151 | 152 | false 153 | } 154 | 155 | fn is_owned_zone(&self, y: usize, x: usize) -> bool { 156 | x >= self.x_range.0 && x < self.x_range.1 && y >= self.y_range.0 && y < self.y_range.1 157 | } 158 | } 159 | 160 | unsafe impl Send for ImageChunkMut<'_, T> {} 161 | unsafe impl Sync for ImageChunkMut<'_, T> {} 162 | 163 | impl Index<(usize, usize)> for ImageChunkMut<'_, T> { 164 | type Output = T; 165 | 166 | fn index(&self, (y, x): (usize, usize)) -> &Self::Output { 167 | let index = self.channel_start + y * self.image_width + x; 168 | 169 | assert!( 170 | self.is_owned_zone(y, x) || !self.is_writeable_zone(y, x), 171 | "Access to mutable neighbour image chunk parts is not permitted!" 172 | ); 173 | assert!( 174 | index < self.channel_start + self.channel_size, 175 | "Cross-channel indexing is not permitted!" 176 | ); 177 | 178 | unsafe { 179 | let data = slice::from_raw_parts_mut(self.data_ptr, self.data_len); 180 | &data[index] 181 | } 182 | } 183 | } 184 | 185 | impl IndexMut<(usize, usize)> for ImageChunkMut<'_, T> { 186 | fn index_mut(&mut self, (y, x): (usize, usize)) -> &mut Self::Output { 187 | let index = self.channel_start + y * self.image_width + x; 188 | 189 | assert!( 190 | self.is_owned_zone(y, x) && self.is_writeable_zone(y, x), 191 | "Write to immutable image chunk parts is not permitted!", 192 | ); 193 | assert!( 194 | index < self.channel_start + self.channel_size, 195 | "Cross-channel indexing is not permitted!" 196 | ); 197 | 198 | unsafe { 199 | let data = slice::from_raw_parts_mut(self.data_ptr, self.data_len); 200 | &mut data[index] 201 | } 202 | } 203 | } 204 | 205 | impl<'a, T> Iterator for ImageChunkIteratorMut<'a, T> { 206 | type Item = ImageChunkMut<'a, T>; 207 | 208 | fn next(&mut self) -> Option { 209 | self.state 210 | .next_chunk() 211 | .map(|(x_range, y_range, channel, channel_start)| ImageChunkMut { 212 | data_ptr: self.data.as_mut_ptr(), 213 | data_len: self.data.len(), 214 | phantom_data: PhantomData, 215 | image_width: self.state.width, 216 | channel_size: self.state.width * self.state.height, 217 | channel_start, 218 | channel, 219 | x_range, 220 | y_range, 221 | step: self.state.step, 222 | }) 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/header/mod.rs: -------------------------------------------------------------------------------- 1 | // FromPrimitive and ToPrimitive causes clippy error, so we disable it until 2 | // https://github.com/rust-num/num-derive/issues/20 is fixed 3 | #![cfg_attr(feature = "cargo-clippy", allow(clippy::useless_attribute))] 4 | 5 | use std::{io, usize}; 6 | 7 | use crate::errors::HeaderErr; 8 | use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; 9 | use num_traits::{FromPrimitive, ToPrimitive}; 10 | 11 | mod builder; 12 | #[cfg(test)] 13 | mod test; 14 | 15 | pub use self::builder::HeaderBuilder; 16 | 17 | pub const QUALITY_MAX: u16 = 1024; 18 | pub const BLOCK_DEFAULT: u8 = 7; 19 | pub const BLOCK_MAX: u8 = 30; 20 | 21 | const MAGIC: u32 = 'G' as u32 | (('F' as u32) << 8) | (('W' as u32) << 16) | (('X' as u32) << 24); 22 | 23 | #[derive(Clone, Copy, Debug, PartialEq, ToPrimitive, FromPrimitive)] 24 | pub enum Filter { 25 | Linear = 0, 26 | Cubic = 1, 27 | } 28 | 29 | #[derive(Clone, Copy, Debug, PartialEq, ToPrimitive, FromPrimitive)] 30 | pub enum Quantization { 31 | Scalar = 0, 32 | } 33 | 34 | #[derive(Clone, Copy, Debug, PartialEq, ToPrimitive, FromPrimitive)] 35 | pub enum Encoder { 36 | Turbo = 0, 37 | Fast = 1, 38 | Contextual = 2, 39 | } 40 | 41 | #[derive(Clone, Copy, Debug, PartialEq, ToPrimitive, FromPrimitive)] 42 | pub enum Intent { 43 | Generic = 0, 44 | RGB = 7, 45 | RGBA = 8, 46 | BGR = 10, 47 | BGRA = 11, 48 | YUV444 = 12, 49 | } 50 | 51 | #[derive(Debug, PartialEq)] 52 | pub struct Header { 53 | pub version: u32, 54 | pub width: u32, 55 | pub height: u32, 56 | pub layers: u16, 57 | pub channels: u16, 58 | pub bit_depth: u8, 59 | pub is_signed: bool, 60 | pub quality: u16, 61 | pub chroma_scale: u8, 62 | pub block_size: u8, 63 | pub filter: Filter, 64 | pub quantization: Quantization, 65 | pub encoder: Encoder, 66 | pub intent: Intent, 67 | pub metadata_size: u32, 68 | pub channel_size: usize, 69 | pub image_size: usize, 70 | } 71 | 72 | impl Header { 73 | pub fn decode(encoded: &mut impl io::Read) -> Result { 74 | if encoded.read_u32::()? != MAGIC { 75 | return Err(HeaderErr::WrongMagic); 76 | } 77 | 78 | let version = encoded.read_u32::()?; 79 | let width = encoded.read_u32::()?; 80 | let height = encoded.read_u32::()?; 81 | let channels = encoded 82 | .read_u16::()? 83 | .checked_add(1) 84 | .ok_or_else(|| HeaderErr::WrongValue(String::from("Wrong channels value")))?; 85 | let layers = encoded 86 | .read_u16::()? 87 | .checked_add(1) 88 | .ok_or_else(|| HeaderErr::WrongValue(String::from("Wrong layers value")))?; 89 | 90 | let tmp = encoded.read_u24::()?; 91 | let block_size = ((tmp & 0b11111) as u8) 92 | .checked_add(2) 93 | .ok_or_else(|| HeaderErr::WrongValue(String::from("Wrong block_size value")))?; 94 | let chroma_scale = (((tmp >> 5) & 0xff) as u8) 95 | .checked_add(1) 96 | .ok_or_else(|| HeaderErr::WrongValue(String::from("Wrong chroma_scale value")))?; 97 | let quality = (((tmp >> 13) & 0b11_1111_1111) as u16) 98 | .checked_add(1) 99 | .ok_or_else(|| HeaderErr::WrongValue(String::from("Wrong quality value")))?; 100 | let is_signed = (tmp >> 23) == 1; 101 | 102 | let bit_depth = encoded 103 | .read_u8()? 104 | .checked_add(1) 105 | .ok_or_else(|| HeaderErr::WrongValue(String::from("Wrong bit_depth value")))?; 106 | let intent = Intent::from_u8(encoded.read_u8()?) 107 | .ok_or_else(|| HeaderErr::WrongValue(String::from("Wrong intent value")))?; 108 | let encoder = Encoder::from_u8(encoded.read_u8()?) 109 | .ok_or_else(|| HeaderErr::WrongValue(String::from("Wrong encoder value")))?; 110 | let quantization = Quantization::from_u8(encoded.read_u8()?) 111 | .ok_or_else(|| HeaderErr::WrongValue(String::from("Wrong quantization value")))?; 112 | let filter = Filter::from_u8(encoded.read_u8()?) 113 | .ok_or_else(|| HeaderErr::WrongValue(String::from("Wrong filter value")))?; 114 | let metadata_size = encoded 115 | .read_u32::()? 116 | .checked_mul(4) 117 | .ok_or_else(|| HeaderErr::WrongValue(String::from("Wrong metadata_size value")))?; 118 | 119 | if is_signed || bit_depth > 8 { 120 | return Err(HeaderErr::WrongValue(String::from( 121 | "Unsupported bit_depth and is_signed values", 122 | ))); 123 | } 124 | 125 | let builder = HeaderBuilder { 126 | width, 127 | height, 128 | layers, 129 | channels, 130 | quality, 131 | chroma_scale, 132 | block_size, 133 | filter, 134 | encoder, 135 | intent, 136 | metadata_size, 137 | }; 138 | let mut header = builder.build()?; 139 | header.version = version; 140 | header.bit_depth = bit_depth; 141 | header.is_signed = is_signed; 142 | header.quantization = quantization; 143 | 144 | Ok(header) 145 | } 146 | 147 | pub fn encode(&self, buff: &mut impl io::Write) -> io::Result<()> { 148 | buff.write_u32::(MAGIC)?; 149 | buff.write_u32::(self.version)?; 150 | buff.write_u32::(self.width)?; 151 | buff.write_u32::(self.height)?; 152 | buff.write_u16::(self.channels - 1)?; 153 | buff.write_u16::(self.layers - 1)?; 154 | let tmp = u32::from(self.block_size - 2) 155 | | (u32::from(self.chroma_scale - 1) << 5) 156 | | (u32::from(self.quality - 1) << 13) 157 | | ((if self.is_signed { 1 } else { 0 }) << 23); 158 | buff.write_u24::(tmp)?; 159 | buff.write_u8(self.bit_depth - 1)?; 160 | buff.write_u8(self.intent.to_u8().unwrap())?; 161 | buff.write_u8(self.encoder.to_u8().unwrap())?; 162 | buff.write_u8(self.quantization.to_u8().unwrap())?; 163 | buff.write_u8(self.filter.to_u8().unwrap())?; 164 | buff.write_u32::(self.metadata_size / 4)?; 165 | 166 | Ok(()) 167 | } 168 | 169 | pub fn get_decompress_buffer_size(&self, downsampling: usize) -> usize { 170 | self.get_downsampled_image_size(downsampling) * ((self.bit_depth + 7) / 8) as usize 171 | } 172 | 173 | pub fn get_boost(&self) -> i16 { 174 | if self.quality == QUALITY_MAX { 175 | 1 176 | } else { 177 | 8 178 | } 179 | } 180 | 181 | pub fn get_downsampled_width(&self, downsampling: usize) -> usize { 182 | (self.width as usize + (1 << downsampling) - 1) >> downsampling 183 | } 184 | 185 | pub fn get_downsampled_height(&self, downsampling: usize) -> usize { 186 | (self.height as usize + (1 << downsampling) - 1) >> downsampling 187 | } 188 | 189 | pub fn get_downsampled_channel_size(&self, downsampling: usize) -> usize { 190 | self.get_downsampled_width(downsampling) * self.get_downsampled_height(downsampling) 191 | } 192 | 193 | pub fn get_channel_size(&self) -> usize { 194 | self.channel_size 195 | } 196 | 197 | pub fn get_chroma_quality(&self) -> i32 { 198 | 1.max( 199 | (i32::from(self.quality) + i32::from(self.chroma_scale) / 2) 200 | / i32::from(self.chroma_scale), 201 | ) 202 | } 203 | 204 | pub fn get_image_size(&self) -> usize { 205 | self.image_size 206 | } 207 | 208 | pub fn get_downsampled_image_size(&self, downsampling: usize) -> usize { 209 | self.get_downsampled_channel_size(downsampling) 210 | * self.layers as usize 211 | * self.channels as usize 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/header/test.rs: -------------------------------------------------------------------------------- 1 | use super::{builder::HeaderBuilder, *}; 2 | 3 | #[test] 4 | fn test_header_encoding() { 5 | let width = 1920; 6 | let height = 1080; 7 | let layers = 1; 8 | let channels = 4; 9 | let header = Header { 10 | version: 1, 11 | width, 12 | height, 13 | layers, 14 | channels, 15 | bit_depth: 8, 16 | is_signed: false, 17 | quality: QUALITY_MAX, 18 | chroma_scale: 1, 19 | block_size: BLOCK_DEFAULT, 20 | filter: Filter::Linear, 21 | quantization: Quantization::Scalar, 22 | encoder: Encoder::Contextual, 23 | intent: Intent::RGBA, 24 | metadata_size: 0, 25 | channel_size: width as usize * height as usize, 26 | image_size: width as usize * height as usize * layers as usize * channels as usize, 27 | }; 28 | 29 | let expected = vec![ 30 | 71, 70, 87, 88, 1, 0, 0, 0, 128, 7, 0, 0, 56, 4, 0, 0, 3, 0, 0, 0, 5, 224, 127, 7, 8, 2, 0, 31 | 0, 0, 0, 0, 0, 32 | ]; 33 | let mut encoded = vec![]; 34 | header.encode(&mut encoded).unwrap(); 35 | assert_eq!(encoded, expected); 36 | } 37 | 38 | #[test] 39 | fn test_header_decoding() { 40 | let width = 1920; 41 | let height = 1080; 42 | let layers = 1; 43 | let channels = 4; 44 | let expected_header = Header { 45 | version: 1, 46 | width, 47 | height, 48 | layers, 49 | channels, 50 | bit_depth: 8, 51 | is_signed: false, 52 | quality: QUALITY_MAX, 53 | chroma_scale: 1, 54 | block_size: BLOCK_DEFAULT, 55 | filter: Filter::Linear, 56 | quantization: Quantization::Scalar, 57 | encoder: Encoder::Contextual, 58 | intent: Intent::RGBA, 59 | metadata_size: 0, 60 | channel_size: width as usize * height as usize, 61 | image_size: width as usize * height as usize * layers as usize * channels as usize, 62 | }; 63 | 64 | let buff = vec![ 65 | 71, 70, 87, 88, 1, 0, 0, 0, 128, 7, 0, 0, 56, 4, 0, 0, 3, 0, 0, 0, 5, 224, 127, 7, 8, 2, 0, 66 | 0, 0, 0, 0, 0, 67 | ]; 68 | let mut encoded = io::Cursor::new(buff); 69 | let header = Header::decode(&mut encoded).unwrap(); 70 | assert_eq!(header, expected_header); 71 | } 72 | 73 | #[test] 74 | fn test_encoding_decoding() { 75 | let width = 1920; 76 | let height = 1080; 77 | let layers = 1; 78 | let channels = 4; 79 | let header = Header { 80 | version: 1, 81 | width, 82 | height, 83 | layers, 84 | channels, 85 | bit_depth: 8, 86 | is_signed: false, 87 | quality: QUALITY_MAX, 88 | chroma_scale: 1, 89 | block_size: BLOCK_DEFAULT, 90 | filter: Filter::Linear, 91 | quantization: Quantization::Scalar, 92 | encoder: Encoder::Contextual, 93 | intent: Intent::RGBA, 94 | metadata_size: 0, 95 | channel_size: width as usize * height as usize, 96 | image_size: width as usize * height as usize * layers as usize * channels as usize, 97 | }; 98 | 99 | let mut encoded = vec![]; 100 | header.encode(&mut encoded).unwrap(); 101 | let mut cursor = io::Cursor::new(encoded); 102 | let decoded = Header::decode(&mut cursor).unwrap(); 103 | assert_eq!(header, decoded); 104 | } 105 | 106 | #[test] 107 | fn test_wrong_magic() { 108 | let mut encoded = io::Cursor::new(vec![0u8; 32]); 109 | match Header::decode(&mut encoded) { 110 | Err(HeaderErr::WrongMagic) => (), 111 | _ => panic!("wrong result of decoding with invalid magic"), 112 | } 113 | } 114 | 115 | #[test] 116 | fn test_builder_small_width() { 117 | let small_width = 0; 118 | let builder = HeaderBuilder { 119 | width: small_width, 120 | height: 1080, 121 | layers: 1, 122 | channels: 4, 123 | quality: QUALITY_MAX, 124 | chroma_scale: 1, 125 | block_size: BLOCK_DEFAULT, 126 | filter: Filter::Linear, 127 | encoder: Encoder::Contextual, 128 | intent: Intent::RGBA, 129 | metadata_size: 0, 130 | }; 131 | match builder.build() { 132 | Err(HeaderErr::WrongValue(_)) => (), 133 | _ => panic!( 134 | "HeaderBuilder must return Err for the small width: {}", 135 | small_width 136 | ), 137 | } 138 | } 139 | 140 | #[test] 141 | fn test_builder_large_width() { 142 | let large_width = 1 << 30; 143 | let builder = HeaderBuilder { 144 | width: large_width, 145 | height: 1080, 146 | layers: 1, 147 | channels: 4, 148 | quality: QUALITY_MAX, 149 | chroma_scale: 1, 150 | block_size: BLOCK_DEFAULT, 151 | filter: Filter::Linear, 152 | encoder: Encoder::Contextual, 153 | intent: Intent::RGBA, 154 | metadata_size: 0, 155 | }; 156 | match builder.build() { 157 | Err(HeaderErr::WrongValue(_)) => (), 158 | _ => panic!( 159 | "HeaderBuilder must return Err for the large width: {}", 160 | large_width 161 | ), 162 | } 163 | } 164 | 165 | #[test] 166 | fn test_builder_small_height() { 167 | let small_height = 0; 168 | let builder = HeaderBuilder { 169 | width: 1920, 170 | height: small_height, 171 | layers: 1, 172 | channels: 4, 173 | quality: QUALITY_MAX, 174 | chroma_scale: 1, 175 | block_size: BLOCK_DEFAULT, 176 | filter: Filter::Linear, 177 | encoder: Encoder::Contextual, 178 | intent: Intent::RGBA, 179 | metadata_size: 0, 180 | }; 181 | match builder.build() { 182 | Err(HeaderErr::WrongValue(_)) => (), 183 | _ => panic!( 184 | "HeaderBuilder must return Err for the small height: {}", 185 | small_height 186 | ), 187 | } 188 | } 189 | 190 | #[test] 191 | fn test_builder_large_height() { 192 | let large_height = 1 << 30; 193 | let builder = HeaderBuilder { 194 | width: 1920, 195 | height: large_height, 196 | layers: 1, 197 | channels: 4, 198 | quality: QUALITY_MAX, 199 | chroma_scale: 1, 200 | block_size: BLOCK_DEFAULT, 201 | filter: Filter::Linear, 202 | encoder: Encoder::Contextual, 203 | intent: Intent::RGBA, 204 | metadata_size: 0, 205 | }; 206 | match builder.build() { 207 | Err(HeaderErr::WrongValue(_)) => (), 208 | _ => panic!( 209 | "HeaderBuilder must return Err for the large height: {}", 210 | large_height 211 | ), 212 | } 213 | } 214 | 215 | #[test] 216 | fn test_builder_small_quality() { 217 | let small_quality = 0; 218 | let builder = HeaderBuilder { 219 | height: 1920, 220 | width: 1080, 221 | layers: 1, 222 | channels: 4, 223 | quality: small_quality, 224 | chroma_scale: 1, 225 | block_size: BLOCK_DEFAULT, 226 | filter: Filter::Linear, 227 | encoder: Encoder::Contextual, 228 | intent: Intent::RGBA, 229 | metadata_size: 0, 230 | }; 231 | match builder.build() { 232 | Err(HeaderErr::WrongValue(_)) => (), 233 | _ => panic!( 234 | "HeaderBuilder must return Err for the small quality: {}", 235 | small_quality 236 | ), 237 | } 238 | } 239 | 240 | #[test] 241 | fn test_builder_large_quality() { 242 | let large_quality = QUALITY_MAX + 1; 243 | let builder = HeaderBuilder { 244 | height: 1920, 245 | width: 1080, 246 | layers: 1, 247 | channels: 4, 248 | quality: large_quality, 249 | chroma_scale: 1, 250 | block_size: BLOCK_DEFAULT, 251 | filter: Filter::Linear, 252 | encoder: Encoder::Contextual, 253 | intent: Intent::RGBA, 254 | metadata_size: 0, 255 | }; 256 | match builder.build() { 257 | Err(HeaderErr::WrongValue(_)) => (), 258 | _ => panic!( 259 | "HeaderBuilder must return Err for the large quality: {}", 260 | large_quality 261 | ), 262 | } 263 | } 264 | 265 | #[test] 266 | fn test_builder_small_block_size() { 267 | let small_block_size = 0; 268 | let builder = HeaderBuilder { 269 | height: 1920, 270 | width: 1080, 271 | layers: 1, 272 | channels: 4, 273 | quality: QUALITY_MAX, 274 | chroma_scale: 1, 275 | block_size: small_block_size, 276 | filter: Filter::Linear, 277 | encoder: Encoder::Contextual, 278 | intent: Intent::RGBA, 279 | metadata_size: 0, 280 | }; 281 | match builder.build() { 282 | Err(HeaderErr::WrongValue(_)) => (), 283 | _ => panic!( 284 | "HeaderBuilder must return Err for the small block size: {}", 285 | small_block_size 286 | ), 287 | } 288 | } 289 | 290 | #[test] 291 | fn test_builder_large_block_size() { 292 | let large_block_size = 31; 293 | let builder = HeaderBuilder { 294 | height: 1920, 295 | width: 1080, 296 | layers: 1, 297 | channels: 4, 298 | quality: QUALITY_MAX, 299 | chroma_scale: 1, 300 | block_size: large_block_size, 301 | filter: Filter::Linear, 302 | encoder: Encoder::Contextual, 303 | intent: Intent::RGBA, 304 | metadata_size: 0, 305 | }; 306 | match builder.build() { 307 | Err(HeaderErr::WrongValue(_)) => (), 308 | _ => panic!( 309 | "HeaderBuilder must return Err for the large block size: {}", 310 | large_block_size 311 | ), 312 | } 313 | } 314 | 315 | #[test] 316 | fn test_builder_large_layer_size() { 317 | let builder = HeaderBuilder { 318 | height: 1 << 30 - 1, 319 | width: 1 << 30 - 1, 320 | layers: 1 << 4, 321 | channels: 4, 322 | quality: QUALITY_MAX, 323 | chroma_scale: 1, 324 | block_size: BLOCK_DEFAULT, 325 | filter: Filter::Linear, 326 | encoder: Encoder::Contextual, 327 | intent: Intent::RGBA, 328 | metadata_size: 0, 329 | }; 330 | match builder.build() { 331 | Err(HeaderErr::WrongValue(_)) => (), 332 | _ => panic!("HeaderBuilder must return Err for the large layer size",), 333 | } 334 | } 335 | 336 | #[test] 337 | fn test_builder_large_image_size() { 338 | let builder = HeaderBuilder { 339 | height: 1 << 30 - 1, 340 | width: 1 << 30 - 1, 341 | layers: 1 << 2, 342 | channels: 1 << 4, 343 | quality: QUALITY_MAX, 344 | chroma_scale: 1, 345 | block_size: BLOCK_DEFAULT, 346 | filter: Filter::Linear, 347 | encoder: Encoder::Contextual, 348 | intent: Intent::RGBA, 349 | metadata_size: 0, 350 | }; 351 | match builder.build() { 352 | Err(HeaderErr::WrongValue(_)) => (), 353 | _ => panic!("HeaderBuilder must return Err for the large image size",), 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/color_transform/test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn test_color_transform_program_builder() { 5 | let mut color_transform_program = ColorTransformProgram::new(); 6 | 7 | color_transform_program 8 | .add_channel_transform( 9 | ChannelTransformBuilder::with_dest_channel(0) 10 | .add_channel_factor(1, -1) 11 | .set_chroma() 12 | .build(), 13 | ) 14 | .add_channel_transform( 15 | ChannelTransformBuilder::with_dest_channel(2) 16 | .add_channel_factor(1, -1) 17 | .set_chroma() 18 | .build(), 19 | ) 20 | .add_channel_transform( 21 | ChannelTransformBuilder::with_dest_channel(1) 22 | .add_channel_factor(0, 1) 23 | .add_channel_factor(2, 1) 24 | .set_denominator(4) 25 | .build(), 26 | ) 27 | .add_channel_transform( 28 | ChannelTransformBuilder::with_dest_channel(3) 29 | .set_chroma() 30 | .build(), 31 | ) 32 | .add_channel_transform( 33 | ChannelTransformBuilder::with_dest_channel(4) 34 | .set_denominator(2) 35 | .build(), 36 | ); 37 | 38 | assert_eq!(true, color_transform_program.is_channel_has_transform(0)); 39 | assert_eq!(true, color_transform_program.is_channel_has_transform(1)); 40 | assert_eq!(true, color_transform_program.is_channel_has_transform(2)); 41 | assert_eq!(false, color_transform_program.is_channel_has_transform(3)); 42 | assert_eq!(true, color_transform_program.is_channel_has_transform(4)); 43 | 44 | let mut channel_transform_iter = color_transform_program.iter(); 45 | 46 | let channel_transform = channel_transform_iter.next().unwrap(); 47 | assert_eq!(0, channel_transform.dest_channel); 48 | assert_eq!(true, channel_transform.is_chroma); 49 | assert_eq!(1, channel_transform.denominator); 50 | let mut channel_factors_iter = channel_transform.channel_factors.iter(); 51 | let channel_factor = channel_factors_iter.next().unwrap(); 52 | assert_eq!(1, channel_factor.src_channel); 53 | assert_eq!(-1, channel_factor.factor); 54 | assert!(channel_factors_iter.next().is_none()); 55 | 56 | let channel_transform = channel_transform_iter.next().unwrap(); 57 | assert_eq!(2, channel_transform.dest_channel); 58 | assert_eq!(true, channel_transform.is_chroma); 59 | assert_eq!(1, channel_transform.denominator); 60 | let mut channel_factors_iter = channel_transform.channel_factors.iter(); 61 | let channel_factor = channel_factors_iter.next().unwrap(); 62 | assert_eq!(1, channel_factor.src_channel); 63 | assert_eq!(-1, channel_factor.factor); 64 | assert!(channel_factors_iter.next().is_none()); 65 | 66 | let channel_transform = channel_transform_iter.next().unwrap(); 67 | assert_eq!(1, channel_transform.dest_channel); 68 | assert_eq!(false, channel_transform.is_chroma); 69 | assert_eq!(4, channel_transform.denominator); 70 | let mut channel_factors_iter = channel_transform.channel_factors.iter(); 71 | let channel_factor = channel_factors_iter.next().unwrap(); 72 | assert_eq!(0, channel_factor.src_channel); 73 | assert_eq!(1, channel_factor.factor); 74 | let channel_factor = channel_factors_iter.next().unwrap(); 75 | assert_eq!(2, channel_factor.src_channel); 76 | assert_eq!(1, channel_factor.factor); 77 | assert!(channel_factors_iter.next().is_none()); 78 | 79 | let channel_transform = channel_transform_iter.next().unwrap(); 80 | assert_eq!(3, channel_transform.dest_channel); 81 | assert_eq!(true, channel_transform.is_chroma); 82 | assert_eq!(1, channel_transform.denominator); 83 | let mut channel_factors_iter = channel_transform.channel_factors.iter(); 84 | assert!(channel_factors_iter.next().is_none()); 85 | 86 | let channel_transform = channel_transform_iter.next().unwrap(); 87 | assert_eq!(4, channel_transform.dest_channel); 88 | assert_eq!(false, channel_transform.is_chroma); 89 | assert_eq!(2, channel_transform.denominator); 90 | let mut channel_factors_iter = channel_transform.channel_factors.iter(); 91 | assert!(channel_factors_iter.next().is_none()); 92 | } 93 | 94 | #[test] 95 | fn test_color_transform_passtrough() { 96 | let width = 3; 97 | let height = 2; 98 | let layers = 1; 99 | let channels = 3; 100 | let header = header::Header { 101 | version: 1, 102 | width, 103 | height, 104 | layers, 105 | channels, 106 | bit_depth: 8, 107 | is_signed: false, 108 | quality: 512, 109 | chroma_scale: 8, 110 | block_size: header::BLOCK_DEFAULT, 111 | filter: header::Filter::Cubic, 112 | quantization: header::Quantization::Scalar, 113 | encoder: header::Encoder::Turbo, 114 | intent: header::Intent::RGB, 115 | metadata_size: 0, 116 | channel_size: width as usize * height as usize, 117 | image_size: width as usize * height as usize * layers as usize * channels as usize, 118 | }; 119 | 120 | let input: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8]; 121 | 122 | let expected = input.iter().map(|v| (*v as i16) * 8).collect::>(); 123 | 124 | let mut color_transform_program = ColorTransformProgram::new(); 125 | color_transform_program 126 | .add_channel_transform(ChannelTransformBuilder::with_dest_channel(0).build()) 127 | .add_channel_transform(ChannelTransformBuilder::with_dest_channel(2).build()) 128 | .add_channel_transform(ChannelTransformBuilder::with_dest_channel(1).build()); 129 | 130 | let mut actual = vec![0_i16; 3 * 2 * 3]; 131 | 132 | color_transform_program.transform(&input, &header, &mut actual); 133 | 134 | assert_eq!(expected, actual); 135 | } 136 | 137 | #[test] 138 | fn test_color_transform_yuv() { 139 | let width = 3; 140 | let height = 2; 141 | let layers = 1; 142 | let channels = 3; 143 | let header = header::Header { 144 | version: 1, 145 | width, 146 | height, 147 | layers, 148 | channels, 149 | bit_depth: 8, 150 | is_signed: false, 151 | quality: 512, 152 | chroma_scale: 8, 153 | block_size: header::BLOCK_DEFAULT, 154 | filter: header::Filter::Cubic, 155 | quantization: header::Quantization::Scalar, 156 | encoder: header::Encoder::Turbo, 157 | intent: header::Intent::RGB, 158 | metadata_size: 0, 159 | channel_size: width as usize * height as usize, 160 | image_size: width as usize * height as usize * layers as usize * channels as usize, 161 | }; 162 | 163 | let input: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8]; 164 | 165 | let expected: Vec = vec![ 166 | -48, -48, -48, 32, 32, 32, 36, 44, 52, 20, 28, 36, -32, -32, -32, 48, 48, 48, 167 | ]; 168 | 169 | let mut color_transform_program = ColorTransformProgram::new(); 170 | color_transform_program 171 | .add_channel_transform( 172 | ChannelTransformBuilder::with_dest_channel(0) 173 | .add_channel_factor(1, -1) 174 | .set_chroma() 175 | .build(), 176 | ) 177 | .add_channel_transform( 178 | ChannelTransformBuilder::with_dest_channel(2) 179 | .add_channel_factor(1, -1) 180 | .set_chroma() 181 | .build(), 182 | ) 183 | .add_channel_transform( 184 | ChannelTransformBuilder::with_dest_channel(1) 185 | .add_channel_factor(0, 1) 186 | .add_channel_factor(2, 1) 187 | .set_denominator(4) 188 | .build(), 189 | ); 190 | 191 | let mut actual = vec![0_i16; 3 * 2 * 3]; 192 | 193 | color_transform_program.transform(&input, &header, &mut actual); 194 | 195 | assert_eq!(expected, actual); 196 | } 197 | 198 | #[test] 199 | fn test_color_transform_encode() { 200 | let expected = vec![183, 119, 85, 151, 246, 114, 119, 85, 0, 128, 50, 233]; 201 | 202 | let mut color_transform_program = ColorTransformProgram::new(); 203 | color_transform_program 204 | .add_channel_transform( 205 | ChannelTransformBuilder::with_dest_channel(0) 206 | .add_channel_factor(1, -1) 207 | .set_chroma() 208 | .build(), 209 | ) 210 | .add_channel_transform( 211 | ChannelTransformBuilder::with_dest_channel(2) 212 | .add_channel_factor(1, -1) 213 | .set_chroma() 214 | .build(), 215 | ) 216 | .add_channel_transform( 217 | ChannelTransformBuilder::with_dest_channel(1) 218 | .add_channel_factor(0, 1) 219 | .add_channel_factor(2, 1) 220 | .set_denominator(4) 221 | .build(), 222 | ); 223 | let mut buffer = vec![0u8; expected.len()]; 224 | 225 | let is_chroma = { 226 | let mut slice = buffer.as_mut_slice(); 227 | color_transform_program.encode(3, &mut slice).unwrap() 228 | }; 229 | 230 | assert_eq!(buffer, expected); 231 | assert_eq!(is_chroma, vec![true, false, true]); 232 | } 233 | 234 | #[test] 235 | fn test_color_transform_decode() { 236 | let buffer = vec![183, 119, 85, 151, 246, 114, 119, 85, 0, 128, 50, 233]; 237 | 238 | let expected_color_transform_program = { 239 | let mut color_transform_program = ColorTransformProgram::new(); 240 | color_transform_program 241 | .add_channel_transform( 242 | ChannelTransformBuilder::with_dest_channel(0) 243 | .add_channel_factor(1, -1) 244 | .set_chroma() 245 | .build(), 246 | ) 247 | .add_channel_transform( 248 | ChannelTransformBuilder::with_dest_channel(2) 249 | .add_channel_factor(1, -1) 250 | .set_chroma() 251 | .build(), 252 | ) 253 | .add_channel_transform( 254 | ChannelTransformBuilder::with_dest_channel(1) 255 | .add_channel_factor(0, 1) 256 | .add_channel_factor(2, 1) 257 | .set_denominator(4) 258 | .build(), 259 | ); 260 | color_transform_program 261 | }; 262 | 263 | let mut slice = buffer.as_slice(); 264 | let mut is_chroma = vec![false; 3]; 265 | let color_transform_program = 266 | ColorTransformProgram::decode(&mut slice, &mut is_chroma).unwrap(); 267 | 268 | assert_eq!(color_transform_program, expected_color_transform_program); 269 | assert_eq!(is_chroma, vec![true, false, true]); 270 | } 271 | 272 | #[test] 273 | fn test_interleaved_to_planar() { 274 | let boost = 2; 275 | let channels = 3; 276 | let input: Vec = vec![0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5]; 277 | let expected = vec![0, 2, 4, 6, 2, 4, 6, 8, 4, 6, 8, 10]; 278 | 279 | let mut actual = vec![0; expected.len()]; 280 | interleaved_to_planar(&input, channels, boost, &mut actual, &[]); 281 | assert_eq!(actual, expected); 282 | } 283 | 284 | #[test] 285 | fn test_interleaved_to_planar_with_skip() { 286 | let boost = 2; 287 | let channels = 3; 288 | let input: Vec = vec![0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5]; 289 | let expected = vec![0, 2, 4, 6, 4, 6, 8, 10]; 290 | 291 | let mut actual = vec![0; expected.len()]; 292 | interleaved_to_planar(&input, channels, boost, &mut actual, &[1]); 293 | assert_eq!(actual, expected); 294 | } 295 | 296 | #[test] 297 | fn test_planar_to_interleaved() { 298 | let boost = 2; 299 | let channels = 3; 300 | let input: Vec = vec![0, 2, 4, 6, 2, 4, 6, 8, 4, 6, 8, 10]; 301 | let expected: Vec = vec![0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5]; 302 | 303 | let mut actual = vec![0; expected.len()]; 304 | planar_to_interleaved(&input, channels, boost, &mut actual, &[]); 305 | assert_eq!(actual, expected); 306 | } 307 | 308 | #[test] 309 | fn test_planar_to_interleaved_with_skip() { 310 | let boost = 2; 311 | let channels = 3; 312 | let input: Vec = vec![0, 2, 4, 6, 4, 6, 8, 10]; 313 | let expected = vec![0, 255, 2, 1, 255, 3, 2, 255, 4, 3, 255, 5]; 314 | 315 | let mut actual = vec![255u8; expected.len()]; 316 | planar_to_interleaved(&input, channels, boost, &mut actual, &[1]); 317 | assert_eq!(actual, expected); 318 | } 319 | -------------------------------------------------------------------------------- /src/compress/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{self, mem, u8}; 2 | 3 | use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; 4 | 5 | use crate::bits::{BitsIOReader, BitsIOWriter, BitsWriter}; 6 | use crate::config::Config; 7 | use crate::encode::{decode, encode}; 8 | use crate::errors::{CompressError, DecompressError}; 9 | use crate::header; 10 | use crate::lifting; 11 | use crate::processing::{ 12 | image::Image, process_maybe_parallel_for_each, process_maybe_parallel_map_collect, 13 | VariableChunksIterator, 14 | }; 15 | use crate::quant; 16 | 17 | #[cfg(test)] 18 | mod test; 19 | 20 | pub fn compress_aux_data( 21 | mut aux_data: &mut [i16], 22 | header: &header::Header, 23 | is_chroma: &[bool], 24 | mut buffer: &mut [u8], 25 | ) -> Result { 26 | lift_and_quantize(&mut aux_data, &header, &is_chroma); 27 | compress_image_data(&mut aux_data, &header, &mut buffer, &is_chroma) 28 | } 29 | 30 | pub fn decompress_aux_data( 31 | data: &[u8], 32 | header: &header::Header, 33 | is_chroma: &[bool], 34 | downsampling: usize, 35 | test: bool, 36 | mut aux_data: &mut [i16], 37 | ) -> Result { 38 | let payload_next_point_of_interest = 39 | decompress_image_data(&mut aux_data, &header, data, downsampling, test, &is_chroma)?; 40 | 41 | if !test { 42 | unlift_and_dequantize(&mut aux_data, &header, &is_chroma, downsampling); 43 | } 44 | 45 | Ok(payload_next_point_of_interest) 46 | } 47 | 48 | fn aux_data_to_2d_channel<'a>( 49 | aux_data: &'a mut [i16], 50 | header: &'a header::Header, 51 | is_chroma: &'a [bool], 52 | downsampling: usize, 53 | ) -> impl Iterator, &'a bool)> { 54 | let channel_size = header.get_downsampled_channel_size(downsampling); 55 | 56 | aux_data 57 | .chunks_mut(channel_size) 58 | .map(move |chunk| { 59 | chunk 60 | .chunks_mut(header.get_downsampled_width(downsampling)) 61 | .collect::>() 62 | }) 63 | .zip(is_chroma.iter()) 64 | } 65 | 66 | fn lift_and_quantize(aux_data: &mut [i16], header: &header::Header, is_chroma: &[bool]) { 67 | let chroma_quality = header.get_chroma_quality(); 68 | let boost = header.get_boost(); 69 | 70 | process_maybe_parallel_for_each( 71 | aux_data_to_2d_channel(aux_data, header, is_chroma, 0), 72 | |(ref mut image, &is_chroma)| { 73 | match header.filter { 74 | header::Filter::Linear => lifting::lift_linear(image), 75 | header::Filter::Cubic => lifting::lift_cubic(image), 76 | }; 77 | 78 | let quality = if is_chroma { 79 | chroma_quality 80 | } else { 81 | i32::from(header.quality) 82 | }; 83 | 84 | quant::quantize( 85 | image, 86 | quality, 87 | 0, 88 | i32::from(header::QUALITY_MAX) * i32::from(boost), 89 | ); 90 | }, 91 | true, 92 | ); 93 | } 94 | 95 | fn unlift_and_dequantize( 96 | aux_data: &mut [i16], 97 | header: &header::Header, 98 | is_chroma: &[bool], 99 | downsampling: usize, 100 | ) { 101 | let chroma_quality = header.get_chroma_quality(); 102 | let boost = header.get_boost(); 103 | 104 | process_maybe_parallel_for_each( 105 | aux_data_to_2d_channel(aux_data, header, is_chroma, downsampling), 106 | |(ref mut image, &is_chroma)| { 107 | let quality = if is_chroma { 108 | chroma_quality 109 | } else { 110 | i32::from(header.quality) 111 | }; 112 | 113 | quant::dequantize( 114 | image, 115 | quality << downsampling, 116 | 0, 117 | i32::from(header::QUALITY_MAX) * i32::from(boost), 118 | ); 119 | 120 | match header.filter { 121 | header::Filter::Linear => lifting::unlift_linear(image), 122 | header::Filter::Cubic => lifting::unlift_cubic(image), 123 | }; 124 | }, 125 | true, 126 | ); 127 | } 128 | 129 | fn compress_image_data( 130 | aux_data: &mut [i16], 131 | header: &header::Header, 132 | buffer: &mut [u8], 133 | is_chroma: &[bool], 134 | ) -> Result { 135 | let chroma_quality = header.get_chroma_quality(); 136 | 137 | let width = header.width as usize; 138 | let height = header.height as usize; 139 | let layers = usize::from(header.layers); 140 | let channels = usize::from(header.channels); 141 | 142 | let mut step = 1; 143 | while step * 2 < width || step * 2 < height { 144 | step *= 2; 145 | } 146 | if (step << header.block_size) == 0 { 147 | return Err(CompressError::Overflow); 148 | } 149 | 150 | let mut has_dc = true; 151 | let mut compressed_size = 0; 152 | let hint_do_parallel = aux_data.len() > Config::multithreading_factors().compress; 153 | 154 | while step >= 1 { 155 | let bs = step << header.block_size; 156 | let block_count_x = (width + bs - 1) / bs; 157 | let block_count_y = (height + bs - 1) / bs; 158 | let block_count = block_count_x * block_count_y * layers * channels; 159 | 160 | let block_sizes_storage_size = block_count * mem::size_of::(); 161 | 162 | // slice out already compressed data from buffer 163 | let (_, buffer_remainder) = buffer.split_at_mut(compressed_size); 164 | 165 | if buffer_remainder.len() < block_sizes_storage_size { 166 | return Err(CompressError::Overflow); 167 | } 168 | 169 | let (block_sizes_buffer, blocks_buffer) = 170 | buffer_remainder.split_at_mut(block_sizes_storage_size); 171 | 172 | compressed_size += block_sizes_buffer.len(); 173 | 174 | // make iterator over temporary blocks 175 | let blocks_buffer_free_space = blocks_buffer.len(); 176 | let temp_block_size = blocks_buffer_free_space / block_count; 177 | 178 | let aux_data_chunks = Image::from_slice(aux_data, (width, height), channels * layers) 179 | .into_chunks_mut(bs, step); 180 | 181 | let block_encode_results = process_maybe_parallel_map_collect( 182 | aux_data_chunks 183 | .zip(blocks_buffer.chunks_mut(temp_block_size)) 184 | .enumerate(), 185 | |(block_index, (aux_data_chunk, mut output_block))| { 186 | let empty_block_size = output_block.len(); 187 | 188 | let channel = aux_data_chunk.channel; 189 | let is_first_block_in_channel = 190 | aux_data_chunk.x_range.0 == 0 && aux_data_chunk.y_range.0 == 0; 191 | 192 | { 193 | let mut output_block_writer = BitsIOWriter::new(&mut output_block); 194 | let quality = if is_chroma[channel] { 195 | chroma_quality 196 | } else { 197 | i32::from(header.quality) 198 | }; 199 | encode( 200 | &aux_data_chunk, 201 | &mut output_block_writer, 202 | header.encoder, 203 | quality, 204 | has_dc && is_first_block_in_channel, 205 | is_chroma[channel], 206 | )?; 207 | 208 | output_block_writer.flush_write_word()?; 209 | } 210 | 211 | // After writes to output_block, it's size (as slice) is reduced to free space 212 | Ok((block_index, (empty_block_size - output_block.len()) as u32)) 213 | }, 214 | hint_do_parallel, 215 | ); 216 | 217 | let mut sorted_block_encode_results = vec![0; block_encode_results.len()]; 218 | for result in block_encode_results { 219 | match result { 220 | Ok((i, v)) => sorted_block_encode_results[i] = v, 221 | Err(e) => return Err(e), 222 | } 223 | } 224 | 225 | // last block tail relative to blocks_buffer slice start 226 | let mut tail_pos = 0; 227 | 228 | for (block_index, (block_size, mut block_size_buffer)) in sorted_block_encode_results 229 | .into_iter() 230 | .zip(block_sizes_buffer.chunks_mut(std::mem::size_of::())) 231 | .enumerate() 232 | { 233 | block_size_buffer.write_u32::(block_size / 4)?; 234 | 235 | let block_size = block_size as usize; 236 | if block_index != 0 { 237 | let block_start = block_index * temp_block_size; 238 | for i in 0..block_size { 239 | blocks_buffer[tail_pos + i] = blocks_buffer[block_start + i]; 240 | } 241 | } 242 | 243 | compressed_size += block_size; 244 | tail_pos += block_size; 245 | } 246 | 247 | has_dc = false; 248 | step /= 2; 249 | } 250 | 251 | Ok(compressed_size) 252 | } 253 | 254 | pub fn decompress_image_data( 255 | aux_data: &mut [i16], 256 | header: &header::Header, 257 | buffer: &[u8], 258 | downsampling: usize, 259 | test: bool, 260 | is_chroma: &[bool], 261 | ) -> Result { 262 | let chroma_quality = header.get_chroma_quality(); 263 | 264 | let width = header.width as usize; 265 | let height = header.height as usize; 266 | let layers = usize::from(header.layers); 267 | let channels = usize::from(header.channels); 268 | 269 | let mut step = 1; 270 | while step * 2 < width || step * 2 < height { 271 | step *= 2; 272 | } 273 | if (step << header.block_size) == 0 { 274 | return Err(DecompressError::Underflow); 275 | } 276 | 277 | // guess next point of interest 278 | let mut next_point_of_interest = buffer.len() + 1024; 279 | let mut is_truncated = false; 280 | let mut has_dc = true; 281 | let mut decompressed_size = 0; 282 | let hint_do_parallel = aux_data.len() > Config::multithreading_factors().compress; 283 | 284 | let downsampled_width = header.get_downsampled_width(downsampling); 285 | let downsampled_height = header.get_downsampled_height(downsampling); 286 | 287 | while (step >> downsampling) >= 1 { 288 | let bs = step << header.block_size; 289 | 290 | let block_count_x = (width + bs - 1) / bs; 291 | let block_count_y = (height + bs - 1) / bs; 292 | let block_count = block_count_x * block_count_y * layers * channels; 293 | 294 | is_truncated = true; 295 | 296 | let block_sizes_storage_size = block_count * std::mem::size_of::(); 297 | 298 | if decompressed_size > buffer.len() { 299 | return Err(DecompressError::Underflow); 300 | } 301 | let (_, buffer_remainder) = buffer.split_at(decompressed_size); 302 | 303 | if buffer_remainder.len() <= block_sizes_storage_size { 304 | break; 305 | } 306 | 307 | let (block_sizes_buffer, blocks_buffer) = 308 | buffer_remainder.split_at(block_sizes_storage_size); 309 | 310 | // get block sizes 311 | let mut blocks_sizes = vec![0_usize; block_count]; 312 | 313 | for (mut chunk_size_buffer, block_size) in block_sizes_buffer 314 | .chunks(std::mem::size_of::()) 315 | .zip(blocks_sizes.iter_mut()) 316 | { 317 | *block_size = chunk_size_buffer.read_u32::().unwrap() as usize * 4; 318 | } 319 | 320 | let blocks_size_sum: usize = blocks_sizes.iter().sum(); 321 | 322 | next_point_of_interest = decompressed_size 323 | + block_sizes_storage_size 324 | + blocks_size_sum 325 | + if step >> downsampling > 1 { 326 | // 4 times more block on next iteration 327 | block_sizes_storage_size * 4 328 | } else { 329 | 0 330 | }; 331 | 332 | if blocks_buffer.len() >= blocks_size_sum { 333 | is_truncated = false; 334 | } 335 | 336 | let step_downsampled = step >> downsampling; 337 | let block_size_downsampled = step_downsampled << header.block_size; 338 | 339 | let aux_data_chunks = Image::from_slice( 340 | aux_data, 341 | (downsampled_width, downsampled_height), 342 | channels * layers, 343 | ) 344 | .into_chunks_mut(block_size_downsampled, step_downsampled); 345 | 346 | let block_decode_results: Vec> = process_maybe_parallel_map_collect( 347 | aux_data_chunks 348 | .zip(VariableChunksIterator::new(&blocks_buffer, &blocks_sizes)) 349 | .enumerate(), 350 | |(block_index, (aux_data_chunk, mut input_block))| { 351 | // truncated chunk does not require any actions. 352 | if test || input_block.len() < blocks_sizes[block_index] { 353 | return Ok(()); 354 | } 355 | 356 | let channel = aux_data_chunk.channel; 357 | let is_first_block_in_channel = 358 | aux_data_chunk.x_range.0 == 0 && aux_data_chunk.y_range.0 == 0; 359 | 360 | { 361 | let mut input_block_reader = BitsIOReader::new(&mut input_block); 362 | let quality = if is_chroma[channel] { 363 | chroma_quality 364 | } else { 365 | i32::from(header.quality) 366 | }; 367 | decode( 368 | aux_data_chunk, 369 | &mut input_block_reader, 370 | header.encoder, 371 | quality, 372 | has_dc && is_first_block_in_channel, 373 | is_chroma[channel], 374 | )?; 375 | } 376 | 377 | Ok(()) 378 | }, 379 | hint_do_parallel, 380 | ); 381 | 382 | for result in block_decode_results { 383 | result.map_err(|_| DecompressError::Underflow)? 384 | } 385 | 386 | has_dc = false; 387 | step /= 2; 388 | decompressed_size += block_sizes_storage_size + blocks_size_sum; 389 | } 390 | 391 | if is_truncated { 392 | Ok(next_point_of_interest) 393 | } else { 394 | Ok(0) 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /src/encode/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use num_traits; 4 | 5 | use crate::bits; 6 | use crate::header::Encoder; 7 | use crate::processing::image::ImageChunkMut; 8 | 9 | #[cfg(test)] 10 | mod test; 11 | 12 | // limited length power-of-two Golomb-Rice code 13 | pub fn unsigned_code(x: u32, stream: &mut impl bits::BitsWriter, pot: u32) -> io::Result<()> { 14 | let y = x >> pot; 15 | if y >= 12 { 16 | stream.put_bits(0, 12)?; // escape to larger code 17 | let new_pot = if pot < 20 { pot + 4 } else { 24 }; 18 | unsigned_code(x - (12 << (pot)), stream, new_pot)?; 19 | } else { 20 | // encode x / 2^pot in unary followed by x % 2^pot in binary 21 | stream.put_bits((1 << (pot)) | (x & !(!0u32 << (pot))), y + 1 + pot)?; 22 | } 23 | 24 | Ok(()) 25 | } 26 | 27 | pub fn unsigned_decode(stream: &mut impl bits::BitsReader, pot: u32) -> io::Result { 28 | let x = stream.get_zeros(12)?; 29 | let p = pot.min(24); // actual pot. The max 108 below is to prevent unlimited recursion in malformed files, yet admit 2^32 - 1. 30 | if pot < 108 && x == 12 { 31 | let new_pot = (pot + 4).min(108); 32 | Ok((12u32 << p).wrapping_add(unsigned_decode(stream, new_pot)?)) 33 | } else if p != 0 { 34 | Ok((x << p).wrapping_add(stream.get_bits(p)?)) 35 | } else { 36 | Ok(x) 37 | } 38 | } 39 | 40 | pub fn interleaved_code(x: i32, stream: &mut impl bits::BitsWriter, pot: u32) -> io::Result<()> { 41 | let x = if x <= 0 { -2 * x } else { 2 * x - 1 } as u32; 42 | unsigned_code(x, stream, pot)?; 43 | 44 | Ok(()) 45 | } 46 | 47 | pub fn interleaved_decode(stream: &mut impl bits::BitsReader, pot: u32) -> io::Result { 48 | let x = unsigned_decode(stream, pot)? as i32; 49 | if (x & 1) != 0 { 50 | Ok((x + 1) / 2) 51 | } else { 52 | Ok(-x / 2) 53 | } 54 | } 55 | 56 | pub fn signed_code(x: i32, stream: &mut impl bits::BitsWriter, pot: u32) -> io::Result<()> { 57 | unsigned_code(x.abs() as u32, stream, pot)?; 58 | if x != 0 { 59 | stream.put_bits(if x > 0 { 1 } else { 0 }, 1)?; 60 | } 61 | 62 | Ok(()) 63 | } 64 | 65 | pub fn signed_decode(stream: &mut impl bits::BitsReader, pot: u32) -> io::Result { 66 | let x = unsigned_decode(stream, pot)? as i32; 67 | 68 | if x == 0 { 69 | Ok(0) 70 | } else if stream.get_bits(1)? != 0 { 71 | Ok(x) 72 | } else { 73 | Ok(-x) 74 | } 75 | } 76 | 77 | pub fn square(t: T) -> T::Output { 78 | t.wrapping_mul(&t) 79 | } 80 | 81 | pub fn add_context(x: i16, w: i32, sum: &mut u32, sum2: &mut u32, count: &mut u32) { 82 | let x = x.abs() as u32; 83 | *sum += x * w as u32; 84 | *sum2 += square(x.min(4096)) * w as u32; 85 | *count += w as u32; 86 | } 87 | 88 | pub unsafe fn get_context(image: &ImageChunkMut<'_, i16>, x: i32, y: i32) -> (u32, u32) { 89 | let skip = image.step as i32; 90 | let x_range = (image.x_range.0 as i32, image.x_range.1 as i32); 91 | let y_range = (image.y_range.0 as i32, image.y_range.1 as i32); 92 | 93 | let mut px = x_range.0 + (x & !(skip * 2)) + (x & skip); 94 | if px >= x_range.1 { 95 | px -= skip * 2; 96 | } 97 | 98 | let mut py = y_range.0 + (y & !(skip * 2)) + (y & skip); 99 | if py >= y_range.1 { 100 | py -= skip * 2; 101 | } 102 | 103 | let mut count = 0u32; 104 | let mut sum = 0u32; 105 | let mut sum2 = 0u32; 106 | 107 | add_context( 108 | *image.get_unchecked(py as usize, px as usize), 109 | 2, 110 | &mut sum, 111 | &mut sum2, 112 | &mut count, 113 | ); // ancestor 114 | if (y & skip) != 0 && (x | skip) < (x_range.1 - x_range.0) { 115 | add_context( 116 | *image.get_unchecked( 117 | (y_range.0 + y - skip) as usize, 118 | (x_range.0 + (x | skip)) as usize, 119 | ), 120 | 2, 121 | &mut sum, 122 | &mut sum2, 123 | &mut count, 124 | ); // upper sibling 125 | if (x & skip) != 0 { 126 | add_context( 127 | *image.get_unchecked((y_range.0 + y) as usize, (x_range.0 + x - skip) as usize), 128 | 2, 129 | &mut sum, 130 | &mut sum2, 131 | &mut count, 132 | ); // left sibling 133 | } 134 | } 135 | if y >= skip * 2 && x >= skip * 2 { 136 | // neighbors 137 | let points = [ 138 | (y_range.0 + y - skip * 2, x_range.0 + x, 4), 139 | (y_range.0 + y, x_range.0 + x - skip * 2, 4), 140 | (y_range.0 + y - skip * 2, x_range.0 + x - skip * 2, 2), 141 | ]; 142 | for p in &points { 143 | add_context( 144 | *image.get_unchecked(p.0 as usize, p.1 as usize), 145 | p.2, 146 | &mut sum, 147 | &mut sum2, 148 | &mut count, 149 | ); 150 | } 151 | if x + skip * 2 < x_range.1 - x_range.0 { 152 | add_context( 153 | *image.get_unchecked( 154 | (y_range.0 + y - skip * 2) as usize, 155 | (x_range.0 + x + skip * 2) as usize, 156 | ), 157 | 2, 158 | &mut sum, 159 | &mut sum2, 160 | &mut count, 161 | ); 162 | } 163 | if y >= skip * 4 && x >= skip * 4 { 164 | let points = [ 165 | (y_range.0 + y - skip * 4, x_range.0 + x, 2), 166 | (y_range.0 + y, x_range.0 + x - skip * 4, 2), 167 | (y_range.0 + y - skip * 4, x_range.0 + x - skip * 4, 1), 168 | ]; 169 | for p in &points { 170 | add_context( 171 | *image.get_unchecked(p.0 as usize, p.1 as usize), 172 | p.2, 173 | &mut sum, 174 | &mut sum2, 175 | &mut count, 176 | ); 177 | } 178 | if x + skip * 4 < x_range.1 - x_range.0 { 179 | add_context( 180 | *image.get_unchecked( 181 | (y_range.0 + y - skip * 4) as usize, 182 | (x_range.0 + x + skip * 4) as usize, 183 | ), 184 | 1, 185 | &mut sum, 186 | &mut sum2, 187 | &mut count, 188 | ); 189 | } 190 | } 191 | } 192 | 193 | ( 194 | (sum * 16 + count / 2) / count, 195 | (sum2 * 16 + count / 2) / count, 196 | ) 197 | } 198 | 199 | fn encode_s( 200 | stream: &mut impl bits::BitsWriter, 201 | s: i32, 202 | sum_sq: u32, 203 | context: (u32, u32), 204 | is_chroma: bool, 205 | ) -> io::Result<()> { 206 | if sum_sq < 2 * context.1 + (if is_chroma { 250 } else { 100 }) { 207 | interleaved_code(s, stream, 0) 208 | } else if sum_sq < 2 * context.1 + 950 { 209 | interleaved_code(s, stream, 1) 210 | } else if sum_sq < 3 * context.1 + 3000 { 211 | if sum_sq < 5 * context.1 + 400 { 212 | signed_code(s, stream, 1) 213 | } else { 214 | interleaved_code(s, stream, 2) 215 | } 216 | } else if sum_sq < 3 * context.1 + 12000 { 217 | if sum_sq < 5 * context.1 + 3000 { 218 | signed_code(s, stream, 2) 219 | } else { 220 | interleaved_code(s, stream, 3) 221 | } 222 | } else if sum_sq < 4 * context.1 + 44000 { 223 | if sum_sq < 6 * context.1 + 12000 { 224 | signed_code(s, stream, 3) 225 | } else { 226 | interleaved_code(s, stream, 4) 227 | } 228 | } else { 229 | signed_code(s, stream, 4) 230 | } 231 | } 232 | 233 | pub fn encode( 234 | image: &ImageChunkMut<'_, i16>, 235 | stream: &mut impl bits::BitsWriter, 236 | scheme: Encoder, 237 | q: i32, 238 | has_dc: bool, 239 | is_chroma: bool, 240 | ) -> io::Result<()> { 241 | let step = image.step as i32; 242 | let x_range = (image.x_range.0 as i32, image.x_range.1 as i32); 243 | let y_range = (image.y_range.0 as i32, image.y_range.1 as i32); 244 | 245 | let sizex = (image.x_range.1 - image.x_range.0) as i32; 246 | let sizey = (image.y_range.1 - image.y_range.0) as i32; 247 | 248 | if has_dc && (sizex > 0) && (sizey > 0) { 249 | signed_code( 250 | i32::from(unsafe { *image.get_unchecked(y_range.0 as usize, x_range.0 as usize) }), 251 | stream, 252 | 4, 253 | )?; 254 | } 255 | 256 | let mut context = (0, 0); 257 | let mut run = 0; 258 | let mut run_coder = 259 | if scheme == Encoder::Turbo && ((q == 0) || ((step < 2048) && (q * step < 2048))) { 260 | 1 261 | } else { 262 | 0 263 | }; 264 | 265 | for y in (0..sizey).step_by(step as usize) { 266 | let x_step = if (y & step) != 0 { step } else { step * 2 }; 267 | for x in ((x_step - step)..sizex).step_by(x_step as usize) { 268 | // [NOTE] arranged so that (x | y) & step == 1 269 | let mut s = i32::from(unsafe { 270 | *image.get_unchecked((y_range.0 + y) as usize, (x_range.0 + x) as usize) 271 | }); 272 | 273 | if run_coder != 0 && s == 0 { 274 | run += 1; 275 | continue; 276 | } 277 | 278 | if scheme == Encoder::Turbo { 279 | if run_coder != 0 { 280 | unsigned_code(run as u32, stream, 1)?; 281 | run = 0; 282 | // s can't be zero, so shift negatives by 1 283 | interleaved_code(if s < 0 { s + 1 } else { s }, stream, 1)?; 284 | } else { 285 | interleaved_code(s, stream, 1)?; 286 | } 287 | continue; 288 | } 289 | 290 | if run_coder != 0 { 291 | unsigned_code(run as u32, stream, run_coder)?; 292 | 293 | run = 0; 294 | if s < 0 { 295 | s += 1; 296 | } 297 | } 298 | 299 | if scheme == Encoder::Contextual { 300 | context = unsafe { get_context(&image, x, y) }; 301 | } 302 | 303 | let sum_sq = square(context.0); 304 | encode_s(stream, s, sum_sq, context, is_chroma)?; 305 | 306 | if scheme == Encoder::Fast { 307 | let t = s.abs() as u32; 308 | context = ( 309 | ((context.0 * 15 + 7) >> 4) + t, 310 | ((context.1 * 15 + 7) >> 4) + square(t.min(4096)), 311 | ); 312 | run_coder = get_run_coder_fast(context, s, run_coder); 313 | } else { 314 | run_coder = get_run_coder(context, s, q, run_coder, sum_sq); 315 | } 316 | } 317 | } 318 | 319 | if run != 0 { 320 | // flush run 321 | unsigned_code(run as u32, stream, run_coder)?; 322 | } 323 | 324 | Ok(()) 325 | } 326 | 327 | fn get_s( 328 | stream: &mut impl bits::BitsReader, 329 | sum_sq: u32, 330 | context: (u32, u32), 331 | is_chroma: bool, 332 | ) -> io::Result { 333 | if sum_sq < 2 * context.1 + (if is_chroma { 250 } else { 100 }) { 334 | interleaved_decode(stream, 0) 335 | } else if sum_sq < 2 * context.1 + 950 { 336 | interleaved_decode(stream, 1) 337 | } else if sum_sq < 3 * context.1 + 3000 { 338 | if sum_sq < 5 * context.1 + 400 { 339 | signed_decode(stream, 1) 340 | } else { 341 | interleaved_decode(stream, 2) 342 | } 343 | } else if sum_sq < 3 * context.1 + 12000 { 344 | if sum_sq < 5 * context.1 + 3000 { 345 | signed_decode(stream, 2) 346 | } else { 347 | interleaved_decode(stream, 3) 348 | } 349 | } else if sum_sq < 4 * context.1 + 44000 { 350 | if sum_sq < 6 * context.1 + 12000 { 351 | signed_decode(stream, 3) 352 | } else { 353 | interleaved_decode(stream, 4) 354 | } 355 | } else { 356 | signed_decode(stream, 4) 357 | } 358 | } 359 | 360 | fn get_run_coder_fast(context: (u32, u32), s: i32, run_coder: u32) -> u32 { 361 | // use decaying first and second moment 362 | if (s == 0) == (run_coder == 0) { 363 | if context.0 < 1 { 364 | 4 365 | } else if context.0 < 2 { 366 | 3 367 | } else if context.0 < 4 { 368 | 2 369 | } else if context.0 < 8 { 370 | 1 371 | } else { 372 | 0 373 | } 374 | } else { 375 | run_coder 376 | } 377 | } 378 | 379 | fn get_run_coder(context: (u32, u32), s: i32, q: i32, run_coder: u32, sum_sq: u32) -> u32 { 380 | if (s == 0) == (run_coder == 0) { 381 | if q == 1024 { 382 | if context.0 < 2 { 383 | 1 384 | } else { 385 | 0 386 | } 387 | } else if context.0 < 4 && context.1 < 2 { 388 | 4 389 | } else if context.0 < 8 && context.1 < 4 { 390 | 3 391 | } else if 2 * sum_sq < 3 * context.1 + 48 { 392 | 2 393 | } else if 2 * sum_sq < 5 * context.1 + 32 { 394 | 1 395 | } else { 396 | 0 397 | } 398 | } else { 399 | run_coder 400 | } 401 | } 402 | 403 | pub fn decode( 404 | mut image: ImageChunkMut<'_, i16>, 405 | stream: &mut impl bits::BitsReader, 406 | scheme: Encoder, 407 | q: i32, 408 | has_dc: bool, 409 | is_chroma: bool, 410 | ) -> io::Result<()> { 411 | let step = image.step as i32; 412 | let x_range = (image.x_range.0 as i32, image.x_range.1 as i32); 413 | let y_range = (image.y_range.0 as i32, image.y_range.1 as i32); 414 | 415 | let sizex = (image.x_range.1 - image.x_range.0) as i32; 416 | let sizey = (image.y_range.1 - image.y_range.0) as i32; 417 | 418 | if has_dc && (sizex > 0) && (sizey > 0) { 419 | unsafe { 420 | *image.get_unchecked_mut(y_range.0 as usize, x_range.0 as usize) = 421 | signed_decode(stream, 4)? as i16; 422 | } 423 | } 424 | let mut context = (0, 0); 425 | let mut run = -1; 426 | let mut run_coder = 427 | if scheme == Encoder::Turbo && ((q == 0) || ((step < 2048) && (q * step < 2048))) { 428 | 1 429 | } else { 430 | 0 431 | }; 432 | 433 | for y in (0..sizey).step_by(step as usize) { 434 | let x_step = if (y & step) != 0 { step } else { step * 2 }; 435 | for x in ((x_step - step)..sizex).step_by(x_step as usize) { 436 | // [NOTE] arranged so that (x | y) & step == 1 437 | let mut s = 0; 438 | if run_coder != 0 && run == -1 { 439 | run = unsigned_decode(stream, run_coder)? as i32; 440 | } 441 | if run <= 0 { 442 | if scheme == Encoder::Turbo { 443 | s = interleaved_decode(stream, 1)?; 444 | } else { 445 | if scheme == Encoder::Contextual { 446 | context = unsafe { get_context(&image, x as i32, y as i32) }; 447 | } 448 | let sum_sq = square(context.0); 449 | s = get_s(&mut *stream, sum_sq, context, is_chroma)?; 450 | 451 | if scheme == Encoder::Fast { 452 | let t = s.abs() as u32; 453 | context = ( 454 | ((context.0 * 15 + 7) >> 4) + t, 455 | ((context.1 * 15 + 7) >> 4) + square(t.min(4096)), 456 | ); 457 | run_coder = get_run_coder_fast(context, s, run_coder); 458 | } else { 459 | run_coder = get_run_coder(context, s, q, run_coder, sum_sq); 460 | } 461 | } 462 | if run == 0 && s <= 0 { 463 | s -= 1; // s can't be zero, so shift negatives by 1 464 | } 465 | run = -1; 466 | } else { 467 | run -= 1; // consume a zero 468 | } 469 | unsafe { 470 | *image.get_unchecked_mut((y_range.0 + y) as usize, (x_range.0 + x) as usize) = 471 | s as i16 472 | }; 473 | } 474 | } 475 | 476 | Ok(()) 477 | } 478 | -------------------------------------------------------------------------------- /src/color_transform/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{cmp, io, u8}; 2 | 3 | use num_traits::cast::NumCast; 4 | 5 | use crate::bits::{BitsIOReader, BitsIOWriter, BitsReader, BitsWriter}; 6 | use crate::encode::{signed_code, signed_decode}; 7 | use crate::errors::DecompressError; 8 | use crate::header; 9 | 10 | #[cfg(test)] 11 | mod test; 12 | 13 | #[derive(Clone, Debug, PartialEq)] 14 | pub struct ChannelTransformFactor { 15 | pub src_channel: usize, 16 | pub factor: isize, 17 | } 18 | 19 | #[derive(Clone, Debug, PartialEq)] 20 | pub struct ChannelTransform { 21 | pub dest_channel: usize, 22 | pub channel_factors: Vec, 23 | pub denominator: isize, 24 | pub is_chroma: bool, 25 | } 26 | 27 | pub struct ChannelTransformBuilder { 28 | dest_channel: usize, 29 | channel_factors: Vec, 30 | denominator: isize, 31 | is_chroma: bool, 32 | } 33 | 34 | impl ChannelTransformBuilder { 35 | pub fn with_dest_channel(dest_channel: usize) -> ChannelTransformBuilder { 36 | ChannelTransformBuilder { 37 | dest_channel, 38 | channel_factors: vec![], 39 | denominator: 1, 40 | is_chroma: false, 41 | } 42 | } 43 | 44 | pub fn set_chroma(&mut self) -> &mut Self { 45 | self.is_chroma = true; 46 | self 47 | } 48 | 49 | pub fn add_channel_factor(&mut self, src_channel: usize, factor: isize) -> &mut Self { 50 | self.channel_factors.push(ChannelTransformFactor { 51 | src_channel, 52 | factor, 53 | }); 54 | self 55 | } 56 | 57 | pub fn set_denominator(&mut self, denominator: isize) -> &mut Self { 58 | assert!( 59 | self.denominator > 0, 60 | "Denominator should be positive integer" 61 | ); 62 | self.denominator = denominator; 63 | self 64 | } 65 | 66 | pub fn build(&self) -> ChannelTransform { 67 | ChannelTransform { 68 | dest_channel: self.dest_channel, 69 | channel_factors: self.channel_factors.clone(), 70 | denominator: self.denominator, 71 | is_chroma: self.is_chroma, 72 | } 73 | } 74 | } 75 | 76 | #[derive(Clone, Debug, Default, PartialEq)] 77 | pub struct ColorTransformProgram { 78 | channel_transforms: Vec, 79 | } 80 | 81 | impl ColorTransformProgram { 82 | pub fn new() -> Self { 83 | ColorTransformProgram { 84 | channel_transforms: vec![], 85 | } 86 | } 87 | 88 | /// Stores input as yuv444. Does not perform any color transforms, just flags second and 89 | /// third channels as chroma channels. Decompressed output also will be in yuv44. 90 | pub fn yuv444_to_yuv444() -> Self { 91 | let mut program = Self::new(); 92 | 93 | program 94 | .add_channel_transform( 95 | ChannelTransformBuilder::with_dest_channel(1) 96 | .set_chroma() 97 | .build(), 98 | ) 99 | .add_channel_transform( 100 | ChannelTransformBuilder::with_dest_channel(2) 101 | .set_chroma() 102 | .build(), 103 | ); 104 | program 105 | } 106 | 107 | /// Stores rgb data as approximated YUV444 (real order is UYV). 108 | /// performs the following: R -= G (chroma); B -= G (chroma); G += (R + B) / 4 (luma) 109 | pub fn rgb_to_yuv() -> Self { 110 | let mut program = Self::new(); 111 | 112 | program 113 | .add_channel_transform( 114 | ChannelTransformBuilder::with_dest_channel(0) 115 | .add_channel_factor(1, -1) 116 | .set_chroma() 117 | .build(), 118 | ) 119 | .add_channel_transform( 120 | ChannelTransformBuilder::with_dest_channel(2) 121 | .add_channel_factor(1, -1) 122 | .set_chroma() 123 | .build(), 124 | ) 125 | .add_channel_transform( 126 | ChannelTransformBuilder::with_dest_channel(1) 127 | .add_channel_factor(0, 1) 128 | .add_channel_factor(2, 1) 129 | .set_denominator(4) 130 | .build(), 131 | ); 132 | program 133 | } 134 | 135 | /// Stores bgr data as approximated a710. 136 | /// performs the following: 137 | /// R -= G (chroma); B -= (G * 2 + R) / 2 (chroma); G += (B * 2 + R * 3) / 8 (luma) 138 | pub fn bgr_to_a710() -> Self { 139 | let mut program = Self::new(); 140 | 141 | program 142 | .add_channel_transform( 143 | ChannelTransformBuilder::with_dest_channel(2) 144 | .add_channel_factor(1, -1) 145 | .set_chroma() 146 | .build(), 147 | ) 148 | .add_channel_transform( 149 | ChannelTransformBuilder::with_dest_channel(0) 150 | .add_channel_factor(1, -2) 151 | .add_channel_factor(2, -1) 152 | .set_denominator(2) 153 | .set_chroma() 154 | .build(), 155 | ) 156 | .add_channel_transform( 157 | ChannelTransformBuilder::with_dest_channel(1) 158 | .add_channel_factor(0, 2) 159 | .add_channel_factor(2, 3) 160 | .set_denominator(8) 161 | .build(), 162 | ); 163 | program 164 | } 165 | 166 | /// Stores rgb data as approximated a710. 167 | /// performs the following: 168 | /// R -= G (chroma); B -= (G * 2 + R) / 2 (chroma); G += (B * 2 + R * 3) / 8 (luma) 169 | pub fn rgb_to_a710() -> Self { 170 | let mut program = Self::new(); 171 | 172 | program 173 | .add_channel_transform( 174 | ChannelTransformBuilder::with_dest_channel(0) 175 | .add_channel_factor(1, -1) 176 | .set_chroma() 177 | .build(), 178 | ) 179 | .add_channel_transform( 180 | ChannelTransformBuilder::with_dest_channel(2) 181 | .add_channel_factor(1, -2) 182 | .add_channel_factor(0, -1) 183 | .set_denominator(2) 184 | .set_chroma() 185 | .build(), 186 | ) 187 | .add_channel_transform( 188 | ChannelTransformBuilder::with_dest_channel(1) 189 | .add_channel_factor(2, 2) 190 | .add_channel_factor(0, 3) 191 | .set_denominator(8) 192 | .build(), 193 | ); 194 | program 195 | } 196 | 197 | pub fn decode( 198 | mut buffer: &mut impl io::Read, 199 | is_chroma: &mut [bool], 200 | ) -> Result { 201 | let mut stream = BitsIOReader::new(&mut buffer); 202 | let mut color_transform_program = ColorTransformProgram::new(); 203 | loop { 204 | let dest_channel = signed_decode(&mut stream, 2)?; 205 | 206 | if dest_channel < 0 { 207 | break; 208 | } 209 | let dest_channel = dest_channel as usize; 210 | 211 | if dest_channel >= is_chroma.len() { 212 | return Err(DecompressError::Malformed); 213 | } 214 | 215 | let mut channel_transform_builder = 216 | ChannelTransformBuilder::with_dest_channel(dest_channel); 217 | loop { 218 | let src_channel = signed_decode(&mut stream, 2)?; 219 | 220 | if src_channel < 0 { 221 | break; 222 | } 223 | if src_channel as usize >= is_chroma.len() { 224 | return Err(DecompressError::Malformed); 225 | } 226 | 227 | let factor = signed_decode(&mut stream, 2)?; 228 | channel_transform_builder.add_channel_factor(src_channel as usize, factor as isize); 229 | } 230 | let denominator = signed_decode(&mut stream, 2)?; 231 | if denominator == 0 { 232 | return Err(DecompressError::Malformed); 233 | } 234 | channel_transform_builder.set_denominator(denominator as isize); 235 | 236 | let channel_is_chroma = signed_decode(&mut stream, 2)?; 237 | 238 | if channel_is_chroma != 0 { 239 | channel_transform_builder.set_chroma(); 240 | is_chroma[dest_channel] = true; 241 | } 242 | 243 | color_transform_program.add_channel_transform(channel_transform_builder.build()); 244 | } 245 | stream.flush_read_word(); 246 | Ok(color_transform_program) 247 | } 248 | 249 | pub fn add_channel_transform(&mut self, channel_transform: ChannelTransform) -> &mut Self { 250 | self.channel_transforms.push(channel_transform); 251 | self 252 | } 253 | 254 | pub fn is_channel_has_transform(&self, channel: usize) -> bool { 255 | self.channel_transforms 256 | .iter() 257 | .filter(|t| t.dest_channel == channel) 258 | .any(|c| c.denominator > 1 || !c.channel_factors.is_empty()) 259 | } 260 | 261 | pub fn iter(&self) -> impl DoubleEndedIterator { 262 | self.channel_transforms.iter() 263 | } 264 | 265 | pub fn encode( 266 | &self, 267 | channels: usize, 268 | mut buffer: &mut impl io::Write, 269 | ) -> io::Result> { 270 | let mut stream = BitsIOWriter::new(&mut buffer); 271 | let mut is_chroma = vec![false; channels]; 272 | 273 | for channel_transform in &self.channel_transforms { 274 | signed_code(channel_transform.dest_channel as i32, &mut stream, 2)?; 275 | 276 | for channel_factor in &channel_transform.channel_factors { 277 | signed_code(channel_factor.src_channel as i32, &mut stream, 2)?; 278 | signed_code(channel_factor.factor as i32, &mut stream, 2)?; 279 | } 280 | signed_code(-1, &mut stream, 2)?; 281 | 282 | signed_code(channel_transform.denominator as i32, &mut stream, 2)?; 283 | signed_code(channel_transform.is_chroma as i32, &mut stream, 2)?; 284 | 285 | is_chroma[channel_transform.dest_channel] = channel_transform.is_chroma; 286 | } 287 | 288 | // end of decode program 289 | signed_code(-1, &mut stream, 2)?; 290 | stream.flush_write_word()?; 291 | 292 | Ok(is_chroma) 293 | } 294 | 295 | fn transform_base( 296 | &self, 297 | image: &[T], 298 | header: &header::Header, 299 | aux: &mut [i16], 300 | channels_in_index: usize, 301 | channel_layer: F, 302 | ) where 303 | T: Into + Copy, 304 | F: Fn(usize, usize, usize) -> usize, 305 | { 306 | assert!(aux.len() >= image.len()); 307 | 308 | let boost = header.get_boost() as i16; 309 | let channels = header.channels as usize; 310 | let channel_size = header.get_channel_size(); 311 | let mut is_channel_transformed = vec![false; channels * header.layers as usize]; 312 | 313 | for channel_transform in &self.channel_transforms { 314 | let dest_base = channel_transform.dest_channel * channel_size; 315 | 316 | for channel_factor in &channel_transform.channel_factors { 317 | if is_channel_transformed[channel_factor.src_channel] { 318 | for i in 0..channel_size { 319 | aux[dest_base + i] += aux[channel_factor.src_channel * channel_size + i] 320 | * channel_factor.factor as i16; 321 | } 322 | } else { 323 | let boosted_factor = channel_factor.factor as i16 * boost; 324 | let layer = channel_layer(channel_factor.src_channel, channels, channel_size); 325 | for i in 0..channel_size { 326 | aux[dest_base + i] += 327 | image[layer + i * channels_in_index].into() * boosted_factor; 328 | } 329 | } 330 | } 331 | 332 | let layer = channel_layer(channel_transform.dest_channel, channels, channel_size); 333 | for i in 0..channel_size { 334 | aux[dest_base + i] /= channel_transform.denominator as i16; 335 | aux[dest_base + i] += image[layer + i * channels_in_index].into() * boost; 336 | } 337 | 338 | is_channel_transformed[channel_transform.dest_channel] = true; 339 | } 340 | 341 | for (channel, is_transformed) in is_channel_transformed.iter().enumerate() { 342 | if !is_transformed { 343 | let dest_base = channel * channel_size; 344 | let layer = channel_layer(channel, channels, channel_size); 345 | for i in 0..channel_size { 346 | aux[dest_base + i] = image[layer + i * channels_in_index].into() * boost; 347 | } 348 | } 349 | } 350 | } 351 | 352 | pub fn transform_and_to_planar( 353 | &self, 354 | image: &[T], 355 | header: &header::Header, 356 | mut aux: &mut [i16], 357 | ) where 358 | T: Into + Copy, 359 | { 360 | ColorTransformProgram::transform_base( 361 | &self, 362 | &image, 363 | &header, 364 | &mut aux, 365 | header.channels as usize, 366 | get_layer, 367 | ); 368 | } 369 | 370 | pub fn transform(&self, image: &[T], header: &header::Header, mut aux: &mut [i16]) 371 | where 372 | T: Into + Copy, 373 | { 374 | ColorTransformProgram::transform_base( 375 | &self, 376 | &image, 377 | &header, 378 | &mut aux, 379 | 1, 380 | |channel, _, channel_size| channel * channel_size, 381 | ); 382 | } 383 | 384 | fn detransform_base<'a, T>( 385 | &self, 386 | aux: &mut [i16], 387 | header: &header::Header, 388 | channel_size: usize, 389 | image: &'a mut [T], 390 | ) -> (&'a mut [T], i16) 391 | where 392 | T: NumCast, 393 | { 394 | assert!(image.len() >= aux.len()); 395 | 396 | for channel_transform in self.channel_transforms.iter().rev() { 397 | let mut transform_temp = vec![0_i16; channel_size]; 398 | let dest_base = channel_transform.dest_channel * channel_size; 399 | 400 | for channel_factor in &channel_transform.channel_factors { 401 | for i in 0..channel_size { 402 | transform_temp[i] += aux[channel_factor.src_channel * channel_size + i] 403 | * channel_factor.factor as i16; 404 | } 405 | } 406 | 407 | for i in 0..channel_size { 408 | transform_temp[i] /= channel_transform.denominator as i16; 409 | aux[dest_base + i] -= transform_temp[i]; 410 | } 411 | } 412 | 413 | // split off leftover 414 | let (image, _) = image.split_at_mut(aux.len()); 415 | 416 | let boost = header.get_boost(); 417 | 418 | (image, boost) 419 | } 420 | 421 | pub fn detransform_and_to_interleaved( 422 | &self, 423 | mut aux: &mut [i16], 424 | header: &header::Header, 425 | channel_size: usize, 426 | mut image: &mut [T], 427 | ) where 428 | T: NumCast, 429 | { 430 | let (image, boost) = ColorTransformProgram::detransform_base( 431 | &self, 432 | &mut aux, 433 | &header, 434 | channel_size, 435 | &mut image, 436 | ); 437 | 438 | let channels = header.channels as usize; 439 | for c in 0..channels * header.layers as usize { 440 | let layer = get_layer(c, channels, channel_size); 441 | for i in 0..channel_size { 442 | image[layer + i * channels] = 443 | T::from(cut_with_u8(aux[c * channel_size + i] / boost)).unwrap(); 444 | } 445 | } 446 | } 447 | 448 | pub fn detransform( 449 | &self, 450 | mut aux: &mut [i16], 451 | header: &header::Header, 452 | channel_size: usize, 453 | mut image: &mut [T], 454 | ) where 455 | T: NumCast, 456 | { 457 | let (image, boost) = ColorTransformProgram::detransform_base( 458 | &self, 459 | &mut aux, 460 | &header, 461 | channel_size, 462 | &mut image, 463 | ); 464 | 465 | for (dest, src) in image.iter_mut().zip(aux.iter()) { 466 | *dest = T::from(cut_with_u8(*src / boost)).unwrap(); 467 | } 468 | } 469 | } 470 | 471 | fn convert_between_interleaved_and_planar( 472 | len: usize, 473 | channels: usize, 474 | skip_channels: &[usize], 475 | mut output: &mut [T], 476 | func: F, 477 | ) where 478 | F: Fn(&mut [T], usize, usize, usize), 479 | { 480 | let channel_size = len / channels; 481 | 482 | let mut skipped = 0; 483 | for c in 0..channels { 484 | if skip_channels.contains(&c) { 485 | skipped += 1; 486 | continue; 487 | } 488 | let dest_base = (c - skipped) * channel_size; 489 | let layer = get_layer(c, channels, channel_size); 490 | for i in 0..channel_size { 491 | func(&mut output, layer, dest_base, i); 492 | } 493 | } 494 | } 495 | 496 | pub fn interleaved_to_planar( 497 | input: &[T], 498 | channels: usize, 499 | boost: i16, 500 | mut output: &mut [i16], 501 | skip_channels: &[usize], 502 | ) where 503 | T: Into + Copy, 504 | { 505 | convert_between_interleaved_and_planar( 506 | input.len(), 507 | channels, 508 | &skip_channels, 509 | &mut output, 510 | |output, layer, dest_base, i| { 511 | output[dest_base + i] = input[layer + i * channels].into() * boost; 512 | }, 513 | ); 514 | } 515 | 516 | pub fn planar_to_interleaved( 517 | input: &[i16], 518 | channels: usize, 519 | boost: i16, 520 | mut output: &mut [T], 521 | skip_channels: &[usize], 522 | ) where 523 | T: NumCast, 524 | { 525 | convert_between_interleaved_and_planar( 526 | output.len(), 527 | channels, 528 | &skip_channels, 529 | &mut output, 530 | |output, layer, dest_base, i| { 531 | output[layer + i * channels] = 532 | T::from(cut_with_u8(input[dest_base + i] / boost)).unwrap(); 533 | }, 534 | ); 535 | } 536 | 537 | fn get_layer(channel: usize, channels: usize, channel_size: usize) -> usize { 538 | (channel / channels) * channel_size * channels + channel % channels 539 | } 540 | 541 | fn cut_with_u8(value: T) -> T 542 | where 543 | T: cmp::Ord + From, 544 | { 545 | value.min(u8::MAX.into()).max(u8::MIN.into()) 546 | } 547 | -------------------------------------------------------------------------------- /src/compress/test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn test_lift_and_quantize() { 5 | let width = 12; 6 | let height = 8; 7 | let layers = 1; 8 | let channels = 3; 9 | let header = header::Header { 10 | version: 1, 11 | width, 12 | height, 13 | layers, 14 | channels, 15 | bit_depth: 8, 16 | is_signed: false, 17 | quality: 124, 18 | chroma_scale: 8, 19 | block_size: header::BLOCK_DEFAULT, 20 | filter: header::Filter::Linear, 21 | quantization: header::Quantization::Scalar, 22 | encoder: header::Encoder::Contextual, 23 | intent: header::Intent::RGB, 24 | metadata_size: 0, 25 | channel_size: width as usize * height as usize, 26 | image_size: width as usize * height as usize * layers as usize * channels as usize, 27 | }; 28 | 29 | let mut aux_data = vec![ 30 | 0, 24, 48, 72, 96, 120, 144, 168, 192, 216, 240, 264, 288, 312, 336, 360, 384, 408, 432, 31 | 456, 480, 504, 528, 552, 576, 600, 624, 648, 672, 696, 720, 744, 768, 792, 816, 840, 864, 32 | 888, 912, 936, 960, 984, 1008, 1032, 1056, 1080, 1104, 1128, 1152, 1176, 1200, 1224, 1248, 33 | 1272, 1296, 1320, 1344, 1368, 1392, 1416, 1440, 1464, 1488, 1512, 1536, 1560, 1584, 1608, 34 | 1632, 1656, 1680, 1704, 1728, 1752, 1776, 1800, 1824, 1848, 1872, 1896, 1920, 1944, 1968, 35 | 1992, 2016, 2040, 16, 40, 64, 88, 112, 136, 160, 184, 208, 232, 8, 32, 56, 80, 104, 128, 36 | 152, 176, 200, 224, 248, 272, 296, 320, 344, 368, 392, 416, 440, 464, 488, 512, 536, 560, 37 | 584, 608, 632, 656, 680, 704, 728, 752, 776, 800, 824, 848, 872, 896, 920, 944, 968, 992, 38 | 1016, 1040, 1064, 1088, 1112, 1136, 1160, 1184, 1208, 1232, 1256, 1280, 1304, 1328, 1352, 39 | 1376, 1400, 1424, 1448, 1472, 1496, 1520, 1544, 1568, 1592, 1616, 1640, 1664, 1688, 1712, 40 | 1736, 1760, 1784, 1808, 1832, 1856, 1880, 1904, 1928, 1952, 1976, 2000, 2024, 0, 24, 48, 41 | 72, 96, 120, 144, 168, 192, 216, 240, 16, 40, 64, 88, 112, 136, 160, 184, 208, 232, 256, 42 | 280, 304, 328, 352, 376, 400, 424, 448, 472, 496, 520, 544, 568, 592, 616, 640, 664, 688, 43 | 712, 736, 760, 784, 808, 832, 856, 880, 904, 928, 952, 976, 1000, 1024, 1048, 1072, 1096, 44 | 1120, 1144, 1168, 1192, 1216, 1240, 1264, 1288, 1312, 1336, 1360, 1384, 1408, 1432, 1456, 45 | 1480, 1504, 1528, 1552, 1576, 1600, 1624, 1648, 1672, 1696, 1720, 1744, 1768, 1792, 1816, 46 | 1840, 1864, 1888, 1912, 1936, 1960, 1984, 2008, 2032, 8, 32, 56, 80, 104, 128, 152, 176, 47 | 200, 224, 248, 48 | ]; 49 | 50 | let expected = vec![ 51 | 0, 0, 0, 0, 0, 0, 0, 0, 202, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 79, 0, -1, 0, -4, 0, 0, 0, 69, 0, 1, 53 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 3, -5, 0, 2, 0, 0, 0, 4, 0, 0, 0, 4, 15, -22, 0, 54 | -26, 0, -26, 0, -26, 0, -26, 0, 8, 0, 0, 0, 0, 0, 0, 0, 202, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 55 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 79, 56 | 0, -2, 0, -5, 0, 0, 0, 69, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, -3, -9, 0, 1, 57 | 0, 0, 0, 4, 0, 0, 0, 4, -15, -30, 0, -26, 0, -26, 0, -26, 0, -26, 0, 16, 0, 0, 0, 0, 0, 0, 58 | 0, 202, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 79, 0, -2, 0, -5, 0, 0, 0, 69, 0, 1, 0, 0, 0, 0, 0, 0, 0, 60 | 0, 0, 0, 0, 0, 0, 19, -3, -9, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, -15, -30, 0, -26, 0, -26, 0, 61 | -26, 0, -26, 0, 62 | ]; 63 | 64 | lift_and_quantize(&mut aux_data, &header, &[false; 3]); 65 | assert_eq!(aux_data, expected); 66 | } 67 | 68 | #[test] 69 | fn test_compress_lossy_contextual_linear() { 70 | let width = 12; 71 | let height = 8; 72 | let layers = 1; 73 | let channels = 3; 74 | let header = header::Header { 75 | version: 1, 76 | width, 77 | height, 78 | layers, 79 | channels, 80 | bit_depth: 8, 81 | is_signed: false, 82 | quality: 124, 83 | chroma_scale: 8, 84 | block_size: header::BLOCK_DEFAULT, 85 | filter: header::Filter::Linear, 86 | quantization: header::Quantization::Scalar, 87 | encoder: header::Encoder::Contextual, 88 | intent: header::Intent::RGB, 89 | metadata_size: 0, 90 | channel_size: width as usize * height as usize, 91 | image_size: width as usize * height as usize * layers as usize * channels as usize, 92 | }; 93 | 94 | let mut image = (0..header.get_image_size() as u32) 95 | .map(|i| (i % 256) as i16) 96 | .collect::>(); 97 | 98 | let mut buffer = vec![0u8; image.len()]; 99 | 100 | let gfwx_size = compress_aux_data(&mut image, &header, &[false; 3], &mut buffer).unwrap(); 101 | 102 | let expected = vec![ 103 | 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 76, 0, 128, 0, 64, 28, 2, 16, 7, 8, 0, 1, 0, 0, 0, 104 | 1, 0, 0, 0, 1, 0, 0, 0, 0, 32, 96, 128, 0, 224, 228, 137, 0, 128, 228, 136, 1, 0, 0, 0, 1, 105 | 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 162, 0, 0, 0, 162, 88, 192, 0, 236, 0, 0, 0, 20, 1, 0, 0, 0, 106 | 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 192, 133, 0, 0, 192, 133, 149, 213, 184, 153, 0, 0, 112, 216, 107 | ]; 108 | 109 | assert_eq!(&buffer[..gfwx_size], expected.as_slice()); 110 | } 111 | 112 | #[test] 113 | fn test_compress_lossy_turbo_cubic() { 114 | let width = 12; 115 | let height = 8; 116 | let layers = 1; 117 | let channels = 3; 118 | let header = header::Header { 119 | version: 1, 120 | width, 121 | height, 122 | layers, 123 | channels, 124 | bit_depth: 8, 125 | is_signed: false, 126 | quality: 512, 127 | chroma_scale: 8, 128 | block_size: header::BLOCK_DEFAULT, 129 | filter: header::Filter::Cubic, 130 | quantization: header::Quantization::Scalar, 131 | encoder: header::Encoder::Turbo, 132 | intent: header::Intent::RGB, 133 | metadata_size: 0, 134 | channel_size: width as usize * height as usize, 135 | image_size: width as usize * height as usize * layers as usize * channels as usize, 136 | }; 137 | 138 | let mut image = (0..header.get_image_size() as u32) 139 | .map(|i| (i % 256) as i16) 140 | .collect::>(); 141 | 142 | let mut buffer = vec![0u8; 2 * image.len()]; 143 | 144 | let gfwx_size = compress_aux_data(&mut image, &header, &[false; 3], &mut buffer).unwrap(); 145 | 146 | let expected = vec![ 147 | 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 12, 128, 0, 24, 16, 2, 6, 4, 8, 0, 2, 0, 0, 0, 2, 148 | 0, 0, 0, 1, 0, 0, 0, 0, 24, 2, 128, 0, 0, 0, 33, 0, 24, 2, 128, 0, 0, 0, 33, 128, 0, 32, 149 | 134, 1, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 192, 243, 60, 2, 192, 243, 60, 2, 104, 188, 87, 150 | 106, 3, 192, 62, 0, 192, 62, 0, 236, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 6, 0, 0, 0, 151 | 6, 0, 1, 89, 11, 0, 193, 1, 14, 224, 128, 5, 44, 96, 0, 128, 0, 147, 152 | ]; 153 | 154 | assert_eq!(&buffer[..gfwx_size], expected.as_slice()); 155 | } 156 | 157 | #[test] 158 | fn test_compress_loseless_fast_linear_block_max() { 159 | let width = 12; 160 | let height = 8; 161 | let layers = 1; 162 | let channels = 3; 163 | let header = header::Header { 164 | version: 1, 165 | width, 166 | height, 167 | layers, 168 | channels, 169 | bit_depth: 8, 170 | is_signed: false, 171 | quality: header::QUALITY_MAX, 172 | chroma_scale: 8, 173 | block_size: header::BLOCK_MAX, 174 | filter: header::Filter::Linear, 175 | quantization: header::Quantization::Scalar, 176 | encoder: header::Encoder::Fast, 177 | intent: header::Intent::RGB, 178 | metadata_size: 0, 179 | channel_size: width as usize * height as usize, 180 | image_size: width as usize * height as usize * layers as usize * channels as usize, 181 | }; 182 | 183 | let mut image = (0..header.get_image_size() as u32) 184 | .map(|i| (i % 256) as i16) 185 | .collect::>(); 186 | 187 | let mut buffer = vec![0u8; 2 * image.len()]; 188 | 189 | let gfwx_size = compress_aux_data(&mut image, &header, &[false; 3], &mut buffer).unwrap(); 190 | 191 | let expected = vec![ 192 | 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 76, 0, 128, 152, 0, 16, 2, 0, 4, 8, 0, 0, 0, 0, 38, 193 | 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 248, 1, 0, 192, 0, 224, 3, 0, 248, 1, 0, 192, 0, 224, 194 | 3, 0, 0, 25, 0, 192, 56, 0, 0, 4, 3, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0, 0, 48, 72, 196, 152, 195 | 1, 128, 25, 0, 192, 12, 0, 0, 48, 72, 196, 152, 1, 128, 25, 0, 192, 12, 0, 36, 0, 96, 196, 196 | 4, 0, 12, 0, 0, 79, 0, 216, 0, 40, 18, 5, 0, 0, 106, 1, 0, 128, 254, 1, 0, 0, 80, 11, 5, 0, 197 | 0, 0, 5, 0, 0, 0, 11, 0, 0, 0, 133, 10, 37, 209, 0, 220, 0, 96, 224, 6, 0, 55, 128, 3, 192, 198 | 1, 0, 0, 0, 176, 133, 10, 37, 209, 0, 220, 0, 96, 224, 6, 0, 55, 128, 3, 192, 1, 0, 0, 0, 199 | 176, 0, 20, 37, 209, 0, 0, 250, 4, 0, 128, 51, 1, 0, 192, 92, 0, 132, 2, 0, 81, 0, 2, 5, 0, 200 | 0, 0, 129, 2, 24, 1, 66, 160, 102, 152, 77, 68, 5, 44, 192, 4, 0, 0, 176, 128, 201 | ]; 202 | 203 | assert_eq!(&buffer[..gfwx_size], expected.as_slice()); 204 | } 205 | 206 | #[test] 207 | fn test_decompress_lossy_contextual_linear() { 208 | let expected = vec![ 209 | 0, 25, 50, 75, 101, 123, 145, 167, 190, 214, 239, 239, 288, 312, 337, 363, 389, 410, 432, 210 | 454, 476, 500, 525, 525, 576, 600, 625, 651, 678, 699, 720, 741, 763, 787, 812, 812, 864, 211 | 888, 912, 939, 967, 987, 1008, 1029, 1050, 1074, 1099, 1099, 1152, 1176, 1200, 1228, 1256, 212 | 1276, 1296, 1316, 1337, 1361, 1386, 1386, 1437, 1451, 1490, 1514, 1538, 1560, 1583, 1606, 213 | 1629, 1653, 1678, 1678, 1722, 1726, 1781, 1800, 1820, 1845, 1871, 1896, 1922, 1946, 1971, 214 | 1971, 2019, 2028, 40, 55, 70, 95, 121, 146, 172, 196, 221, 221, 8, 33, 58, 83, 109, 131, 215 | 153, 175, 198, 222, 247, 247, 296, 320, 345, 370, 396, 418, 441, 463, 486, 510, 535, 535, 216 | 584, 608, 632, 658, 684, 706, 729, 752, 775, 799, 824, 824, 872, 895, 919, 945, 972, 994, 217 | 1017, 1040, 1064, 1088, 1113, 1113, 1160, 1183, 1206, 1233, 1260, 1283, 1306, 1329, 1353, 218 | 1377, 1402, 1402, 1445, 1479, 1490, 1516, 1542, 1567, 1593, 1619, 1645, 1669, 1694, 1694, 219 | 1730, 1777, 1775, 1799, 1824, 1852, 1881, 1909, 1938, 1962, 1987, 1987, 2027, 22, 16, 45, 220 | 74, 102, 131, 159, 188, 212, 237, 237, 16, 41, 66, 91, 117, 139, 161, 183, 206, 230, 255, 221 | 255, 304, 328, 353, 378, 404, 426, 449, 471, 494, 518, 543, 543, 592, 616, 640, 666, 692, 222 | 714, 737, 760, 783, 807, 832, 832, 880, 903, 927, 953, 980, 1002, 1025, 1048, 1072, 1096, 223 | 1121, 1121, 1168, 1191, 1214, 1241, 1268, 1291, 1314, 1337, 1361, 1385, 1410, 1410, 1453, 224 | 1487, 1498, 1524, 1550, 1575, 1601, 1627, 1653, 1677, 1702, 1702, 1738, 1785, 1783, 1807, 225 | 1832, 1860, 1889, 1917, 1946, 1970, 1995, 1995, 2035, 30, 24, 53, 82, 110, 139, 167, 196, 226 | 220, 245, 245, 227 | ]; 228 | 229 | let compressed = vec![ 230 | 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 128, 0, 0, 0, 28, 160, 58, 0, 196, 168, 16, 0, 231 | 66, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 35, 0, 0, 128, 232, 0, 0, 64, 212, 54, 0, 128, 0, 232 | 0, 176, 1, 212, 94, 0, 128, 0, 0, 128, 21, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 161, 80, 56, 233 | 198, 177, 170, 211, 7, 0, 0, 0, 96, 161, 80, 56, 198, 78, 233, 244, 1, 0, 0, 0, 44, 161, 234 | 80, 56, 198, 78, 233, 244, 1, 0, 0, 0, 44, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 30, 247, 235 | 157, 138, 8, 0, 36, 0, 86, 128, 25, 144, 0, 0, 80, 1, 28, 247, 149, 138, 0, 32, 19, 0, 2, 236 | 204, 128, 68, 0, 128, 10, 176, 28, 247, 149, 138, 0, 32, 19, 0, 2, 204, 128, 68, 0, 128, 237 | 10, 176, 238 | ]; 239 | 240 | let width = 12; 241 | let height = 8; 242 | let layers = 1; 243 | let channels = 3; 244 | let header = header::Header { 245 | version: 1, 246 | width, 247 | height, 248 | layers, 249 | channels, 250 | bit_depth: 8, 251 | is_signed: false, 252 | quality: 124, 253 | chroma_scale: 8, 254 | block_size: header::BLOCK_DEFAULT, 255 | filter: header::Filter::Linear, 256 | quantization: header::Quantization::Scalar, 257 | encoder: header::Encoder::Contextual, 258 | intent: header::Intent::RGB, 259 | metadata_size: 0, 260 | channel_size: width as usize * height as usize, 261 | image_size: width as usize * height as usize * layers as usize * channels as usize, 262 | }; 263 | 264 | let mut actual = vec![0i16; expected.len()]; 265 | let next_point_of_interest = 266 | decompress_aux_data(&compressed, &header, &[false; 3], 0, false, &mut actual).unwrap(); 267 | 268 | assert_eq!(0, next_point_of_interest); 269 | assert_eq!(expected, actual); 270 | } 271 | 272 | #[test] 273 | fn test_decompress_lossy_contextual_linear_downsampled() { 274 | let expected = vec![ 275 | 0, 50, 101, 145, 190, 239, 576, 625, 678, 720, 763, 812, 1152, 1200, 1256, 1296, 1337, 276 | 1386, 1796, 1404, 1383, 1434, 1485, 1534, 8, 58, 109, 153, 198, 247, 584, 632, 684, 729, 277 | 775, 824, 1160, 1206, 1260, 1306, 1353, 1402, 1804, 1278, 1387, 1444, 1501, 1550, 16, 66, 278 | 117, 161, 206, 255, 592, 640, 692, 737, 783, 832, 1168, 1214, 1268, 1314, 1361, 1410, 1812, 279 | 1286, 1395, 1452, 1509, 1558, 280 | ]; 281 | 282 | let width = 12; 283 | let height = 8; 284 | let layers = 1; 285 | let channels = 3; 286 | let header = header::Header { 287 | version: 1, 288 | width, 289 | height, 290 | layers, 291 | channels, 292 | bit_depth: 8, 293 | is_signed: false, 294 | quality: 124, 295 | chroma_scale: 8, 296 | block_size: header::BLOCK_DEFAULT, 297 | filter: header::Filter::Linear, 298 | quantization: header::Quantization::Scalar, 299 | encoder: header::Encoder::Contextual, 300 | intent: header::Intent::RGB, 301 | metadata_size: 0, 302 | channel_size: width as usize * height as usize, 303 | image_size: width as usize * height as usize * layers as usize * channels as usize, 304 | }; 305 | 306 | let compressed = vec![ 307 | 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 128, 0, 0, 0, 28, 160, 58, 0, 196, 168, 16, 0, 308 | 66, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 35, 0, 0, 128, 232, 0, 0, 64, 212, 54, 0, 128, 0, 309 | 0, 176, 1, 212, 94, 0, 128, 0, 0, 128, 21, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 161, 80, 56, 310 | 198, 177, 170, 211, 7, 0, 0, 0, 96, 161, 80, 56, 198, 78, 233, 244, 1, 0, 0, 0, 44, 161, 311 | 80, 56, 198, 78, 233, 244, 1, 0, 0, 0, 44, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 30, 247, 312 | 157, 138, 8, 0, 36, 0, 86, 128, 25, 144, 0, 0, 80, 1, 28, 247, 149, 138, 0, 32, 19, 0, 2, 313 | 204, 128, 68, 0, 128, 10, 176, 28, 247, 149, 138, 0, 32, 19, 0, 2, 204, 128, 68, 0, 128, 314 | 10, 176, 315 | ]; 316 | 317 | let mut actual = vec![0i16; expected.len()]; 318 | let next_point_of_interest = 319 | decompress_aux_data(&compressed, &header, &[false; 3], 1, false, &mut actual).unwrap(); 320 | 321 | assert_eq!(0, next_point_of_interest); 322 | assert_eq!(expected, actual); 323 | } 324 | 325 | #[test] 326 | fn test_decompress_truncated() { 327 | let expected = vec![ 328 | 0, 25, 50, 75, 101, 126, 151, 176, 202, 202, 202, 202, 328, 344, 361, 377, 394, 420, 446, 329 | 472, 498, 498, 498, 498, 656, 664, 672, 680, 688, 714, 741, 767, 794, 794, 794, 794, 984, 330 | 983, 983, 982, 981, 1008, 1035, 1062, 1090, 1090, 1090, 1090, 1313, 1303, 1294, 1284, 1275, 331 | 1302, 1330, 1358, 1386, 1386, 1386, 1386, 1313, 1303, 1294, 1284, 1275, 1302, 1330, 1358, 332 | 1386, 1386, 1386, 1386, 1313, 1303, 1294, 1284, 1275, 1302, 1330, 1358, 1386, 1386, 1386, 333 | 1386, 1313, 1303, 1294, 1284, 1275, 1302, 1330, 1358, 1386, 1386, 1386, 1386, 8, 33, 58, 334 | 83, 109, 134, 159, 184, 210, 210, 210, 210, 336, 351, 367, 383, 399, 426, 453, 480, 508, 335 | 508, 508, 508, 664, 670, 677, 683, 690, 719, 748, 777, 806, 806, 806, 806, 992, 989, 986, 336 | 983, 980, 1011, 1042, 1073, 1104, 1104, 1104, 1104, 1321, 1308, 1296, 1283, 1271, 1303, 337 | 1336, 1369, 1402, 1402, 1402, 1402, 1321, 1308, 1296, 1283, 1271, 1303, 1336, 1369, 1402, 338 | 1402, 1402, 1402, 1321, 1308, 1296, 1283, 1271, 1303, 1336, 1369, 1402, 1402, 1402, 1402, 339 | 1321, 1308, 1296, 1283, 1271, 1303, 1336, 1369, 1402, 1402, 1402, 1402, 16, 41, 66, 91, 340 | 117, 142, 167, 192, 218, 218, 218, 218, 344, 359, 375, 391, 407, 434, 461, 488, 516, 516, 341 | 516, 516, 672, 678, 685, 691, 698, 727, 756, 785, 814, 814, 814, 814, 1000, 997, 994, 991, 342 | 988, 1019, 1050, 1081, 1112, 1112, 1112, 1112, 1329, 1316, 1304, 1291, 1279, 1311, 1344, 343 | 1377, 1410, 1410, 1410, 1410, 1329, 1316, 1304, 1291, 1279, 1311, 1344, 1377, 1410, 1410, 344 | 1410, 1410, 1329, 1316, 1304, 1291, 1279, 1311, 1344, 1377, 1410, 1410, 1410, 1410, 1329, 345 | 1316, 1304, 1291, 1279, 1311, 1344, 1377, 1410, 1410, 1410, 1410, 346 | ]; 347 | 348 | let compressed = vec![ 349 | 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 128, 0, 0, 0, 28, 160, 58, 0, 196, 168, 16, 0, 350 | 66, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 35, 0, 0, 128, 232, 0, 0, 64, 212, 54, 0, 128, 0, 351 | 0, 176, 1, 212, 94, 0, 128, 0, 0, 128, 21, 3, 0, 0, 0, 3, 0, 0, 0, 3_u8, 352 | ]; 353 | 354 | let width = 12; 355 | let height = 8; 356 | let layers = 1; 357 | let channels = 3; 358 | let header = header::Header { 359 | version: 1, 360 | width, 361 | height, 362 | layers, 363 | channels, 364 | bit_depth: 8, 365 | is_signed: false, 366 | quality: 124, 367 | chroma_scale: 8, 368 | block_size: 7, 369 | filter: header::Filter::Linear, 370 | quantization: header::Quantization::Scalar, 371 | encoder: header::Encoder::Contextual, 372 | intent: header::Intent::RGB, 373 | metadata_size: 0, 374 | channel_size: width as usize * height as usize, 375 | image_size: width as usize * height as usize * layers as usize * channels as usize, 376 | }; 377 | 378 | let mut actual = vec![0i16; expected.len()]; 379 | let next_point_of_interest = 380 | decompress_aux_data(&compressed, &header, &[false; 3], 0, false, &mut actual).unwrap(); 381 | 382 | assert_eq!(112, next_point_of_interest); 383 | assert_eq!(expected, actual); 384 | } 385 | 386 | #[test] 387 | fn test_decompress_invalid_block_length() { 388 | let compressed = vec![ 389 | // ↓ there must be 1 instead of 2 390 | 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 254, 9, 0, 0, 254, 9, 0, 0, 254, 9, 0, 1, 0, 0, 0, 1, 391 | 0, 0, 0, 1, 0, 0, 0, 0, 0, 160, 170, 0, 0, 160, 170, 0, 0, 160, 170, 1, 0, 0, 0, 1, 0, 0, 392 | 0, 1, 0, 0, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 8, 0, 393 | ]; 394 | 395 | let width = 8; 396 | let height = 4; 397 | let layers = 1; 398 | let channels = 3; 399 | let header = header::Header { 400 | version: 1, 401 | width, 402 | height, 403 | layers, 404 | channels, 405 | bit_depth: 8, 406 | is_signed: false, 407 | quality: header::QUALITY_MAX, 408 | chroma_scale: 8, 409 | block_size: header::BLOCK_DEFAULT, 410 | filter: header::Filter::Linear, 411 | quantization: header::Quantization::Scalar, 412 | encoder: header::Encoder::Turbo, 413 | intent: header::Intent::RGB, 414 | metadata_size: 0, 415 | channel_size: width as usize * height as usize, 416 | image_size: width as usize * height as usize * layers as usize * channels as usize, 417 | }; 418 | 419 | let mut buffer = vec![0i16; header.get_decompress_buffer_size(0)]; 420 | match decompress_aux_data(&compressed, &header, &[false; 3], 0, false, &mut buffer) { 421 | Err(DecompressError::Underflow) => (), 422 | Err(e) => panic!("unexpected error: {:?}", e), 423 | Ok(_) => panic!("decompress must return error on invalid block lenth"), 424 | } 425 | } 426 | 427 | #[test] 428 | fn test_decompress_invalid_block_length2() { 429 | let compressed = vec![ 430 | // ↓ there must be 1 instead of 2 431 | 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 128, 0, 0, 0, 28, 160, 58, 0, 196, 168, 16, 0, 432 | 66, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 35, 0, 0, 128, 232, 0, 0, 64, 212, 54, 0, 128, 0, 433 | 0, 176, 1, 212, 94, 0, 128, 0, 0, 128, 21, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 161, 80, 56, 434 | 198, 177, 170, 211, 7, 0, 0, 0, 96, 161, 80, 56, 198, 78, 233, 244, 1, 0, 0, 0, 44, 161, 435 | 80, 56, 198, 78, 233, 244, 1, 0, 0, 0, 44, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 30, 247, 436 | 157, 138, 8, 0, 36, 0, 86, 128, 25, 144, 0, 0, 80, 1, 28, 247, 149, 138, 0, 32, 19, 0, 2, 437 | 204, 128, 68, 0, 128, 10, 176, 28, 247, 149, 138, 0, 32, 19, 0, 2, 204, 128, 68, 0, 128, 438 | 10, 176, 439 | ]; 440 | 441 | let width = 12; 442 | let height = 8; 443 | let layers = 1; 444 | let channels = 3; 445 | let header = header::Header { 446 | version: 1, 447 | width, 448 | height, 449 | layers, 450 | channels, 451 | bit_depth: 8, 452 | is_signed: false, 453 | quality: 124, 454 | chroma_scale: 8, 455 | block_size: header::BLOCK_DEFAULT, 456 | filter: header::Filter::Linear, 457 | quantization: header::Quantization::Scalar, 458 | encoder: header::Encoder::Contextual, 459 | intent: header::Intent::RGB, 460 | metadata_size: 0, 461 | channel_size: width as usize * height as usize, 462 | image_size: width as usize * height as usize * layers as usize * channels as usize, 463 | }; 464 | 465 | let mut buffer = vec![0i16; header.get_decompress_buffer_size(0)]; 466 | match decompress_aux_data(&compressed, &header, &[false; 3], 0, false, &mut buffer) { 467 | Err(DecompressError::Underflow) => (), 468 | Err(e) => panic!("unexpected error: {:?}", e), 469 | Ok(_) => panic!("decompress must return error on invalid block lenth"), 470 | } 471 | } 472 | 473 | #[test] 474 | fn test_decompress_invalid_block_length3() { 475 | let compressed = vec![ 476 | 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 128, 0, 0, 0, 28, 160, 58, 0, 196, 168, 16, 0, 477 | 66, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 35, 0, 0, 128, 232, 0, 0, 64, 212, 54, 0, 128, 0, 478 | // ↓ there must be 3 instead of 5 479 | 0, 176, 1, 212, 94, 0, 128, 0, 0, 128, 21, 5, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 161, 80, 56, 480 | 198, 177, 170, 211, 7, 0, 0, 0, 96, 161, 80, 56, 198, 78, 233, 244, 1, 0, 0, 0, 44, 161, 481 | 80, 56, 198, 78, 233, 244, 1, 0, 0, 0, 44, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 30, 247, 482 | 157, 138, 8, 0, 36, 0, 86, 128, 25, 144, 0, 0, 80, 1, 28, 247, 149, 138, 0, 32, 19, 0, 2, 483 | 204, 128, 68, 0, 128, 10, 176, 28, 247, 149, 138, 0, 32, 19, 0, 2, 204, 128, 68, 0, 128, 484 | 10, 176, 485 | ]; 486 | 487 | let width = 12; 488 | let height = 8; 489 | let layers = 1; 490 | let channels = 3; 491 | let header = header::Header { 492 | version: 1, 493 | width, 494 | height, 495 | layers, 496 | channels, 497 | bit_depth: 8, 498 | is_signed: false, 499 | quality: 124, 500 | chroma_scale: 8, 501 | block_size: header::BLOCK_DEFAULT, 502 | filter: header::Filter::Linear, 503 | quantization: header::Quantization::Scalar, 504 | encoder: header::Encoder::Contextual, 505 | intent: header::Intent::RGB, 506 | metadata_size: 0, 507 | channel_size: width as usize * height as usize, 508 | image_size: width as usize * height as usize * layers as usize * channels as usize, 509 | }; 510 | 511 | let mut buffer = vec![0i16; header.get_decompress_buffer_size(0)]; 512 | match decompress_aux_data(&compressed, &header, &[false; 3], 0, false, &mut buffer) { 513 | Err(DecompressError::Underflow) => (), 514 | Err(e) => panic!("unexpected error: {:?}", e), 515 | Ok(_) => panic!("decompress must return error on invalid block lenth"), 516 | } 517 | } 518 | --------------------------------------------------------------------------------