From c22b390f3403c1857f5c22133f275d0d1e88a721 Mon Sep 17 00:00:00 2001 From: Matteo Paonessa Date: Sat, 25 Feb 2023 20:39:52 +0100 Subject: [PATCH] Compress to memory --- Cargo.toml | 6 +++--- src/jpeg.rs | 17 +++++++++++----- src/lib.rs | 5 ++++- src/png.rs | 52 +++++++++++++++++++++++------------------------ src/webp.rs | 28 ++++++++++++++++--------- tests/cleanup.rs | 7 +++++++ tests/gif.rs | 14 ++++--------- tests/jpeg.rs | 28 ++++++++++--------------- tests/metadata.rs | 19 +++++++---------- tests/png.rs | 26 +++++++++--------------- tests/webp.rs | 43 +++++++++++++++++---------------------- 11 files changed, 120 insertions(+), 125 deletions(-) create mode 100644 tests/cleanup.rs diff --git a/Cargo.toml b/Cargo.toml index 548a4e1..9f19a58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libcaesium" -version = "0.10.1" +version = "0.11.0" authors = ["Matteo Paonessa "] edition = "2021" categories = ["multimedia::images"] @@ -23,11 +23,11 @@ license = "Apache-2.0" [dependencies] mozjpeg-sys = { version = "1.0.2", optional = true } -oxipng = { version = "6.0.1", optional = true } +oxipng = { version = "8.0.0", optional = true } libc = "0.2" gifsicle = { version = "1.92.5", optional = true } webp = { version = "0.2", optional = true } -infer = "0.9" +infer = "0.12.0" image = { version = "0.24.3", default-features = false, features = ["jpeg", "png", "webp", "gif"] } img-parts = "0.3" bytes = "1.1" diff --git a/src/jpeg.rs b/src/jpeg.rs index 1a17945..883149b 100644 --- a/src/jpeg.rs +++ b/src/jpeg.rs @@ -15,8 +15,16 @@ pub fn compress( output_path: String, parameters: &CSParameters, ) -> Result<(), io::Error> { - let mut in_file = fs::read(input_path)?; + let in_file = fs::read(input_path)?; + let out_buffer = compress_to_memory(in_file, parameters)?; + let mut out_file = File::create(output_path)?; + out_file.write_all(&out_buffer)?; + Ok(()) +} + +pub fn compress_to_memory(mut in_file: Vec, parameters: &CSParameters) -> Result, io::Error> +{ if parameters.width > 0 || parameters.height > 0 { if parameters.keep_metadata { let metadata = extract_metadata(in_file.clone()); @@ -33,13 +41,12 @@ pub fn compress( } else { lossy(in_file, parameters)? }; - let mut output_file_buffer = File::create(output_path)?; - output_file_buffer.write_all(std::slice::from_raw_parts( + let slice = std::slice::from_raw_parts( compression_buffer.0, compression_buffer.1 as usize, - ))?; + ); + Ok(slice.to_vec()) } - Ok(()) } unsafe fn lossless( diff --git a/src/lib.rs b/src/lib.rs index b7688aa..27f8e57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,8 @@ +extern crate alloc; + +use alloc::ffi::CString; use std::error::Error; -use std::ffi::{CStr, CString}; +use std::ffi::{CStr}; use std::os::raw::c_char; use crate::utils::get_filetype; diff --git a/src/png.rs b/src/png.rs index aaa0a3e..ac25dbb 100644 --- a/src/png.rs +++ b/src/png.rs @@ -1,47 +1,45 @@ -use image::io::Reader as ImageReader; - -use oxipng::Deflaters::{Libdeflater, Zopfli}; +use std::{fs, io}; use std::fs::File; use std::io::Write; use std::num::NonZeroU8; -use std::{fs, io}; +use image::ImageOutputFormat; + +use oxipng::Deflaters::{Libdeflater, Zopfli}; -use crate::resize::resize_image; use crate::CSParameters; +use crate::resize::resize; pub fn compress( input_path: String, output_path: String, parameters: &CSParameters, ) -> Result<(), io::Error> { - let mut original_path = input_path; + let mut in_file = fs::read(input_path)?; + if parameters.width > 0 || parameters.height > 0 { - let image = match ImageReader::open(original_path)?.decode() { - Ok(i) => i, - Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e.to_string())), - }; - let image = resize_image(image, parameters.width, parameters.height)?; - match image.save_with_format(output_path.clone(), image::ImageFormat::Png) { - Ok(_) => {} - Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e.to_string())), - }; - original_path = output_path.clone(); + in_file = resize(in_file, parameters.width, parameters.height, ImageOutputFormat::Png)?; } - let optimized_png: Vec = if parameters.optimize { - lossless(original_path, parameters)? - } else { - lossy(original_path, parameters)? - }; - + let optimized_png = compress_to_memory(in_file, parameters)?; let mut output_file_buffer = File::create(output_path)?; output_file_buffer.write_all(optimized_png.as_slice())?; Ok(()) } -fn lossy(input_path: String, parameters: &CSParameters) -> Result, io::Error> { - let rgba_bitmap = match lodepng::decode32_file(input_path) { +pub fn compress_to_memory(in_file: Vec, parameters: &CSParameters) -> Result, io::Error> +{ + let optimized_png: Vec = if parameters.optimize { + lossless(in_file, parameters)? + } else { + lossy(in_file, parameters)? + }; + + Ok(optimized_png) +} + +fn lossy(in_file: Vec, parameters: &CSParameters) -> Result, io::Error> { + let rgba_bitmap = match lodepng::decode32(in_file) { Ok(i) => i, Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)), }; @@ -85,8 +83,8 @@ fn lossy(input_path: String, parameters: &CSParameters) -> Result, io::E Ok(png_vec) } -fn lossless(input_path: String, parameters: &CSParameters) -> Result, io::Error> { - let in_file = fs::read(input_path)?; +fn lossless(in_file: Vec, parameters: &CSParameters) -> Result, io::Error> { + // let in_file = fs::read(input_path)?; let mut oxipng_options = oxipng::Options::default(); if !parameters.keep_metadata { oxipng_options.strip = oxipng::Headers::Safe; @@ -98,7 +96,7 @@ fn lossless(input_path: String, parameters: &CSParameters) -> Result, io }; } else { oxipng_options = oxipng::Options::from_preset(3); - oxipng_options.deflate = Libdeflater; + oxipng_options.deflate = Libdeflater { compression: 6 }; } let optimized_png = match oxipng::optimize_from_memory(in_file.as_slice(), &oxipng_options) { diff --git a/src/webp.rs b/src/webp.rs index 8773e48..48757c6 100644 --- a/src/webp.rs +++ b/src/webp.rs @@ -11,18 +11,26 @@ pub fn compress( 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(); input_file.read_to_end(&mut input_data)?; - let decoder = webp::Decoder::new(&input_data); + + let mut output_file = File::create(output_path)?; + let compressed_image = compress_to_memory(input_data, parameters)?; + output_file.write_all(&compressed_image)?; + Ok(()) +} + +pub fn compress_to_memory(in_file: Vec, parameters: &CSParameters) -> Result, io::Error> +{ + let decoder = webp::Decoder::new(&in_file); let input_webp = match decoder.decode() { Some(img) => img, None => return Err(io::Error::new(io::ErrorKind::Other, "WebP decode failed!")), }; let mut input_image = input_webp.to_image(); - + let must_resize = parameters.width > 0 || parameters.height > 0; if must_resize { input_image = resize_image(input_image, parameters.width, parameters.height)?; } @@ -32,16 +40,16 @@ pub fn compress( Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)), }; - let mut output_file = File::create(output_path)?; - if parameters.optimize { + let encoded_image = if parameters.optimize { if must_resize { - output_file.write_all(encoder.encode(100.0).deref())?; + encoder.encode(100.0) } else { //TODO With resize can throw an error - output_file.write_all(encoder.encode_lossless().deref())?; + encoder.encode_lossless() } } else { - output_file.write_all(encoder.encode(parameters.webp.quality as f32).deref())?; - } - Ok(()) + encoder.encode(parameters.webp.quality as f32) + }; + + Ok(encoded_image.deref().to_vec()) } diff --git a/tests/cleanup.rs b/tests/cleanup.rs new file mode 100644 index 0000000..bb1feec --- /dev/null +++ b/tests/cleanup.rs @@ -0,0 +1,7 @@ +use std::fs; + +pub fn remove_compressed_test_file(file: &str) { + if fs::metadata(file).is_ok() { + fs::remove_file(file).unwrap(); + } +} \ No newline at end of file diff --git a/tests/gif.rs b/tests/gif.rs index 49507b6..5f6c125 100644 --- a/tests/gif.rs +++ b/tests/gif.rs @@ -1,22 +1,16 @@ -use std::fs; use std::sync::Once; +use crate::cleanup::remove_compressed_test_file; + +mod cleanup; static INIT: Once = Once::new(); pub fn initialize(file: &str) { INIT.call_once(|| { - if fs::metadata(file).is_ok() { - fs::remove_file(file).unwrap(); - } + remove_compressed_test_file(file) }); } -pub fn cleanup(file: &str) { - if fs::metadata(file).is_ok() { - fs::remove_file(file).unwrap(); - } -} - // #[test] // fn compress_20() { // let output = "tests/samples/output/compressed_20.gif"; diff --git a/tests/jpeg.rs b/tests/jpeg.rs index 90761e4..c483c93 100644 --- a/tests/jpeg.rs +++ b/tests/jpeg.rs @@ -1,23 +1,17 @@ use dssim::Val; -use std::fs; use std::sync::Once; +use crate::cleanup::remove_compressed_test_file; + +mod cleanup; static INIT: Once = Once::new(); pub fn initialize(file: &str) { INIT.call_once(|| { - if fs::metadata(file).is_ok() { - fs::remove_file(file).unwrap(); - } + remove_compressed_test_file(file); }); } -pub fn cleanup(file: &str) { - if fs::metadata(file).is_ok() { - fs::remove_file(file).unwrap(); - } -} - fn diff(compressed: &str) -> Val { let attr = dssim::Dssim::new(); let orig = dssim::load_image(&attr, "tests/samples/uncompressed_드림캐쳐.jpg").unwrap(); @@ -42,7 +36,7 @@ fn compress_100() { 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) + remove_compressed_test_file(output) } #[test] @@ -61,7 +55,7 @@ fn compress_80() { 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) + remove_compressed_test_file(output) } #[test] @@ -80,7 +74,7 @@ fn compress_50() { 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) + remove_compressed_test_file(output) } #[test] @@ -99,7 +93,7 @@ fn compress_10() { 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) + remove_compressed_test_file(output) } #[test] @@ -122,7 +116,7 @@ fn optimize_jpeg() { //Floats error assert!(diff(output) < 0.001); - cleanup(output) + remove_compressed_test_file(output) } #[test] @@ -143,7 +137,7 @@ fn downscale_exact() { 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) + remove_compressed_test_file(output) } #[test] @@ -164,5 +158,5 @@ fn downscale_exact_optimize() { 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) + remove_compressed_test_file(output) } diff --git a/tests/metadata.rs b/tests/metadata.rs index cae8e6d..d178271 100644 --- a/tests/metadata.rs +++ b/tests/metadata.rs @@ -3,22 +3,17 @@ use std::fs; use std::path::Path; use std::sync::Once; +use crate::cleanup::remove_compressed_test_file; + +mod cleanup; static INIT: Once = Once::new(); pub fn initialize(file: &str) { INIT.call_once(|| { - if fs::metadata(file).is_ok() { - fs::remove_file(file).unwrap(); - } + remove_compressed_test_file(file); }); } -pub fn cleanup(file: &str) { - if fs::metadata(file).is_ok() { - fs::remove_file(file).unwrap(); - } -} - #[test] fn compress_80_with_metadata() { let output = "tests/samples/output/compressed_80_metadata.jpg"; @@ -37,7 +32,7 @@ fn compress_80_with_metadata() { Path::new("tests/samples/uncompressed_드림캐쳐.jpg"), Path::new(output) )); - cleanup(output) + remove_compressed_test_file(output) } #[test] @@ -58,7 +53,7 @@ fn optimize_with_metadata() { Path::new("tests/samples/uncompressed_드림캐쳐.jpg"), Path::new(output) )); - cleanup(output) + remove_compressed_test_file(output) } #[test] @@ -81,7 +76,7 @@ fn resize_optimize_with_metadata() { Path::new("tests/samples/uncompressed_드림캐쳐.jpg"), Path::new(output) )); - cleanup(output) + remove_compressed_test_file(output) } fn extract_exif(path: &Path) -> HashMap { diff --git a/tests/png.rs b/tests/png.rs index 29e0f54..db5101e 100644 --- a/tests/png.rs +++ b/tests/png.rs @@ -1,22 +1,16 @@ -use std::fs; use std::sync::Once; +use crate::cleanup::remove_compressed_test_file; + +mod cleanup; static INIT: Once = Once::new(); pub fn initialize(file: &str) { INIT.call_once(|| { - if fs::metadata(file).is_ok() { - fs::remove_file(file).unwrap(); - } + remove_compressed_test_file(file); }); } -pub fn cleanup(file: &str) { - if fs::metadata(file).is_ok() { - fs::remove_file(file).unwrap(); - } -} - #[test] fn standard_compress_png() { let output = "tests/samples/output/compressed.png"; @@ -33,7 +27,7 @@ fn standard_compress_png() { "image/png" ); assert_eq!(image::image_dimensions(output).unwrap(), (380, 287)); - cleanup(output) + remove_compressed_test_file(output) } #[test] @@ -54,7 +48,7 @@ fn standard_compress_png_with_optimize_flag() { "image/png" ); assert_eq!(image::image_dimensions(output).unwrap(), (380, 287)); - cleanup(output) + remove_compressed_test_file(output) } // #[test] @@ -72,7 +66,7 @@ fn standard_compress_png_with_optimize_flag() { // 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) +// remove_compressed_test_file(output) // } #[test] @@ -94,7 +88,7 @@ fn downscale_standard_compress_png() { "image/png" ); assert_eq!(image::image_dimensions(output).unwrap(), (150, 150)); - cleanup(output) + remove_compressed_test_file(output) } #[test] @@ -117,7 +111,7 @@ fn downscale_standard_compress_png_with_optimize_flag() { "image/png" ); assert_eq!(image::image_dimensions(output).unwrap(), (150, 150)); - cleanup(output) + remove_compressed_test_file(output) } #[test] @@ -142,5 +136,5 @@ fn downscale_zopfli_compress_png() { "image/png" ); assert_eq!(image::image_dimensions(output).unwrap(), (150, 150)); - cleanup(output) + remove_compressed_test_file(output) } diff --git a/tests/webp.rs b/tests/webp.rs index 50ff22b..1d668c9 100644 --- a/tests/webp.rs +++ b/tests/webp.rs @@ -1,22 +1,17 @@ -use std::fs; use std::sync::Once; +use crate::cleanup::remove_compressed_test_file; + +mod cleanup; + static INIT: Once = Once::new(); pub fn initialize(file: &str) { INIT.call_once(|| { - if fs::metadata(file).is_ok() { - fs::remove_file(file).unwrap(); - } + remove_compressed_test_file(file); }); } -pub fn cleanup(file: &str) { - if fs::metadata(file).is_ok() { - fs::remove_file(file).unwrap(); - } -} - #[test] fn compress_20() { let output = "tests/samples/output/compressed_20.webp"; @@ -28,13 +23,13 @@ fn compress_20() { String::from(output), ¶ms, ) - .unwrap(); + .unwrap(); assert!(std::path::Path::new(output).exists()); assert_eq!( infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/webp" ); - cleanup(output) + remove_compressed_test_file(output) } #[test] @@ -48,13 +43,13 @@ fn compress_50() { String::from(output), ¶ms, ) - .unwrap(); + .unwrap(); assert!(std::path::Path::new(output).exists()); assert_eq!( infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/webp" ); - cleanup(output) + remove_compressed_test_file(output) } #[test] @@ -68,13 +63,13 @@ fn compress_80() { String::from(output), ¶ms, ) - .unwrap(); + .unwrap(); assert!(std::path::Path::new(output).exists()); assert_eq!( infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/webp" ); - cleanup(output) + remove_compressed_test_file(output) } #[test] @@ -88,13 +83,13 @@ fn compress_100() { String::from(output), ¶ms, ) - .unwrap(); + .unwrap(); assert!(std::path::Path::new(output).exists()); assert_eq!( infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/webp" ); - cleanup(output) + remove_compressed_test_file(output) } #[test] @@ -108,13 +103,13 @@ fn optimize() { String::from(output), ¶ms, ) - .unwrap(); + .unwrap(); assert!(std::path::Path::new(output).exists()); assert_eq!( infer::get_from_path(output).unwrap().unwrap().mime_type(), "image/webp" ); - cleanup(output) + remove_compressed_test_file(output) } #[test] @@ -130,14 +125,14 @@ fn downscale_compress_80() { String::from(output), ¶ms, ) - .unwrap(); + .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) + remove_compressed_test_file(output) } #[test] @@ -153,12 +148,12 @@ fn downscale_optimize() { String::from(output), ¶ms, ) - .unwrap(); + .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) + remove_compressed_test_file(output) }