This commit is contained in:
Matteo Paonessa 2022-02-12 14:10:12 +01:00
parent 35da4c38ec
commit 744090fee9
13 changed files with 433 additions and 86 deletions

View File

@ -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"

View File

@ -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.

View File

@ -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,

View File

@ -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<u8>, 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<u8>, 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<u8>) -> (Option<img_parts::Bytes>, Option<img_parts::Bytes>) {
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<u8>, iccp: Option<img_parts::Bytes>, exif: Option<img_parts::Bytes>) -> Vec<u8> {
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<u8> = vec![];
match dyn_image.encoder().write_to(&mut image_with_metadata) {
Ok(_) => image_with_metadata,
Err(_) => image_buffer
}
} else {
image_buffer
}
}

View File

@ -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<dyn Error>> {
validate_parameters(&parameters)?;
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 => {
@ -114,3 +108,23 @@ pub fn compress(input_path: String, output_path: String, parameters: CSParameter
Ok(())
}
fn validate_parameters(parameters: &CSParameters) -> Result<(), Box<dyn Error>> {
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(())
}

View File

@ -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(())
}

82
src/resize.rs Normal file
View File

@ -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<u8>, width: u32, height: u32, format: image::ImageOutputFormat) -> Result<Vec<u8>, 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<u8> = 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<DynamicImage, io::Error> {
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))
}

View File

@ -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())?;
}

View File

@ -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)
// }
//

View File

@ -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)
}

View File

@ -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();
let f = exif.get_field(Tag::Model, In::PRIMARY).unwrap();
f.clone()
#[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)
}
fn extract_exif(path: &Path) -> HashMap<String, String> {
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);
}
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)
}

View File

@ -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)
}

View File

@ -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)
}