Compress to memory

This commit is contained in:
Matteo Paonessa 2023-02-25 20:39:52 +01:00
parent bba91dabfc
commit c22b390f34
11 changed files with 120 additions and 125 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "libcaesium"
version = "0.10.1"
version = "0.11.0"
authors = ["Matteo Paonessa <matteo.paonessa@gmail.com>"]
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"

View File

@ -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<u8>, parameters: &CSParameters) -> Result<Vec<u8>, 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(

View File

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

View File

@ -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<u8> = 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<Vec<u8>, io::Error> {
let rgba_bitmap = match lodepng::decode32_file(input_path) {
pub fn compress_to_memory(in_file: Vec<u8>, parameters: &CSParameters) -> Result<Vec<u8>, io::Error>
{
let optimized_png: Vec<u8> = if parameters.optimize {
lossless(in_file, parameters)?
} else {
lossy(in_file, parameters)?
};
Ok(optimized_png)
}
fn lossy(in_file: Vec<u8>, parameters: &CSParameters) -> Result<Vec<u8>, 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<Vec<u8>, io::E
Ok(png_vec)
}
fn lossless(input_path: String, parameters: &CSParameters) -> Result<Vec<u8>, io::Error> {
let in_file = fs::read(input_path)?;
fn lossless(in_file: Vec<u8>, parameters: &CSParameters) -> Result<Vec<u8>, 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<Vec<u8>, 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) {

View File

@ -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<u8>, parameters: &CSParameters) -> Result<Vec<u8>, 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())
}

7
tests/cleanup.rs Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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";
@ -34,7 +29,7 @@ fn compress_20() {
infer::get_from_path(output).unwrap().unwrap().mime_type(),
"image/webp"
);
cleanup(output)
remove_compressed_test_file(output)
}
#[test]
@ -54,7 +49,7 @@ fn compress_50() {
infer::get_from_path(output).unwrap().unwrap().mime_type(),
"image/webp"
);
cleanup(output)
remove_compressed_test_file(output)
}
#[test]
@ -74,7 +69,7 @@ fn compress_80() {
infer::get_from_path(output).unwrap().unwrap().mime_type(),
"image/webp"
);
cleanup(output)
remove_compressed_test_file(output)
}
#[test]
@ -94,7 +89,7 @@ fn compress_100() {
infer::get_from_path(output).unwrap().unwrap().mime_type(),
"image/webp"
);
cleanup(output)
remove_compressed_test_file(output)
}
#[test]
@ -114,7 +109,7 @@ fn optimize() {
infer::get_from_path(output).unwrap().unwrap().mime_type(),
"image/webp"
);
cleanup(output)
remove_compressed_test_file(output)
}
#[test]
@ -137,7 +132,7 @@ fn downscale_compress_80() {
"image/webp"
);
assert_eq!(image::image_dimensions(output).unwrap(), (150, 100));
cleanup(output)
remove_compressed_test_file(output)
}
#[test]
@ -160,5 +155,5 @@ fn downscale_optimize() {
"image/webp"
);
assert_eq!(image::image_dimensions(output).unwrap(), (150, 100));
cleanup(output)
remove_compressed_test_file(output)
}