Resizing
This commit is contained in:
parent
35da4c38ec
commit
744090fee9
|
@ -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"
|
||||
|
|
10
README.md
10
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.
|
||||
|
|
15
src/gif.rs
15
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,
|
||||
|
|
79
src/jpeg.rs
79
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<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
|
||||
}
|
||||
}
|
58
src/lib.rs
58
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,12 +61,14 @@ 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 {
|
||||
#[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;
|
||||
|
@ -70,31 +77,18 @@ pub extern fn c_compress(input_path: *const c_char, output_path: *const c_char,
|
|||
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(),
|
||||
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()
|
||||
}
|
||||
parameters).is_ok();
|
||||
x
|
||||
}
|
||||
|
||||
pub fn compress(input_path: String, output_path: String, parameters: CSParameters) -> Result<(), Box<dyn Error>> {
|
||||
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 => {
|
||||
|
@ -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(())
|
||||
}
|
30
src/png.rs
30
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 {
|
||||
|
@ -27,5 +34,14 @@ pub fn compress(input_path: String, output_path: String, parameters: CSParameter
|
|||
}
|
||||
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(())
|
||||
}
|
|
@ -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))
|
||||
}
|
14
src/webp.rs
14
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 {
|
||||
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())?;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
// }
|
||||
//
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
62
tests/png.rs
62
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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue