Compress to memory
This commit is contained in:
parent
bba91dabfc
commit
c22b390f34
|
@ -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"
|
||||
|
|
17
src/jpeg.rs
17
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<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(
|
||||
|
|
|
@ -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;
|
||||
|
|
52
src/png.rs
52
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<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) {
|
||||
|
|
28
src/webp.rs
28
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<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())
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
14
tests/gif.rs
14
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";
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
26
tests/png.rs
26
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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue