From 0d99042cbf79815f8dbe65f5e36ac14e50388007 Mon Sep 17 00:00:00 2001 From: Matteo Paonessa Date: Fri, 21 Feb 2025 14:11:55 +0100 Subject: [PATCH] Fix tiff compression to size --- .github/workflows/clippy.yml | 21 +++++++++ .github/workflows/fmt.yml | 27 ++++++++++++ Cargo.toml | 2 +- examples/convert.rs | 7 +-- tests/rustfmt.toml => rustfmt.toml | 0 src/convert.rs | 13 +++--- src/gif.rs | 12 +----- src/interface.rs | 4 +- src/jpeg.rs | 49 +++++---------------- src/lib.rs | 44 +++++++------------ src/png.rs | 53 +++++++---------------- src/resize.rs | 17 +++----- src/tiff.rs | 69 +++++++++++------------------- src/webp.rs | 55 ++++++++---------------- tests/tiff.rs | 4 +- 15 files changed, 152 insertions(+), 225 deletions(-) create mode 100644 .github/workflows/clippy.yml create mode 100644 .github/workflows/fmt.yml rename tests/rustfmt.toml => rustfmt.toml (100%) diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml new file mode 100644 index 0000000..9d58a3a --- /dev/null +++ b/.github/workflows/clippy.yml @@ -0,0 +1,21 @@ +name: Clippy check +on: + push: + paths: + - 'src/**' + - '.github/**' + pull_request: + paths: + - 'src/**' + - '.github/**' + +env: + RUSTFLAGS: "-Dwarnings" + +jobs: + clippy_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run Clippy + run: cargo clippy --all-targets --all-features diff --git a/.github/workflows/fmt.yml b/.github/workflows/fmt.yml new file mode 100644 index 0000000..bf43eea --- /dev/null +++ b/.github/workflows/fmt.yml @@ -0,0 +1,27 @@ +name: Code formatting + +on: + push: + paths: + - 'src/**' + - '.github/**' + pull_request: + paths: + - 'src/**' + - '.github/**' + +jobs: + fmt_check: + name: Fmt + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + + - name: Check code formatting + run: cargo fmt -- --check \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 6f47a58..d7bad9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ oxipng = { version = "9.0", default-features = false, features = ["filetime", "z libc = "0.2" gifsicle = { version = "1.95", optional = true } webp = { version = "0.3.0", optional = true } -infer = "0.16.0" +infer = "0.19.0" image = { version = "0.25.1", default-features = false } img-parts = "0.3.3" bytes = "1.7" diff --git a/examples/convert.rs b/examples/convert.rs index 5cd074c..c8a93a5 100644 --- a/examples/convert.rs +++ b/examples/convert.rs @@ -13,12 +13,7 @@ fn main() -> ExitCode { parameters.keep_metadata = true; parameters.webp.quality = 60; - match convert( - input, - output, - ¶meters, - caesium::SupportedFileTypes::WebP, - ) { + match convert(input, output, ¶meters, caesium::SupportedFileTypes::WebP) { Ok(_) => ExitCode::SUCCESS, Err(e) => { eprintln!("{}", e); diff --git a/tests/rustfmt.toml b/rustfmt.toml similarity index 100% rename from tests/rustfmt.toml rename to rustfmt.toml diff --git a/src/convert.rs b/src/convert.rs index 60eca6f..bfa6a59 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -79,15 +79,14 @@ pub fn convert_in_memory( code: 10404, })?; - let compressed_converted_image = - compress_in_memory(output_image, parameters).map_err(|e| CaesiumError { - message: e.to_string(), - code: 10405, - })?; + let compressed_converted_image = compress_in_memory(output_image, parameters).map_err(|e| CaesiumError { + message: e.to_string(), + code: 10405, + })?; if parameters.keep_metadata { - let dyn_image = DynImage::from_bytes(Bytes::from(compressed_converted_image.clone())) - .map_err(|e| CaesiumError { + let dyn_image = + DynImage::from_bytes(Bytes::from(compressed_converted_image.clone())).map_err(|e| CaesiumError { message: e.to_string(), code: 10408, })?; diff --git a/src/gif.rs b/src/gif.rs index f452404..b887def 100644 --- a/src/gif.rs +++ b/src/gif.rs @@ -4,11 +4,7 @@ use std::os::raw::{c_int, c_void}; use crate::error::CaesiumError; use crate::CSParameters; -pub fn compress( - input_path: String, - output_path: String, - parameters: &CSParameters, -) -> Result<(), CaesiumError> { +pub fn compress(input_path: String, output_path: String, parameters: &CSParameters) -> Result<(), CaesiumError> { if parameters.width > 0 || parameters.height > 0 { return Err(CaesiumError { message: "GIF resizing is not supported".to_string(), @@ -58,11 +54,7 @@ fn lossless(input_path: String, output_path: String) -> Result<(), CaesiumError> } } -pub fn lossy( - input_path: String, - output_path: String, - parameters: &CSParameters, -) -> Result<(), CaesiumError> { +pub fn lossy(input_path: String, output_path: String, parameters: &CSParameters) -> Result<(), CaesiumError> { unsafe { let input_file = libc::fopen( CString::new(input_path) diff --git a/src/interface.rs b/src/interface.rs index 5e16d4a..a735c59 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -3,9 +3,7 @@ use std::os::raw::c_char; use crate::parameters::ChromaSubsampling; use crate::parameters::TiffCompression::{Deflate, Lzw, Packbits, Uncompressed}; -use crate::{ - compress, compress_to_size, convert, error, CSParameters, SupportedFileTypes, TiffDeflateLevel, -}; +use crate::{compress, compress_to_size, convert, error, CSParameters, SupportedFileTypes, TiffDeflateLevel}; #[repr(C)] pub struct CCSParameters { diff --git a/src/jpeg.rs b/src/jpeg.rs index ade76e7..347615c 100644 --- a/src/jpeg.rs +++ b/src/jpeg.rs @@ -18,11 +18,7 @@ use crate::CSParameters; static JPEG_ERROR: AtomicI32 = AtomicI32::new(0); -pub fn compress( - input_path: String, - output_path: String, - parameters: &CSParameters, -) -> Result<(), CaesiumError> { +pub fn compress(input_path: String, output_path: String, parameters: &CSParameters) -> Result<(), CaesiumError> { let in_file = fs::read(input_path).map_err(|e| CaesiumError { message: e.to_string(), code: 20100, @@ -40,10 +36,7 @@ pub fn compress( Ok(()) } -pub fn compress_in_memory( - mut in_file: Vec, - parameters: &CSParameters, -) -> Result, CaesiumError> { +pub fn compress_in_memory(mut in_file: Vec, parameters: &CSParameters) -> Result, CaesiumError> { if parameters.width > 0 || parameters.height > 0 { if parameters.keep_metadata { let metadata = extract_metadata(in_file.clone()); @@ -200,11 +193,7 @@ unsafe fn lossy(in_file: Vec, parameters: &CSParameters) -> Result, let row_stride = dst_info.image_width as usize * dst_info.input_components as usize; dst_info.dct_method = J_DCT_METHOD::JDCT_ISLOW; dst_info.optimize_coding = i32::from(true); - jpeg_set_quality( - &mut dst_info, - parameters.jpeg.quality as i32, - false as boolean, - ); + jpeg_set_quality(&mut dst_info, parameters.jpeg.quality as i32, false as boolean); if !parameters.jpeg.progressive { dst_info.scan_info = null(); @@ -244,17 +233,12 @@ fn extract_metadata(image: Vec) -> (Option, Option, - iccp: Option, - exif: Option, -) -> Vec { +fn save_metadata(image_buffer: Vec, iccp: Option, exif: Option) -> Vec { if iccp.is_some() || exif.is_some() { - let mut dyn_image = - match PartsJpeg::from_bytes(img_parts::Bytes::from(image_buffer.clone())) { - Ok(d) => d, - Err(_) => return image_buffer, - }; + let mut dyn_image = match PartsJpeg::from_bytes(img_parts::Bytes::from(image_buffer.clone())) { + Ok(d) => d, + Err(_) => return image_buffer, + }; dyn_image.set_icc_profile(iccp); dyn_image.set_exif(exif); @@ -269,27 +253,16 @@ fn save_metadata( } } -unsafe fn write_metadata( - src_info: &mut jpeg_decompress_struct, - dst_info: &mut jpeg_compress_struct, -) { +unsafe fn write_metadata(src_info: &mut jpeg_decompress_struct, dst_info: &mut jpeg_compress_struct) { let mut marker = src_info.marker_list; while !marker.is_null() { - jpeg_write_marker( - dst_info, - (*marker).marker as i32, - (*marker).data, - (*marker).data_length, - ); + jpeg_write_marker(dst_info, (*marker).marker as i32, (*marker).data, (*marker).data_length); marker = (*marker).next; } } -unsafe fn set_chroma_subsampling( - subsampling: ChromaSubsampling, - dst_info: &mut jpeg_compress_struct, -) { +unsafe fn set_chroma_subsampling(subsampling: ChromaSubsampling, dst_info: &mut jpeg_compress_struct) { (*dst_info.comp_info.add(1)).h_samp_factor = 1; (*dst_info.comp_info.add(1)).v_samp_factor = 1; (*dst_info.comp_info.add(2)).h_samp_factor = 1; diff --git a/src/lib.rs b/src/lib.rs index 6892a4a..e9ac670 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,11 +37,7 @@ mod webp; /// # Returns /// /// * `Result<(), CaesiumError>` - Returns `Ok(())` if compression is successful, otherwise returns a `CaesiumError`. -pub fn compress( - input_path: String, - output_path: String, - parameters: &CSParameters, -) -> error::Result<()> { +pub fn compress(input_path: String, output_path: String, parameters: &CSParameters) -> error::Result<()> { validate_parameters(parameters)?; let file_type = get_filetype_from_path(&input_path); @@ -151,7 +147,7 @@ pub fn compress_to_size_in_memory( smallest_result = result; } } - return if return_smallest { + return if return_smallest || smallest_result.len() <= max_output_size { Ok(smallest_result) } else { Err(CaesiumError { @@ -194,9 +190,7 @@ pub fn compress_to_size_in_memory( let compressed_file_size = compressed_file.len(); - if compressed_file_size <= max_output_size - && max_output_size - compressed_file_size < tolerance - { + if compressed_file_size <= max_output_size && max_output_size - compressed_file_size < tolerance { break compressed_file; } @@ -264,18 +258,15 @@ pub fn compress_to_size( return Ok(()); } - let compressed_file = - compress_to_size_in_memory(in_file, parameters, max_output_size, return_smallest)?; + let compressed_file = compress_to_size_in_memory(in_file, parameters, max_output_size, return_smallest)?; let mut out_file = File::create(output_path).map_err(|e| CaesiumError { message: e.to_string(), code: 10203, })?; - out_file - .write_all(&compressed_file) - .map_err(|e| CaesiumError { - message: e.to_string(), - code: 10204, - })?; + out_file.write_all(&compressed_file).map_err(|e| CaesiumError { + message: e.to_string(), + code: 10204, + })?; Ok(()) } @@ -311,23 +302,20 @@ pub fn convert( message: e.to_string(), code: 10410, })?; - let output_buffer = - convert_in_memory(in_file, parameters, format).map_err(|e| CaesiumError { - message: e.to_string(), - code: 10411, - })?; + let output_buffer = convert_in_memory(in_file, parameters, format).map_err(|e| CaesiumError { + message: e.to_string(), + code: 10411, + })?; let mut out_file = File::create(output_path).map_err(|e| CaesiumError { message: e.to_string(), code: 10412, })?; - out_file - .write_all(&output_buffer) - .map_err(|e| CaesiumError { - message: e.to_string(), - code: 10413, - })?; + out_file.write_all(&output_buffer).map_err(|e| CaesiumError { + message: e.to_string(), + code: 10413, + })?; Ok(()) } diff --git a/src/png.rs b/src/png.rs index 2f94418..a9cb8ed 100644 --- a/src/png.rs +++ b/src/png.rs @@ -10,23 +10,14 @@ use crate::error::CaesiumError; use crate::resize::resize; use crate::CSParameters; -pub fn compress( - input_path: String, - output_path: String, - parameters: &CSParameters, -) -> Result<(), CaesiumError> { +pub fn compress(input_path: String, output_path: String, parameters: &CSParameters) -> Result<(), CaesiumError> { let mut in_file = fs::read(input_path).map_err(|e| CaesiumError { message: e.to_string(), code: 20200, })?; if parameters.width > 0 || parameters.height > 0 { - in_file = resize( - in_file, - parameters.width, - parameters.height, - ImageFormat::Png, - )?; + in_file = resize(in_file, parameters.width, parameters.height, ImageFormat::Png)?; } let optimized_png = compress_in_memory(in_file, parameters)?; @@ -44,17 +35,9 @@ pub fn compress( Ok(()) } -pub fn compress_in_memory( - in_file: Vec, - parameters: &CSParameters, -) -> Result, CaesiumError> { +pub fn compress_in_memory(in_file: Vec, parameters: &CSParameters) -> Result, CaesiumError> { let input = if parameters.width > 0 || parameters.height > 0 { - resize( - in_file, - parameters.width, - parameters.height, - ImageFormat::Png, - )? + resize(in_file, parameters.width, parameters.height, ImageFormat::Png)? } else { in_file }; @@ -98,20 +81,16 @@ fn lossy(in_file: Vec, parameters: &CSParameters) -> Result, Caesium code: 20207, })?; - let (palette, pixels) = quantization - .remapped(&mut liq_image) - .map_err(|e| CaesiumError { - message: e.to_string(), - code: 20208, - })?; + let (palette, pixels) = quantization.remapped(&mut liq_image).map_err(|e| CaesiumError { + message: e.to_string(), + code: 20208, + })?; let mut encoder = lodepng::Encoder::new(); - encoder - .set_palette(palette.as_slice()) - .map_err(|e| CaesiumError { - message: e.to_string(), - code: 20212, - })?; + encoder.set_palette(palette.as_slice()).map_err(|e| CaesiumError { + message: e.to_string(), + code: 20212, + })?; let png_vec = encoder .encode(pixels.as_slice(), rgba_bitmap.width, rgba_bitmap.height) .map_err(|e| CaesiumError { @@ -142,11 +121,9 @@ fn lossless(in_file: Vec, parameters: &CSParameters) -> Result, Caes } let optimized_png = - oxipng::optimize_from_memory(in_file.as_slice(), &oxipng_options).map_err(|e| { - CaesiumError { - message: e.to_string(), - code: 20210, - } + oxipng::optimize_from_memory(in_file.as_slice(), &oxipng_options).map_err(|e| CaesiumError { + message: e.to_string(), + code: 20210, })?; Ok(optimized_png) diff --git a/src/resize.rs b/src/resize.rs index 5815df1..64cd117 100644 --- a/src/resize.rs +++ b/src/resize.rs @@ -18,10 +18,10 @@ pub fn resize( let orientation = get_jpeg_orientation(buffer_slice); (desired_width, desired_height) = match orientation { 5..=8 => (height, width), - _ => (width, height) + _ => (width, height), }; } - + let mut image = ImageReader::new(Cursor::new(image_buffer)) .with_guessed_format() .map_err(|e| CaesiumError { @@ -34,8 +34,7 @@ pub fn resize( code: 10301, })?; - let dimensions = - compute_dimensions(image.width(), image.height(), desired_width, desired_height); + let dimensions = compute_dimensions(image.width(), image.height(), desired_width, desired_height); image = image.resize_exact(dimensions.0, dimensions.1, FilterType::Lanczos3); let mut resized_file: Vec = vec![]; @@ -106,10 +105,7 @@ fn downscale_on_width() { let original_width = 800; let original_height = 600; - assert_eq!( - compute_dimensions(original_width, original_height, 750, 0), - (750, 563) - ) + assert_eq!(compute_dimensions(original_width, original_height, 750, 0), (750, 563)) } #[test] @@ -117,8 +113,5 @@ fn downscale_on_height() { let original_width = 800; let original_height = 600; - assert_eq!( - compute_dimensions(original_width, original_height, 0, 478), - (637, 478) - ) + assert_eq!(compute_dimensions(original_width, original_height, 0, 478), (637, 478)) } diff --git a/src/tiff.rs b/src/tiff.rs index 0d3e8a1..b68ce64 100644 --- a/src/tiff.rs +++ b/src/tiff.rs @@ -12,23 +12,17 @@ use crate::parameters::TiffCompression; use crate::resize::resize_image; use crate::{CSParameters, TiffDeflateLevel}; -pub fn compress( - input_path: String, - output_path: String, - parameters: &CSParameters, -) -> Result<(), CaesiumError> { +pub fn compress(input_path: String, output_path: String, parameters: &CSParameters) -> Result<(), CaesiumError> { let mut input_file = File::open(input_path).map_err(|e| CaesiumError { message: e.to_string(), code: 20500, })?; let mut input_data = Vec::new(); - input_file - .read_to_end(&mut input_data) - .map_err(|e| CaesiumError { - message: e.to_string(), - code: 20501, - })?; + input_file.read_to_end(&mut input_data).map_err(|e| CaesiumError { + message: e.to_string(), + code: 20501, + })?; let compressed_image = compress_in_memory(input_data, parameters)?; @@ -37,30 +31,23 @@ pub fn compress( code: 20502, })?; - output_file - .write_all(&compressed_image) - .map_err(|e| CaesiumError { - message: e.to_string(), - code: 20503, - })?; + output_file.write_all(&compressed_image).map_err(|e| CaesiumError { + message: e.to_string(), + code: 20503, + })?; Ok(()) } -pub fn compress_in_memory( - in_file: Vec, - parameters: &CSParameters, -) -> Result, CaesiumError> { - let decoding_result = - match panic::catch_unwind(|| image::load_from_memory_with_format(in_file.as_slice(), Tiff)) - { - Ok(i) => i, - Err(_) => { - return Err(CaesiumError { - message: "Failed to decode TIFF image".to_string(), - code: 20504, - }); - } - }; +pub fn compress_in_memory(in_file: Vec, parameters: &CSParameters) -> Result, CaesiumError> { + let decoding_result = match panic::catch_unwind(|| image::load_from_memory_with_format(in_file.as_slice(), Tiff)) { + Ok(i) => i, + Err(_) => { + return Err(CaesiumError { + message: "Failed to decode TIFF image".to_string(), + code: 20504, + }); + } + }; let mut image = match decoding_result { Ok(i) => i, Err(e) => { @@ -106,18 +93,12 @@ pub fn compress_in_memory( }, TiffCompression::Lzw => match color_type { - image::ColorType::Rgb8 => encoder.write_image_with_compression::( - image.width(), - image.height(), - Lzw, - image.as_bytes(), - ), - image::ColorType::Rgba8 => encoder.write_image_with_compression::( - image.width(), - image.height(), - Lzw, - image.as_bytes(), - ), + image::ColorType::Rgb8 => { + encoder.write_image_with_compression::(image.width(), image.height(), Lzw, image.as_bytes()) + } + image::ColorType::Rgba8 => { + encoder.write_image_with_compression::(image.width(), image.height(), Lzw, image.as_bytes()) + } _ => { return Err(CaesiumError { message: format!("Unsupported TIFF color type ({:?})", color_type).to_string(), diff --git a/src/webp.rs b/src/webp.rs index 8158e98..c82ac3c 100644 --- a/src/webp.rs +++ b/src/webp.rs @@ -12,23 +12,17 @@ use crate::error::CaesiumError; use crate::resize::resize_image; use crate::CSParameters; -pub fn compress( - input_path: String, - output_path: String, - parameters: &CSParameters, -) -> Result<(), CaesiumError> { +pub fn compress(input_path: String, output_path: String, parameters: &CSParameters) -> Result<(), CaesiumError> { let mut input_file = File::open(input_path).map_err(|e| CaesiumError { message: e.to_string(), code: 20300, })?; let mut input_data = Vec::new(); - input_file - .read_to_end(&mut input_data) - .map_err(|e| CaesiumError { - message: e.to_string(), - code: 20301, - })?; + input_file.read_to_end(&mut input_data).map_err(|e| CaesiumError { + message: e.to_string(), + code: 20301, + })?; let compressed_image = compress_in_memory(input_data, parameters)?; @@ -37,19 +31,14 @@ pub fn compress( code: 20302, })?; - output_file - .write_all(&compressed_image) - .map_err(|e| CaesiumError { - message: e.to_string(), - code: 20303, - })?; + output_file.write_all(&compressed_image).map_err(|e| CaesiumError { + message: e.to_string(), + code: 20303, + })?; Ok(()) } -pub fn compress_in_memory( - in_file: Vec, - parameters: &CSParameters, -) -> Result, CaesiumError> { +pub fn compress_in_memory(in_file: Vec, parameters: &CSParameters) -> Result, CaesiumError> { let mut iccp: Option = None; let mut exif: Option = None; @@ -59,9 +48,7 @@ pub fn compress_in_memory( message: e.to_string(), code: 20306, })? - .map_or((None, None), |dyn_img| { - (dyn_img.icc_profile(), dyn_img.exif()) - }); + .map_or((None, None), |dyn_img| (dyn_img.icc_profile(), dyn_img.exif())); } let must_resize = parameters.width > 0 || parameters.height > 0; @@ -120,12 +107,10 @@ pub fn compress_in_memory( if must_resize { if images_data.get(i).is_some() { encoder.add_frame( - AnimFrame::from_image(images_data.get(i).unwrap(), last_ms).map_err( - |e| CaesiumError { - message: e.to_string(), - code: 20310, - }, - )?, + AnimFrame::from_image(images_data.get(i).unwrap(), last_ms).map_err(|e| CaesiumError { + message: e.to_string(), + code: 20310, + })?, ); } } else { @@ -206,14 +191,12 @@ fn to_rgba(value: u32) -> [u8; 4] { fn to_dynamic_image(frame: AnimFrame) -> DynamicImage { if frame.get_layout().is_alpha() { - let image = - ImageBuffer::from_raw(frame.width(), frame.height(), frame.get_image().to_owned()) - .expect("ImageBuffer couldn't be created"); + let image = ImageBuffer::from_raw(frame.width(), frame.height(), frame.get_image().to_owned()) + .expect("ImageBuffer couldn't be created"); DynamicImage::ImageRgba8(image) } else { - let image = - ImageBuffer::from_raw(frame.width(), frame.height(), frame.get_image().to_owned()) - .expect("ImageBuffer couldn't be created"); + let image = ImageBuffer::from_raw(frame.width(), frame.height(), frame.get_image().to_owned()) + .expect("ImageBuffer couldn't be created"); DynamicImage::ImageRgb8(image) } } diff --git a/tests/tiff.rs b/tests/tiff.rs index d314c7e..141e335 100644 --- a/tests/tiff.rs +++ b/tests/tiff.rs @@ -126,7 +126,7 @@ fn rgb8_downscale() { #[test] fn rgb8_downscale_to_size() { - let max_output_size = 10_000; + let max_output_size = 100_000; let output = "tests/samples/output/downscale_rgb8_to_size.tif"; initialize(output); let mut params = CSParameters::new(); @@ -138,7 +138,7 @@ fn rgb8_downscale_to_size() { String::from(output), &mut params, max_output_size, - false, + true, ) .unwrap(); assert!(std::path::Path::new(output).exists());