From cd5d0529b122531b37ef0957b8a0205794b20545 Mon Sep 17 00:00:00 2001 From: Matteo Paonessa Date: Thu, 10 Oct 2024 14:05:59 +0200 Subject: [PATCH] Baseline/progressive JPEG switch --- Cargo.toml | 6 +-- README.md | 121 ++++++++++++++++++++++++++++++++++++++--------- src/interface.rs | 12 ++--- src/jpeg.rs | 6 ++- src/lib.rs | 20 +++++--- src/tiff.rs | 16 +++++-- tests/tiff.rs | 5 +- 7 files changed, 139 insertions(+), 47 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6237b99..df3236a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libcaesium" -version = "0.16.5" +version = "0.17.0" authors = ["Matteo Paonessa "] edition = "2021" categories = ["multimedia::images"] @@ -33,12 +33,12 @@ parallel = ["oxipng?/parallel", "imagequant?/threads", "dssim/threads"] mozjpeg-sys = { version = "2.2", optional = true } oxipng = { version = "9.0", default-features = false, features = ["filetime", "zopfli"], optional = true } libc = "0.2" -gifsicle = { version = "1.94", optional = true } +gifsicle = { version = "1.95", optional = true } webp = { version = "0.3.0", optional = true } infer = "0.16.0" image = { version = "0.25.1", default-features = false } img-parts = "0.3" -bytes = "1.6" +bytes = "1.7" lodepng = { version = "3.10", optional = true } imagequant = { version = "4.3", optional = true, default-features = false } tiff = { version = "0.9" } diff --git a/README.md b/README.md index 13590ae..cc13a2f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,18 @@ # libcaesium [![Rust](https://github.com/Lymphatus/libcaesium/actions/workflows/rust.yml/badge.svg)](https://github.com/Lymphatus/libcaesium/actions/workflows/rust.yml) -Libcaesium is a simple library performing JPEG, PNG, WebP and GIF (experimental) compression/optimization written in Rust, with a C interface.\ -**IMPORTANT**: starting from v0.6.0 the library is written in Rust and no longer in C. There's a C interface, but it's not backward compatible with the <0.6.0. +Libcaesium is a simple library performing JPEG, PNG, WebP and GIF (experimental) compression/optimization written in +Rust, with a C interface. + +> [!CAUTION] +> starting from v0.6.0 the library is written in Rust and no longer in C. There's a C interface, but it's not backward +> compatible with the <0.6.0. ## Usage in Rust + Libcaesium exposes two functions, auto-detecting the input file type + ### Based on quality values + ```Rust pub fn compress( input_path: String, @@ -13,12 +20,15 @@ pub fn compress( parameters: &CSParameters ) -> Result<(), Box> ``` + #### Parameters + - `input_path` - input file path (full filename) - `output_path` - output file path (full filename) - `parameters` - options struct, containing compression parameters (see below) ### Based on output size + ```Rust pub fn compress_to_size( input_path: String, @@ -27,20 +37,26 @@ pub fn compress_to_size( max_output_size: usize, ) -> Result<(), Box> ``` + #### Parameters + - `input_path` - input file path (full filename) - `output_path` - output file path (full filename) - `parameters` - options struct, containing compression parameters (see below) - `max_output_size` - the maximum output size, in bytes -This function will attempt to compress the given file *below* the desired size. It will never exceed it. The function -will start looping until the best size under the desired is achieved. The function has a 2% tolerance for the output size. +This function will attempt to compress the given file *below* the desired size. It will never exceed it. The function +will start looping until the best size under the desired is achieved. The function has a 2% tolerance for the output +size. All quality value set to the parameters will be ignored and overwritten during the compression. NOTE: The output folder where the file is compressed **must** exist. + ### Compression options + Libcaesium supports a few compression parameters for each file it supports. They are defined into a top level struct containing each supported file parameters, as follows: + ```Rust pub struct CSParameters { pub jpeg: jpeg::Parameters, @@ -54,23 +70,36 @@ pub struct CSParameters { pub height: u32, } ``` + Each file type has its own options, but the last two are generic: + - `keep_metadata`: will keep metadata information for any supported type. JPEG and PNG supported. Default `false`. -- `optimize`: forces optimization, when available. With this option enabled the compression will be lossless. JPEG, PNG and WebP supported. Default `false`. -- `width`: Resizes the image to the given width. If this value is `0` and the height value is also `0`, no resizing will be done. If this is `0` and height is `> 0`, the image will be scaled based on height keeping the aspect ratio. Default `0`. -- `height`: Resizes the image to the given height. If this value is `0` and the width value is also `0`, no resizing will be done. If this is `0` and width is `> 0`, the image will be scaled based on width keeping the aspect ratio. Default `0`. +- `optimize`: forces optimization, when available. With this option enabled the compression will be lossless. JPEG, PNG + and WebP supported. Default `false`. +- `width`: Resizes the image to the given width. If this value is `0` and the height value is also `0`, no resizing will + be done. If this is `0` and height is `> 0`, the image will be scaled based on height keeping the aspect ratio. + Default `0`. +- `height`: Resizes the image to the given height. If this value is `0` and the width value is also `0`, no resizing + will be done. If this is `0` and width is `> 0`, the image will be scaled based on width keeping the aspect ratio. + Default `0`. #### jpeg + ```Rust pub struct Parameters { pub quality: u32, - pub chroma_subsampling: jpeg::ChromaSubsampling + pub chroma_subsampling: jpeg::ChromaSubsampling, + pub progressive: bool } ``` -- `quality`: in a range from 1 to 100, the quality of the resulting image. Default `80`. -- `chroma_subsampling`: [chroma subsampling](https://en.wikipedia.org/wiki/Chroma_subsampling) to apply during compression. Default `Auto`. + +- `quality`: in a range from 0 to 100, the quality of the resulting image. Default `80`. +- `chroma_subsampling`: [chroma subsampling](https://en.wikipedia.org/wiki/Chroma_subsampling) to apply during + compression. Default `Auto`. +- `progressive`: outputs a progressive image (recommended). Set to `false` for baseline. Default `true`. #### png + ```Rust pub struct Parameters { pub quality: u32, @@ -78,43 +107,62 @@ pub struct Parameters { pub optimization_level: u32 } ``` + - `quality`: in a range from 0 to 100, the quality of the resulting image. Default `80`. -- `force_zopfli`: if `optimization` is `true` and this option is also `true`, will use zopfli algorithm for compression, resulting in a smaller image, but it may take minutes to finish the process. Default `false`. +- `force_zopfli`: if `optimization` is `true` and this option is also `true`, will use zopfli algorithm for compression, + resulting in a smaller image, but it may take minutes to finish the process. Default `false`. - `optimization_level`: if `optimization` is `true` will set the level of oxipng optimization, from 0 to 6. Default `3`. #### gif -GIF support is experimental, has many know issues and does not support optimization. Expect bugs (especially on Windows). + +GIF support is experimental, has many know issues and does not support optimization. Expect bugs (especially on +Windows). + ```Rust pub struct Parameters { pub quality: u32, } ``` -- `quality`: in a range from 0 to 100, the quality of the resulting image. If the optimization flag is `true`, the level is set to `100`. Default: `80`. + +- `quality`: in a range from 0 to 100, the quality of the resulting image. If the optimization flag is `true`, the level + is set to `100`. Default: `80`. #### webp -WebP's compression is tricky. The format is already well optimized and using the `optimize` flag will probably result in a bigger image. + +WebP's compression is tricky. The format is already well optimized and using the `optimize` flag will probably result in +a bigger image. + ```Rust pub struct Parameters { pub quality: u32, } ``` -- `quality`: in a range from 0 to 100, the quality of the resulting image. If the optimization flag is `true`, this option will be ignored. Default: `60`. + +- `quality`: in a range from 0 to 100, the quality of the resulting image. If the optimization flag is `true`, this + option will be ignored. Default: `60`. #### tiff + Supported TIFF compression is only lossless. The supported algorithms are: Lzw, Deflate, Packbits, Uncompressed. + ```Rust pub struct Parameters { pub algorithm: TiffCompression, - pub deflate_level: DeflateLevel, + pub deflate_level: TiffDeflateLevel, } ``` + +- `algorithm`: supported algorithms are: Lzw, Deflate, Packbits, Uncompressed. - `deflate_level`: can be one of `Fast`, `Balanced`, `Best`. _________________ ## Usage in C + Libcaesium exposes two C functions, auto-detecting the input file type: + ### Based on quality values + ```Rust pub unsafe extern "C" fn c_compress( input_path: *const c_char, @@ -122,12 +170,17 @@ pub unsafe extern "C" fn c_compress( params: CCSParameters ) -> CCSResult ``` + #### Parameters + - `input_path` - input file path (full filename) - `output_path` - output file path (full filename) - `parameters` - options struct, containing compression parameters (see below) + #### Return + A `CCSResult` struct + ```Rust #[repr(C)] pub struct CCSResult { @@ -135,10 +188,12 @@ pub struct CCSResult { pub error_message: *const c_char, } ``` + If `success` is `true` the compression process ended successfully and `error_message` will be empty. On failure, the `error_message` will be filled with a string containing a brief explanation of the error. ### Based on output size + ```Rust pub unsafe extern "C" fn c_compress_to_size( input_path: *const c_char, @@ -147,13 +202,18 @@ pub unsafe extern "C" fn c_compress_to_size( max_output_size: usize, ) -> CCSResult ``` + #### Parameters + - `input_path` - input file path (full filename) - `output_path` - output file path (full filename) - `parameters` - options struct, containing compression parameters (see below) - `max_output_size` - the maximum output size, in bytes + #### Return + A `CCSResult` struct + ```Rust #[repr(C)] pub struct CCSResult { @@ -161,11 +221,14 @@ pub struct CCSResult { pub error_message: *const c_char, } ``` + If `success` is `true` the compression process ended successfully and `error_message` will be empty. On failure, the `error_message` will be filled with a string containing a brief explanation of the error. ### Compression options + The C options struct is slightly different from the Rust one: + ```Rust #[repr(C)] pub struct CCSParameters { @@ -184,28 +247,40 @@ pub struct CCSParameters { pub height: u32, } ``` + The option description is the same as the Rust counterpart. -Valid values for `jpeg_chroma_subsampling` are [444, 422, 420, 411]. Any other value will be ignored and will be used the default option. -Valid values for `tiff_compression` are [0 (Uncompressed), 1 (Lzw), 2 (Deflate), 3 (Packbits)]. Any other value will be ignored and `0` will be used. -Valid values for `tiff_deflate_level` are [3 (Fast), 6 (Balanced), 9 (Best)]. Any other value will be ignored and `Best` will be used. +Valid values for `jpeg_chroma_subsampling` are [444, 422, 420, 411]. Any other value will be ignored and will be used +the default option. +Valid values for `tiff_compression` are [0 (Uncompressed), 1 (Lzw), 2 (Deflate), 3 (Packbits)]. Any other value will be +ignored and `0` will be used. +Valid values for `tiff_deflate_level` are [1 (Fast), 6 (Balanced), 9 (Best)]. Any other value will be ignored and `Best` +will be used. ## Download + Binaries not available. Please refer to the compilation section below. ## Compilation and Installation + Compilation is available for all supported platforms: Windows, macOS and Linux. ``` cargo build --release ``` -Note: if you don't use the `--release` flag, the PNG optimizations can take a very long time to complete, especially using the zopfli algorithm. + +Note: if you don't use the `--release` flag, the PNG optimizations can take a very long time to complete, especially +using the zopfli algorithm. The result will be a dynamic library usable by external applications through its C interface. ## Compression vs Optimization -JPEG is a lossy format: that means you will always lose some information after each compression. So, compressing a file with + +JPEG is a lossy format: that means you will always lose some information after each compression. So, compressing a file +with 100 quality for 10 times will result in an always different image, even though you can't really see the difference. -Libcaesium also supports optimization, by setting the _quality_ to 0. This performs a lossless process, resulting in the same image, +Libcaesium also supports optimization, by setting the _quality_ to 0. This performs a lossless process, resulting in the +same image, but with a smaller size (10-12% usually). GIF optimization is possible, but currently not supported. -WebP's optimization is also possible, but it will probably result in a bigger output file as it's well suited to losslessly convert from PNG or JPEG. +WebP's optimization is also possible, but it will probably result in a bigger output file as it's well suited to +losslessly convert from PNG or JPEG. diff --git a/src/interface.rs b/src/interface.rs index ebdb7a7..dc516e4 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -1,9 +1,7 @@ use std::ffi::{CStr, CString}; use std::os::raw::c_char; -use tiff::encoder::compression::DeflateLevel::{Balanced, Best, Fast}; - -use crate::{ChromaSubsampling, compress, compress_to_size, convert, CSParameters, error, initialize_parameters, SupportedFileTypes}; +use crate::{ChromaSubsampling, compress, compress_to_size, convert, CSParameters, error, initialize_parameters, SupportedFileTypes, TiffDeflateLevel}; use crate::TiffCompression::{Deflate, Lzw, Packbits, Uncompressed}; #[repr(C)] @@ -11,6 +9,7 @@ pub struct CCSParameters { pub keep_metadata: bool, pub jpeg_quality: u32, pub jpeg_chroma_subsampling: u32, + pub jpeg_progressive: bool, pub png_quality: u32, pub png_optimization_level: u32, pub png_force_zopfli: bool, @@ -114,6 +113,7 @@ fn c_set_parameters(params: CCSParameters) -> CSParameters { let mut parameters = initialize_parameters(); parameters.jpeg.quality = params.jpeg_quality; + parameters.jpeg.progressive = params.jpeg_progressive; parameters.png.quality = params.png_quality; parameters.optimize = params.optimize; parameters.keep_metadata = params.keep_metadata; @@ -140,9 +140,9 @@ fn c_set_parameters(params: CCSParameters) -> CSParameters { }; parameters.tiff.deflate_level = match params.tiff_deflate_level { - 3 => Fast, - 6 => Balanced, - _ => Best + 1 => TiffDeflateLevel::Fast, + 6 => TiffDeflateLevel::Balanced, + _ => TiffDeflateLevel::Best }; parameters diff --git a/src/jpeg.rs b/src/jpeg.rs index bddee18..1a377ca 100644 --- a/src/jpeg.rs +++ b/src/jpeg.rs @@ -3,7 +3,7 @@ use std::fs::File; use std::io::Write; use std::mem; use std::panic::catch_unwind; - +use std::ptr::null; use image::ImageFormat::Jpeg; use img_parts::{ImageEXIF, ImageICC}; use img_parts::jpeg::Jpeg as PartsJpeg; @@ -201,6 +201,10 @@ unsafe fn lossy(in_file: Vec, parameters: &CSParameters) -> Result, false as boolean, ); + if !parameters.jpeg.progressive { + dst_info.scan_info = null(); + } + jpeg_start_compress(&mut dst_info, true as boolean); if parameters.keep_metadata { diff --git a/src/lib.rs b/src/lib.rs index cf8eab8..c02f8ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,6 @@ use std::{cmp, fs}; use std::fs::File; use std::io::Write; -use ::tiff::encoder::compression::DeflateLevel; -use ::tiff::encoder::compression::DeflateLevel::Best; - use error::CaesiumError; use crate::TiffCompression::{Deflate, Lzw, Packbits}; @@ -45,10 +42,18 @@ pub enum TiffCompression { Packbits = 3, } +#[derive(Copy, Clone, PartialEq)] +pub enum TiffDeflateLevel { + Fast = 1, + Balanced = 6, + Best = 9, +} + #[derive(Copy, Clone)] pub struct JpegParameters { pub quality: u32, pub chroma_subsampling: ChromaSubsampling, + pub progressive: bool } #[derive(Copy, Clone)] @@ -71,7 +76,7 @@ pub struct WebPParameters { #[derive(Copy, Clone)] pub struct TiffParameters { pub algorithm: TiffCompression, - pub deflate_level: DeflateLevel, + pub deflate_level: TiffDeflateLevel, } #[derive(Copy, Clone)] @@ -92,6 +97,7 @@ pub fn initialize_parameters() -> CSParameters { let jpeg = JpegParameters { quality: 80, chroma_subsampling: ChromaSubsampling::Auto, + progressive: true }; let png = PngParameters { quality: 80, @@ -102,7 +108,7 @@ pub fn initialize_parameters() -> CSParameters { let webp = WebPParameters { quality: 80 }; let tiff = TiffParameters { algorithm: Deflate, - deflate_level: DeflateLevel::Balanced, + deflate_level: TiffDeflateLevel::Balanced, }; CSParameters { @@ -207,7 +213,7 @@ pub fn compress_to_size_in_memory( Lzw, Packbits ]; - parameters.tiff.deflate_level = Best; + parameters.tiff.deflate_level = TiffDeflateLevel::Best; parameters.tiff.algorithm = Deflate; let mut smallest_result = tiff::compress_in_memory(in_file.clone(), parameters)?; //TODO clone for tc in algorithms { @@ -367,7 +373,7 @@ pub fn convert_in_memory(in_file: Vec, parameters: &CSParameters, format: Su } fn validate_parameters(parameters: &CSParameters) -> error::Result<()> { - if parameters.jpeg.quality == 0 || parameters.jpeg.quality > 100 { + if parameters.jpeg.quality > 100 { return Err(CaesiumError { message: "Invalid JPEG quality value".into(), code: 10001, diff --git a/src/tiff.rs b/src/tiff.rs index ede1e54..c0da4b3 100644 --- a/src/tiff.rs +++ b/src/tiff.rs @@ -4,10 +4,10 @@ use std::panic; use image::ImageFormat::Tiff; use tiff::encoder::colortype::{RGB8, RGBA8}; -use tiff::encoder::compression::{Deflate, Lzw, Packbits, Uncompressed}; +use tiff::encoder::compression::{Deflate, DeflateLevel, Lzw, Packbits, Uncompressed}; use tiff::encoder::TiffEncoder; -use crate::{CSParameters, TiffCompression}; +use crate::{CSParameters, TiffCompression, TiffDeflateLevel}; use crate::error::CaesiumError; use crate::resize::resize_image; @@ -87,13 +87,13 @@ pub fn compress_in_memory( image::ColorType::Rgb8 => encoder.write_image_with_compression::( image.width(), image.height(), - Deflate::with_level(parameters.tiff.deflate_level), + Deflate::with_level(parse_deflate_level(parameters.tiff.deflate_level)), image.as_bytes(), ), image::ColorType::Rgba8 => encoder.write_image_with_compression::( image.width(), image.height(), - Deflate::with_level(parameters.tiff.deflate_level), + Deflate::with_level(parse_deflate_level(parameters.tiff.deflate_level)), image.as_bytes(), ), _ => { @@ -174,3 +174,11 @@ pub fn compress_in_memory( }), } } + +fn parse_deflate_level(level: TiffDeflateLevel) -> DeflateLevel { + match level { + TiffDeflateLevel::Fast => DeflateLevel::Fast, + TiffDeflateLevel::Best => DeflateLevel::Best, + TiffDeflateLevel::Balanced => DeflateLevel::Balanced, + } +} diff --git a/tests/tiff.rs b/tests/tiff.rs index e43e549..9980c8e 100644 --- a/tests/tiff.rs +++ b/tests/tiff.rs @@ -1,5 +1,4 @@ use std::sync::Once; -use tiff::encoder::compression::DeflateLevel::Balanced; use crate::cleanup::remove_compressed_test_file; mod cleanup; @@ -58,7 +57,7 @@ fn rgb8_deflate() { initialize(output); let mut params = caesium::initialize_parameters(); params.tiff.algorithm = caesium::TiffCompression::Deflate; - params.tiff.deflate_level = Balanced; + params.tiff.deflate_level = caesium::TiffDeflateLevel::Balanced; caesium::compress( String::from("tests/samples/rgb8.tif"), String::from(output), @@ -79,7 +78,7 @@ fn rgba8_deflate() { initialize(output); let mut params = caesium::initialize_parameters(); params.tiff.algorithm = caesium::TiffCompression::Deflate; - params.tiff.deflate_level = Balanced; + params.tiff.deflate_level = caesium::TiffDeflateLevel::Balanced; caesium::compress( String::from("tests/samples/rgba8.tif"), String::from(output),