From 744090fee916b05bb8800b80f661a952ff7bd458 Mon Sep 17 00:00:00 2001 From: Matteo Paonessa Date: Sat, 12 Feb 2022 14:10:12 +0100 Subject: [PATCH] Resizing --- Cargo.toml | 8 +++-- README.md | 10 ++++-- src/gif.rs | 15 +++++---- src/jpeg.rs | 79 +++++++++++++++++++++++++++++++++++---------- src/lib.rs | 76 +++++++++++++++++++++++++------------------ src/png.rs | 32 +++++++++++++----- src/resize.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++++ src/webp.rs | 16 +++++++-- tests/gif.rs | 3 +- tests/jpeg.rs | 49 +++++++++++++++++++++++++++- tests/metadata.rs | 46 +++++++++++++++++++------- tests/png.rs | 62 ++++++++++++++++++++++++++++++++++- tests/webp.rs | 41 ++++++++++++++++++++++++ 13 files changed, 433 insertions(+), 86 deletions(-) create mode 100644 src/resize.rs diff --git a/Cargo.toml b/Cargo.toml index 6c76de8..cb244be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,12 +23,14 @@ license = "Apache-2.0" [dependencies] mozjpeg-sys = "1.0.1" -oxipng = { version = "5.0.1", optional = false } +oxipng = "5.0.1" libc = "0.2.76" -wasm-bindgen = "0.2" gifsicle = "1.92.5" webp = "0.2.0" -infer = "0.5.0" +infer = "0.6.0" +image = { version = "0.24", default-features = false, features = ["jpeg", "png", "webp", "gif"] } +img-parts = "0.2.3" +bytes = "1.1.0" [dev-dependencies] dssim = "2.11.2" diff --git a/README.md b/README.md index 8a10cc2..53b8a0d 100644 --- a/README.md +++ b/README.md @@ -29,11 +29,15 @@ pub struct CSParameters { pub webp: webp::Parameters, pub keep_metadata: bool, pub optimize: bool, + pub width: u32, + 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`. #### jpeg ```Rust @@ -79,7 +83,7 @@ Libcaesium exposes one single C function, auto-detecting the input file type: pub extern fn c_compress( input_path: *const c_char, output_path: *const c_char, - params: C_CSParameters + params: CCSParameters ) -> bool ``` #### Parameters @@ -93,7 +97,7 @@ pub extern fn c_compress( The C options struct is slightly different from the Rust one: ```Rust #[repr(C)] -pub struct C_CSParameters { +pub struct CCSParameters { pub keep_metadata: bool, pub jpeg_quality: u32, pub png_level: u32, @@ -101,6 +105,8 @@ pub struct C_CSParameters { pub gif_quality: u32, pub webp_quality: u32, pub optimize: bool, + pub width: u32, + pub height: u32, } ``` The option description is the same as the Rust counterpart. diff --git a/src/gif.rs b/src/gif.rs index 51ba465..b3a0d1c 100644 --- a/src/gif.rs +++ b/src/gif.rs @@ -9,11 +9,15 @@ pub struct Parameters { pub fn compress(input_path: String, output_path: String, parameters: CSParameters) -> Result<(), io::Error> { - return if parameters.optimize { + if parameters.width > 0 || parameters.height > 0 { + return Err(io::Error::new(io::ErrorKind::Other, "GIF resizing is not supported")); + } + + if parameters.optimize { lossless(input_path, output_path) } else { lossy(input_path, output_path, parameters) - }; + } } fn lossless(input_path: String, output_path: String) -> Result<(), io::Error> @@ -22,7 +26,7 @@ fn lossless(input_path: String, output_path: String) -> Result<(), io::Error> CString::new(format!("{:?}", std::env::current_exe()))?, CString::new(input_path)?, CString::new(format!("--output={}", output_path))?, - CString::new("--optimize=3")? + CString::new("--optimize=3")?, ]; let argv: Vec<_> = args.iter().map(|a| a.as_ptr()).collect(); @@ -46,10 +50,7 @@ pub fn lossy(input_path: String, output_path: String, parameters: CSParameters) libc::fclose(input_file); let padding: [*mut c_void; 7] = [std::ptr::null_mut(); 7]; - let mut loss = 0; - if !parameters.optimize { - loss = (100 - parameters.gif.quality) as c_int - } + let loss = (100 - parameters.gif.quality) as c_int; let gc_info = gifsicle::Gif_CompressInfo { flags: 0, diff --git a/src/jpeg.rs b/src/jpeg.rs index 9036a42..79e09a8 100644 --- a/src/jpeg.rs +++ b/src/jpeg.rs @@ -1,9 +1,12 @@ use std::fs::File; -use std::io::Write; use std::{io, mem}; use mozjpeg_sys::*; use crate::CSParameters; use std::fs; +use std::io::Write; +use image::ImageOutputFormat::Jpeg; +use img_parts::{DynImage, ImageEXIF, ImageICC}; +use crate::resize::resize; pub struct Parameters { pub quality: u32, @@ -11,16 +14,32 @@ pub struct Parameters { pub fn compress(input_path: String, output_path: String, parameters: CSParameters) -> Result<(), io::Error> { - unsafe { - if parameters.optimize { - lossless(input_path, output_path, parameters) + let mut in_file = fs::read(input_path)?; + + if parameters.width > 0 || parameters.height > 0 { + if parameters.keep_metadata { + let metadata = extract_metadata(in_file.clone()); + in_file = resize(in_file, parameters.width, parameters.height, Jpeg(80))?; + in_file = save_metadata(in_file, metadata.0, metadata.1); } else { - lossy(input_path, output_path, parameters) + in_file = resize(in_file, parameters.width, parameters.height, Jpeg(80))?; } } + + unsafe { + let compression_buffer: (*mut u8, u64); + if parameters.optimize { + compression_buffer = lossless(in_file, parameters)?; + } else { + compression_buffer = lossy(in_file, parameters)?; + } + let mut output_file_buffer = File::create(output_path)?; + output_file_buffer.write_all(std::slice::from_raw_parts(compression_buffer.0, compression_buffer.1 as usize))?; + } + Ok(()) } -unsafe fn lossless(input_path: String, output_path: String, parameters: CSParameters) -> Result<(), io::Error> { +unsafe fn lossless(in_file: Vec, parameters: CSParameters) -> Result<(*mut u8, u64), io::Error> { let mut src_info: jpeg_decompress_struct = mem::zeroed(); let mut src_err = mem::zeroed(); @@ -33,7 +52,6 @@ unsafe fn lossless(input_path: String, output_path: String, parameters: CSParame jpeg_create_decompress(&mut src_info); jpeg_create_compress(&mut dst_info); - let in_file = fs::read(input_path)?; jpeg_mem_src(&mut src_info, in_file.as_ptr(), in_file.len() as _); if parameters.keep_metadata { @@ -68,25 +86,21 @@ unsafe fn lossless(input_path: String, output_path: String, parameters: CSParame jpeg_finish_decompress(&mut src_info); jpeg_destroy_decompress(&mut src_info); - let mut output_file_buffer = File::create(output_path)?; - output_file_buffer.write_all(std::slice::from_raw_parts(buf, buf_size as usize))?; - Ok(()) + Ok((buf, buf_size)) } -unsafe fn lossy(input_path: String, output_path: String, parameters: CSParameters) -> Result<(), io::Error> { +unsafe fn lossy(in_file: Vec, parameters: CSParameters) -> Result<(*mut u8, u64), io::Error> { let mut src_info: jpeg_decompress_struct = mem::zeroed(); let mut src_err = mem::zeroed(); let mut dst_info: jpeg_compress_struct = mem::zeroed(); let mut dst_err = mem::zeroed(); - src_info.common.err = jpeg_std_error(&mut src_err); dst_info.common.err = jpeg_std_error(&mut dst_err); jpeg_create_decompress(&mut src_info); jpeg_create_compress(&mut dst_info); - let in_file = fs::read(input_path)?; jpeg_mem_src(&mut src_info, in_file.as_ptr(), in_file.len() as _); if parameters.keep_metadata { @@ -128,7 +142,6 @@ unsafe fn lossy(input_path: String, output_path: String, parameters: CSParameter dst_info.optimize_coding = i32::from(true); jpeg_set_quality(&mut dst_info, parameters.jpeg.quality as i32, false as boolean); - jpeg_start_compress(&mut dst_info, true as boolean); if parameters.keep_metadata { @@ -151,7 +164,39 @@ unsafe fn lossy(input_path: String, output_path: String, parameters: CSParameter jpeg_finish_decompress(&mut src_info); jpeg_destroy_decompress(&mut src_info); - let mut output_file_buffer = File::create(output_path)?; - output_file_buffer.write_all(std::slice::from_raw_parts(buf, buf_size as usize))?; - Ok(()) + // let mut output_file_buffer = File::create(output_path)?; + // output_file_buffer.write_all(std::slice::from_raw_parts(buf, buf_size as usize))?; + Ok((buf, buf_size)) +} + +fn extract_metadata(image: Vec) -> (Option, Option) { + let (iccp, exif) = DynImage::from_bytes(image.into()) + .expect("image loaded") + .map_or((None, None), |dyn_image| (dyn_image.icc_profile(), dyn_image.exif())); + + (iccp, exif) +} + +//TODO if image is resized, change "PixelXDimension" and "PixelYDimension" +fn save_metadata(image_buffer: Vec, iccp: Option, exif: Option) -> Vec { + if iccp.is_some() || exif.is_some() { + let mut dyn_image = match DynImage::from_bytes(img_parts::Bytes::from(image_buffer.clone())) { + Ok(o) => match o { + None => return image_buffer, + Some(d) => d + } + Err(_) => return image_buffer + }; + + dyn_image.set_icc_profile(iccp); + dyn_image.set_exif(exif); + + let mut image_with_metadata: Vec = vec![]; + match dyn_image.encoder().write_to(&mut image_with_metadata) { + Ok(_) => image_with_metadata, + Err(_) => image_buffer + } + } else { + image_buffer + } } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index e84227c..09a9934 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ mod jpeg; mod png; mod gif; mod webp; +mod resize; use std::error::Error; use crate::utils::get_filetype; @@ -10,7 +11,7 @@ use std::ffi::CStr; use std::os::raw::c_char; #[repr(C)] -pub struct C_CSParameters { +pub struct CCSParameters { pub keep_metadata: bool, pub jpeg_quality: u32, pub png_level: u32, @@ -18,6 +19,8 @@ pub struct C_CSParameters { pub gif_quality: u32, pub webp_quality: u32, pub optimize: bool, + pub width: u32, + pub height: u32, } pub struct CSParameters { @@ -27,6 +30,8 @@ pub struct CSParameters { pub webp: webp::Parameters, pub keep_metadata: bool, pub optimize: bool, + pub width: u32, + pub height: u32, } pub fn initialize_parameters() -> CSParameters @@ -56,45 +61,34 @@ pub fn initialize_parameters() -> CSParameters webp, keep_metadata: false, optimize: false, + width: 0, + height: 0, } } #[no_mangle] -pub extern fn c_compress(input_path: *const c_char, output_path: *const c_char, params: C_CSParameters) -> bool { - unsafe { - let mut parameters = initialize_parameters(); - parameters.jpeg.quality = params.jpeg_quality; - parameters.png.level = params.png_level; - parameters.optimize = params.optimize; - parameters.keep_metadata = params.keep_metadata; - parameters.png.force_zopfli = params.png_force_zopfli; - parameters.gif.quality = params.gif_quality; - parameters.webp.quality = params.webp_quality; +#[allow(clippy::missing_safety_doc)] +pub unsafe extern fn c_compress(input_path: *const c_char, output_path: *const c_char, params: CCSParameters) -> bool { + let mut parameters = initialize_parameters(); + parameters.jpeg.quality = params.jpeg_quality; + parameters.png.level = params.png_level; + parameters.optimize = params.optimize; + parameters.keep_metadata = params.keep_metadata; + parameters.png.force_zopfli = params.png_force_zopfli; + parameters.gif.quality = params.gif_quality; + parameters.webp.quality = params.webp_quality; + parameters.width = params.width; + parameters.height = params.height; - compress(CStr::from_ptr(input_path).to_str().unwrap().to_string(), - CStr::from_ptr(output_path).to_str().unwrap().to_string(), - parameters).is_ok() - } + let x = compress(CStr::from_ptr(input_path).to_str().unwrap().to_string(), + CStr::from_ptr(output_path).to_str().unwrap().to_string(), + parameters).is_ok(); + x } pub fn compress(input_path: String, output_path: String, parameters: CSParameters) -> Result<(), Box> { + validate_parameters(¶meters)?; let file_type = get_filetype(&input_path); - if parameters.jpeg.quality == 0 || parameters.jpeg.quality > 100 { - return Err("Invalid JPEG quality value".into()); - } - - if parameters.png.level == 0 || parameters.png.level > 7 { - return Err("Invalid PNG quality value".into()); - } - - if parameters.gif.quality > 100 { - return Err("Invalid GIF quality value".into()); - } - - if parameters.webp.quality > 100 { - return Err("Invalid WebP quality value".into()); - } - match file_type { utils::SupportedFileTypes::Jpeg => { @@ -112,5 +106,25 @@ pub fn compress(input_path: String, output_path: String, parameters: CSParameter _ => return Err("Unknown file type".into()) } + Ok(()) +} + +fn validate_parameters(parameters: &CSParameters) -> Result<(), Box> { + if parameters.jpeg.quality == 0 || parameters.jpeg.quality > 100 { + return Err("Invalid JPEG quality value".into()); + } + + if parameters.png.level == 0 || parameters.png.level > 7 { + return Err("Invalid PNG quality value".into()); + } + + if parameters.gif.quality > 100 { + return Err("Invalid GIF quality value".into()); + } + + if parameters.webp.quality > 100 { + return Err("Invalid WebP quality value".into()); + } + Ok(()) } \ No newline at end of file diff --git a/src/png.rs b/src/png.rs index b28ae09..94b14a9 100644 --- a/src/png.rs +++ b/src/png.rs @@ -1,16 +1,23 @@ -use std::path::PathBuf; -use oxipng::{PngError}; +use std::{fs, io}; +use std::fs::File; +use std::io::Write; +use image::ImageOutputFormat::Png; use crate::CSParameters; +use crate::resize::resize; pub struct Parameters { pub oxipng: oxipng::Options, pub level: u32, - pub force_zopfli: bool + pub force_zopfli: bool, } -pub fn compress(input_path: String, output_path: String, parameters: CSParameters) -> Result<(), PngError> { - let in_file = oxipng::InFile::Path(PathBuf::from(input_path)); - let out_file = oxipng::OutFile::Path(Some(PathBuf::from(output_path))); +pub fn compress(input_path: String, output_path: String, parameters: CSParameters) -> Result<(), io::Error> { + let mut in_file = fs::read(input_path)?; + + if parameters.width > 0 || parameters.height > 0 { + in_file = resize(in_file, parameters.width, parameters.height, Png)?; + } + let mut oxipng_options = parameters.png.oxipng; if !parameters.keep_metadata { @@ -23,9 +30,18 @@ pub fn compress(input_path: String, output_path: String, parameters: CSParameter oxipng_options.deflate = oxipng::Deflaters::Libdeflater; let mut preset = parameters.png.level - 1; if parameters.optimize { - preset = 6; + preset = 6; } oxipng_options = oxipng::Options::from_preset(preset as u8); } - oxipng::optimize(&in_file, &out_file, &oxipng_options) + + let optimized_png = match oxipng::optimize_from_memory(in_file.as_slice(), &oxipng_options) { + Ok(o) => o, + Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)) + }; + + let mut output_file_buffer = File::create(output_path)?; + output_file_buffer.write_all(optimized_png.as_slice())?; + + Ok(()) } \ No newline at end of file diff --git a/src/resize.rs b/src/resize.rs new file mode 100644 index 0000000..a08b9d4 --- /dev/null +++ b/src/resize.rs @@ -0,0 +1,82 @@ +use std::io; +use std::io::{Cursor}; +use image::{DynamicImage, GenericImageView}; +use image::imageops::FilterType; +use image::io::Reader as ImageReader; + +pub fn resize(image_buffer: Vec, width: u32, height: u32, format: image::ImageOutputFormat) -> Result, io::Error> { + let mut image = match ImageReader::new(Cursor::new(image_buffer)).with_guessed_format()?.decode() { + Ok(i) => i, + Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e.to_string())) + }; + + let dimensions = compute_dimensions(image.width(), image.height(),width, height); + image = image.resize_exact(dimensions.0, dimensions.1, FilterType::Lanczos3); + + let mut resized_file: Vec = vec![]; + match image.write_to(&mut resized_file, format) { + Ok(_) => {} + Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e.to_string())) + } + + Ok(resized_file) +} + +pub fn resize_image(image: DynamicImage, width: u32, height: u32) -> Result { + let dimensions = compute_dimensions(image.width(), image.height(),width, height); + let resized_image = image.resize_exact(dimensions.0, dimensions.1, FilterType::Lanczos3); + + Ok(resized_image) +} + +fn compute_dimensions(original_width: u32, original_height: u32, desired_width: u32, desired_height: u32) -> (u32, u32) { + if desired_width > 0 && desired_height > 0 { + return (desired_width, desired_height); + } + + let mut n_width = desired_width as f32; + let mut n_height = desired_height as f32; + let ratio = original_width as f32 / original_height as f32; + + if desired_height == 0 { + n_height = (n_width / ratio).round(); + } + + if desired_width == 0 { + n_width = (n_height * ratio).round(); + } + + (n_width as u32, n_height as u32) +} + +#[test] +fn downscale_exact() { + let original_width = 800; + let original_height = 600; + + assert_eq!(compute_dimensions(original_width, original_height, 300, 300), (300, 300)) +} + +#[test] +fn same_exact() { + let original_width = 800; + let original_height = 600; + + assert_eq!(compute_dimensions(original_width, original_height, 800, 600), (800, 600)) +} + +#[test] +fn downscale_on_width() { + let original_width = 800; + let original_height = 600; + + assert_eq!(compute_dimensions(original_width, original_height, 750, 0), (750, 563)) +} + +#[test] +fn downscale_on_height() { + let original_width = 800; + let original_height = 600; + + assert_eq!(compute_dimensions(original_width, original_height, 0, 478), (637, 478)) +} \ No newline at end of file diff --git a/src/webp.rs b/src/webp.rs index 828a80e..13cd442 100644 --- a/src/webp.rs +++ b/src/webp.rs @@ -2,8 +2,8 @@ use std::fs::File; use std::io; use std::io::{Read, Write}; use std::ops::Deref; -use webp; use crate::CSParameters; +use crate::resize::resize_image; pub struct Parameters { pub quality: u32, @@ -11,6 +11,7 @@ pub struct Parameters { pub fn compress(input_path: String, output_path: String, parameters: CSParameters) -> Result<(), io::Error> { + let must_resize = parameters.width > 0 || parameters.height > 0; let mut input_file = File::open(input_path)?; let mut input_data = Vec::new(); @@ -20,7 +21,11 @@ pub fn compress(input_path: String, output_path: String, parameters: CSParameter Some(img) => img, None => return Err(io::Error::new(io::ErrorKind::Other, "WebP decode failed!")) }; - let input_image = input_webp.to_image(); + let mut input_image = input_webp.to_image(); + + if must_resize { + input_image = resize_image(input_image, parameters.width, parameters.height)?; + } let encoder = match webp::Encoder::from_image(&input_image) { Ok(encoder) => encoder, @@ -29,7 +34,12 @@ pub fn compress(input_path: String, output_path: String, parameters: CSParameter let mut output_file = File::create(output_path)?; if parameters.optimize { - output_file.write_all(encoder.encode_lossless().deref())?; + if must_resize { + output_file.write_all(encoder.encode(100.0).deref())?; + } else { + //TODO With resize can throw an error + output_file.write_all(encoder.encode_lossless().deref())?; + } } else { output_file.write_all(encoder.encode(parameters.webp.quality as f32).deref())?; } diff --git a/tests/gif.rs b/tests/gif.rs index 292f194..294c48a 100644 --- a/tests/gif.rs +++ b/tests/gif.rs @@ -22,12 +22,13 @@ pub fn cleanup(file: &str) { // let output = "tests/samples/output/compressed_20.gif"; // initialize(output); // let mut params = libcaesium::initialize_parameters(); -// params.gif.level = 20; +// params.gif.quality = 20; // libcaesium::compress(String::from("tests/samples/uncompressed_은하.gif"), // String::from(output), // params) // .unwrap(); // assert!(std::path::Path::new(output).exists()); +// assert_eq!(infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/webp"); // cleanup(output) // } // diff --git a/tests/jpeg.rs b/tests/jpeg.rs index 215f4b8..d196bdf 100644 --- a/tests/jpeg.rs +++ b/tests/jpeg.rs @@ -1,4 +1,3 @@ -use libcaesium; use imgref::{Img, ImgVec}; use std::path::Path; use dssim::{RGBAPLU, ToRGBAPLU, Val}; @@ -52,6 +51,9 @@ fn compress_100() { pars.jpeg.quality = 100; libcaesium::compress(String::from("tests/samples/uncompressed_드림캐쳐.jpg"), String::from(output), pars).unwrap(); assert!(std::path::Path::new(output).exists()); + let kind = infer::get_from_path(output).unwrap().unwrap(); + assert_eq!(kind.mime_type(), "image/jpeg"); + assert_eq!(image::image_dimensions(output).unwrap(), (2400, 1600)); cleanup(output) } @@ -63,6 +65,9 @@ fn compress_80() { pars.jpeg.quality = 80; libcaesium::compress(String::from("tests/samples/uncompressed_드림캐쳐.jpg"), String::from(output), pars).unwrap(); assert!(std::path::Path::new(output).exists()); + let kind = infer::get_from_path(output).unwrap().unwrap(); + assert_eq!(kind.mime_type(), "image/jpeg"); + assert_eq!(image::image_dimensions(output).unwrap(), (2400, 1600)); cleanup(output) } @@ -74,6 +79,9 @@ fn compress_50() { pars.jpeg.quality = 50; libcaesium::compress(String::from("tests/samples/uncompressed_드림캐쳐.jpg"), String::from(output), pars).unwrap(); assert!(std::path::Path::new(output).exists()); + let kind = infer::get_from_path(output).unwrap().unwrap(); + assert_eq!(kind.mime_type(), "image/jpeg"); + assert_eq!(image::image_dimensions(output).unwrap(), (2400, 1600)); cleanup(output) } @@ -85,6 +93,9 @@ fn compress_10() { pars.jpeg.quality = 10; libcaesium::compress(String::from("tests/samples/uncompressed_드림캐쳐.jpg"), String::from(output), pars).unwrap(); assert!(std::path::Path::new(output).exists()); + let kind = infer::get_from_path(output).unwrap().unwrap(); + assert_eq!(kind.mime_type(), "image/jpeg"); + assert_eq!(image::image_dimensions(output).unwrap(), (2400, 1600)); cleanup(output) } @@ -96,8 +107,44 @@ fn optimize_jpeg() { pars.optimize = true; libcaesium::compress(String::from("tests/samples/uncompressed_드림캐쳐.jpg"), String::from(output), pars).unwrap(); assert!(std::path::Path::new(output).exists()); + let kind = infer::get_from_path(output).unwrap().unwrap(); + assert_eq!(kind.mime_type(), "image/jpeg"); + assert_eq!(image::image_dimensions(output).unwrap(), (2400, 1600)); //Floats error assert!(diff(output) < 0.001); + + cleanup(output) +} + +#[test] +fn downscale_exact() { + let output = "tests/samples/output/downscale_800_600.jpg"; + initialize(output); + let mut pars = libcaesium::initialize_parameters(); + pars.jpeg.quality = 80; + pars.width = 800; + pars.height = 600; + libcaesium::compress(String::from("tests/samples/uncompressed_드림캐쳐.jpg"), String::from(output), pars).unwrap(); + assert!(std::path::Path::new(output).exists()); + let kind = infer::get_from_path(output).unwrap().unwrap(); + assert_eq!(kind.mime_type(), "image/jpeg"); + assert_eq!(image::image_dimensions(output).unwrap(), (800, 600)); + cleanup(output) +} + +#[test] +fn downscale_exact_optimize() { + let output = "tests/samples/output/downscale_optimize_800_600.jpg"; + initialize(output); + let mut pars = libcaesium::initialize_parameters(); + pars.optimize = true; + pars.width = 800; + pars.height = 600; + libcaesium::compress(String::from("tests/samples/uncompressed_드림캐쳐.jpg"), String::from(output), pars).unwrap(); + assert!(std::path::Path::new(output).exists()); + let kind = infer::get_from_path(output).unwrap().unwrap(); + assert_eq!(kind.mime_type(), "image/jpeg"); + assert_eq!(image::image_dimensions(output).unwrap(), (800, 600)); cleanup(output) } \ No newline at end of file diff --git a/tests/metadata.rs b/tests/metadata.rs index f7e1b90..347f858 100644 --- a/tests/metadata.rs +++ b/tests/metadata.rs @@ -1,9 +1,7 @@ +use std::collections::HashMap; use std::sync::Once; use std::fs; use std::path::Path; -use std::fs::File; -use std::io::BufReader; -use exif::{Tag, In, Field}; static INIT: Once = Once::new(); @@ -30,8 +28,7 @@ fn compress_80_with_metadata() { pars.keep_metadata = true; libcaesium::compress(String::from("tests/samples/uncompressed_드림캐쳐.jpg"), String::from(output), pars).unwrap(); assert!(std::path::Path::new(output).exists()); - let model = get_model_metadata(Path::new(output)); - assert_eq!(model.display_value().to_string(), "\"Canon EOS 2000D\""); + assert!(metadata_is_equal(Path::new("tests/samples/uncompressed_드림캐쳐.jpg"), Path::new(output))); cleanup(output) } @@ -44,16 +41,41 @@ fn optimize_with_metadata() { pars.keep_metadata = true; libcaesium::compress(String::from("tests/samples/uncompressed_드림캐쳐.jpg"), String::from(output), pars).unwrap(); assert!(std::path::Path::new(output).exists()); - let model = get_model_metadata(Path::new(output)); - assert_eq!(model.display_value().to_string(), "\"Canon EOS 2000D\""); + assert!(metadata_is_equal(Path::new("tests/samples/uncompressed_드림캐쳐.jpg"), Path::new(output))); cleanup(output) } -fn get_model_metadata(path: &Path) -> Field { - let file = File::open(path).unwrap(); - let exif = exif::Reader::new().read_from_container(&mut BufReader::new(&file)).unwrap(); +#[test] +fn resize_optimize_with_metadata() { + let output = "tests/samples/output/resized_optimized_metadata.jpg"; + initialize(output); + let mut pars = libcaesium::initialize_parameters(); + pars.optimize = true; + pars.keep_metadata = true; + pars.width = 200; + pars.height = 200; + libcaesium::compress(String::from("tests/samples/uncompressed_드림캐쳐.jpg"), String::from(output), pars).unwrap(); + assert!(std::path::Path::new(output).exists()); + assert!(metadata_is_equal(Path::new("tests/samples/uncompressed_드림캐쳐.jpg"), Path::new(output))); + cleanup(output) +} - let f = exif.get_field(Tag::Model, In::PRIMARY).unwrap(); +fn extract_exif(path: &Path) -> HashMap { + let file = std::fs::File::open(path).unwrap(); + let mut bufreader = std::io::BufReader::new(&file); + let exif_reader = exif::Reader::new(); + let exif = exif_reader.read_from_container(&mut bufreader).unwrap(); + let mut exif_map = HashMap::new(); + for f in exif.fields() { + exif_map.insert(format!("{}", f.tag), f.display_value().to_string() as String); + } - f.clone() + exif_map +} + +fn metadata_is_equal(input: &Path, output: &Path) -> bool { + let original_exif_map = extract_exif(input); + let compressed_exif_map = extract_exif(output); + + original_exif_map.eq(&compressed_exif_map) } \ No newline at end of file diff --git a/tests/png.rs b/tests/png.rs index 6c41202..f82fdac 100644 --- a/tests/png.rs +++ b/tests/png.rs @@ -1,4 +1,3 @@ -use libcaesium; use std::sync::Once; use std::fs; @@ -27,6 +26,8 @@ fn standard_compress_png() { libcaesium::initialize_parameters()) .unwrap(); assert!(std::path::Path::new(output).exists()); + assert_eq!(infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/png"); + assert_eq!(image::image_dimensions(output).unwrap(), (380, 287)); cleanup(output) } @@ -41,6 +42,8 @@ fn standard_compress_png_with_optimize_flag() { params) .unwrap(); assert!(std::path::Path::new(output).exists()); + assert_eq!(infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/png"); + assert_eq!(image::image_dimensions(output).unwrap(), (380, 287)); cleanup(output) } @@ -57,5 +60,62 @@ fn zopfli_compress_png() { params) .unwrap(); assert!(std::path::Path::new(output).exists()); + assert_eq!(infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/png"); + assert_eq!(image::image_dimensions(output).unwrap(), (380, 287)); + cleanup(output) +} + +#[test] +fn downscale_standard_compress_png() { + let output = "tests/samples/output/downscale_compressed.png"; + initialize(output); + let mut params = libcaesium::initialize_parameters(); + params.width = 150; + params.height = 150; + libcaesium::compress(String::from("tests/samples/uncompressed_드림캐쳐.png"), + String::from(output), + params) + .unwrap(); + assert!(std::path::Path::new(output).exists()); + assert_eq!(infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/png"); + assert_eq!(image::image_dimensions(output).unwrap(), (150, 150)); + cleanup(output) +} + +#[test] +fn downscale_standard_compress_png_with_optimize_flag() { + let output = "tests/samples/output/downscale_compressed_max.png"; + initialize(output); + let mut params = libcaesium::initialize_parameters(); + params.width = 150; + params.height = 150; + params.optimize = true; + libcaesium::compress(String::from("tests/samples/uncompressed_드림캐쳐.png"), + String::from(output), + params) + .unwrap(); + assert!(std::path::Path::new(output).exists()); + assert_eq!(infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/png"); + assert_eq!(image::image_dimensions(output).unwrap(), (150, 150)); + cleanup(output) +} + +#[test] +fn downscale_zopfli_compress_png() { + let output = "tests/samples/output/downscale_optimized.png"; + initialize(output); + let mut params = libcaesium::initialize_parameters(); + params.width = 150; + params.height = 150; + params.png.level = 3; + params.optimize = true; + params.png.force_zopfli = true; + libcaesium::compress(String::from("tests/samples/uncompressed_드림캐쳐.png"), + String::from(output), + params) + .unwrap(); + assert!(std::path::Path::new(output).exists()); + assert_eq!(infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/png"); + assert_eq!(image::image_dimensions(output).unwrap(), (150, 150)); cleanup(output) } diff --git a/tests/webp.rs b/tests/webp.rs index 2057aa4..fb7203f 100644 --- a/tests/webp.rs +++ b/tests/webp.rs @@ -29,6 +29,7 @@ fn compress_20() { params) .unwrap(); assert!(std::path::Path::new(output).exists()); + assert_eq!(infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/webp"); cleanup(output) } @@ -43,6 +44,7 @@ fn compress_50() { params) .unwrap(); assert!(std::path::Path::new(output).exists()); + assert_eq!(infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/webp"); cleanup(output) } @@ -57,6 +59,7 @@ fn compress_80() { params) .unwrap(); assert!(std::path::Path::new(output).exists()); + assert_eq!(infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/webp"); cleanup(output) } @@ -71,6 +74,7 @@ fn compress_100() { params) .unwrap(); assert!(std::path::Path::new(output).exists()); + assert_eq!(infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/webp"); cleanup(output) } @@ -85,5 +89,42 @@ fn optimize() { params) .unwrap(); assert!(std::path::Path::new(output).exists()); + assert_eq!(infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/webp"); + cleanup(output) +} + +#[test] +fn downscale_compress_80() { + let output = "tests/samples/output/downscale_compressed_80.webp"; + initialize(output); + let mut params = libcaesium::initialize_parameters(); + params.webp.quality = 80; + params.width = 150; + params.height = 100; + libcaesium::compress(String::from("tests/samples/uncompressed_家.webp"), + String::from(output), + params) + .unwrap(); + assert!(std::path::Path::new(output).exists()); + assert_eq!(infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/webp"); + assert_eq!(image::image_dimensions(output).unwrap(), (150, 100)); + cleanup(output) +} + +#[test] +fn downscale_optimize() { + let output = "tests/samples/output/downscale_optimized.webp"; + initialize(output); + let mut params = libcaesium::initialize_parameters(); + params.optimize = true; + params.width = 150; + params.height = 100; + libcaesium::compress(String::from("tests/samples/uncompressed_家.webp"), + String::from(output), + params) + .unwrap(); + assert!(std::path::Path::new(output).exists()); + assert_eq!(infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/webp"); + assert_eq!(image::image_dimensions(output).unwrap(), (150, 100)); cleanup(output) }