diff --git a/Cargo.toml b/Cargo.toml index 09627bc..d2594d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libcaesium" -version = "0.14.1" +version = "0.15.0" authors = ["Matteo Paonessa "] edition = "2021" categories = ["multimedia::images"] @@ -21,11 +21,12 @@ repository = "https://github.com/Lymphatus/libcaesium" license = "Apache-2.0" [features] -default = ["jpg", "png", "webp", "gif", "parallel"] +default = ["jpg", "png", "webp", "gif", "tiff", "parallel"] jpg = ["dep:mozjpeg-sys", "image/jpeg"] png = ["dep:oxipng", "dep:lodepng", "dep:imagequant", "image/png"] webp = ["dep:webp", "image/webp"] gif = ["dep:gifsicle", "image/gif"] +tiff = ["dep:tiff", "image/tiff"] parallel = ["oxipng?/parallel", "imagequant?/threads", "dssim/threads"] [dependencies] @@ -39,7 +40,8 @@ image = { version = "0.24.8", default-features = false } img-parts = "0.3" bytes = "1.5" lodepng = { version = "3.10", optional = true } -imagequant = {version = "4.3", optional = true, default-features = false} +imagequant = { version = "4.3", optional = true, default-features = false } +tiff = { version = "0.9", optional = true } [dev-dependencies] dssim = { version = "3.3", default-features = false, features = ["no-macos-vimage"] } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..3160956 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,15 @@ +use core::fmt; + +pub type Result = std::result::Result; + +#[derive(Debug, Clone)] +pub struct CaesiumError { + pub message: String, + pub code: u32, +} + +impl fmt::Display for CaesiumError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} [{}]", self.message, self.code) + } +} diff --git a/src/gif.rs b/src/gif.rs index c48061f..f452404 100644 --- a/src/gif.rs +++ b/src/gif.rs @@ -1,7 +1,7 @@ use std::ffi::CString; use std::os::raw::{c_int, c_void}; -use crate::utils::CaesiumError; +use crate::error::CaesiumError; use crate::CSParameters; pub fn compress( diff --git a/src/interface.rs b/src/interface.rs new file mode 100644 index 0000000..4607b59 --- /dev/null +++ b/src/interface.rs @@ -0,0 +1,111 @@ +use crate::jpeg::ChromaSubsampling; +use crate::{compress, compress_to_size, error, initialize_parameters, CSParameters}; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; + +#[repr(C)] +pub struct CCSParameters { + pub keep_metadata: bool, + pub jpeg_quality: u32, + pub jpeg_chroma_subsampling: u32, + pub png_quality: u32, + pub png_force_zopfli: bool, + pub gif_quality: u32, + pub webp_quality: u32, + pub optimize: bool, + pub width: u32, + pub height: u32, +} + +#[repr(C)] +pub struct CCSResult { + pub success: bool, + pub code: u32, + pub error_message: *const c_char, +} + +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn c_compress( + input_path: *const c_char, + output_path: *const c_char, + params: CCSParameters, +) -> CCSResult { + let parameters = c_set_parameters(params); + + c_return_result(compress( + CStr::from_ptr(input_path).to_str().unwrap().to_string(), + CStr::from_ptr(output_path).to_str().unwrap().to_string(), + ¶meters, + )) +} + +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn c_compress_to_size( + input_path: *const c_char, + output_path: *const c_char, + params: CCSParameters, + max_output_size: usize, + return_smallest: bool, +) -> CCSResult { + let mut parameters = c_set_parameters(params); + + c_return_result(compress_to_size( + CStr::from_ptr(input_path).to_str().unwrap().to_string(), + CStr::from_ptr(output_path).to_str().unwrap().to_string(), + &mut parameters, + max_output_size, + return_smallest, + )) +} + +fn c_return_result(result: error::Result<()>) -> CCSResult { + let mut error_message = CString::new("").unwrap(); + + match result { + Ok(_) => { + let em_pointer = error_message.as_ptr(); + std::mem::forget(error_message); + CCSResult { + success: true, + code: 0, + error_message: em_pointer, + } + } + Err(e) => { + error_message = CString::new(e.to_string()).unwrap(); + let em_pointer = error_message.as_ptr(); + std::mem::forget(error_message); + CCSResult { + success: false, + code: e.code, + error_message: em_pointer, + } + } + } +} + +fn c_set_parameters(params: CCSParameters) -> CSParameters { + let mut parameters = initialize_parameters(); + + parameters.jpeg.quality = params.jpeg_quality; + parameters.png.quality = params.png_quality; + 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; + + parameters.jpeg.chroma_subsampling = match params.jpeg_chroma_subsampling { + 444 => ChromaSubsampling::CS444, + 422 => ChromaSubsampling::CS422, + 420 => ChromaSubsampling::CS420, + 411 => ChromaSubsampling::CS411, + _ => ChromaSubsampling::Auto, + }; + + parameters +} diff --git a/src/jpeg.rs b/src/jpeg.rs index a21e9dc..3d8c4da 100644 --- a/src/jpeg.rs +++ b/src/jpeg.rs @@ -10,8 +10,8 @@ use img_parts::{ImageEXIF, ImageICC}; use libc::free; use mozjpeg_sys::*; +use crate::error::CaesiumError; use crate::resize::resize; -use crate::utils::CaesiumError; use crate::CSParameters; static mut JPEG_ERROR: c_int = 0; diff --git a/src/lib.rs b/src/lib.rs index f0dbe04..2a417fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,47 +1,30 @@ extern crate alloc; use crate::jpeg::ChromaSubsampling; +use crate::tiff::TiffCompression; +use crate::tiff::TiffCompression::Deflate; use crate::utils::{get_filetype_from_memory, get_filetype_from_path, SupportedFileTypes}; -use alloc::ffi::CString; -use std::ffi::CStr; +use ::tiff::encoder::compression::DeflateLevel; +use error::CaesiumError; use std::fs::File; use std::io::Write; -use std::os::raw::c_char; use std::{cmp, fs}; -use utils::CaesiumError; +mod error; #[cfg(feature = "gif")] mod gif; +mod interface; #[cfg(feature = "jpg")] pub mod jpeg; #[cfg(feature = "png")] mod png; mod resize; +#[cfg(feature = "tiff")] +mod tiff; mod utils; #[cfg(feature = "webp")] mod webp; -#[repr(C)] -pub struct CCSParameters { - pub keep_metadata: bool, - pub jpeg_quality: u32, - pub jpeg_chroma_subsampling: u32, - pub png_quality: u32, - pub png_force_zopfli: bool, - pub gif_quality: u32, - pub webp_quality: u32, - pub optimize: bool, - pub width: u32, - pub height: u32, -} - -#[repr(C)] -pub struct CCSResult { - pub success: bool, - pub code: u32, - pub error_message: *const c_char, -} - #[derive(Copy, Clone)] pub struct JpegParameters { pub quality: u32, @@ -64,12 +47,19 @@ pub struct WebPParameters { pub quality: u32, } +#[derive(Copy, Clone)] +pub struct TiffParameters { + pub algorithm: TiffCompression, + pub deflate_level: DeflateLevel, +} + #[derive(Copy, Clone)] pub struct CSParameters { pub jpeg: JpegParameters, pub png: PngParameters, pub gif: GifParameters, pub webp: WebPParameters, + pub tiff: TiffParameters, pub keep_metadata: bool, pub optimize: bool, pub width: u32, @@ -82,21 +72,23 @@ pub fn initialize_parameters() -> CSParameters { quality: 80, chroma_subsampling: ChromaSubsampling::Auto, }; - let png = PngParameters { quality: 80, force_zopfli: false, }; - let gif = GifParameters { quality: 80 }; - let webp = WebPParameters { quality: 80 }; + let tiff = TiffParameters { + algorithm: Deflate, + deflate_level: DeflateLevel::Balanced, + }; CSParameters { jpeg, png, gif, webp, + tiff, keep_metadata: false, optimize: false, width: 0, @@ -105,97 +97,11 @@ pub fn initialize_parameters() -> CSParameters { } } -#[no_mangle] -#[allow(clippy::missing_safety_doc)] -pub unsafe extern "C" fn c_compress( - input_path: *const c_char, - output_path: *const c_char, - params: CCSParameters, -) -> CCSResult { - let parameters = c_set_parameters(params); - - c_return_result(compress( - CStr::from_ptr(input_path).to_str().unwrap().to_string(), - CStr::from_ptr(output_path).to_str().unwrap().to_string(), - ¶meters, - )) -} - -#[no_mangle] -#[allow(clippy::missing_safety_doc)] -pub unsafe extern "C" fn c_compress_to_size( - input_path: *const c_char, - output_path: *const c_char, - params: CCSParameters, - max_output_size: usize, - return_smallest: bool, -) -> CCSResult { - let mut parameters = c_set_parameters(params); - - c_return_result(compress_to_size( - CStr::from_ptr(input_path).to_str().unwrap().to_string(), - CStr::from_ptr(output_path).to_str().unwrap().to_string(), - &mut parameters, - max_output_size, - return_smallest, - )) -} - -fn c_return_result(result: utils::Result<()>) -> CCSResult { - let mut error_message = CString::new("").unwrap(); - - match result { - Ok(_) => { - let em_pointer = error_message.as_ptr(); - std::mem::forget(error_message); - CCSResult { - success: true, - code: 0, - error_message: em_pointer, - } - } - Err(e) => { - error_message = CString::new(e.to_string()).unwrap(); - let em_pointer = error_message.as_ptr(); - std::mem::forget(error_message); - CCSResult { - success: false, - code: e.code, - error_message: em_pointer, - } - } - } -} - -fn c_set_parameters(params: CCSParameters) -> CSParameters { - let mut parameters = initialize_parameters(); - - parameters.jpeg.quality = params.jpeg_quality; - parameters.png.quality = params.png_quality; - 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; - - parameters.jpeg.chroma_subsampling = match params.jpeg_chroma_subsampling { - 444 => ChromaSubsampling::CS444, - 422 => ChromaSubsampling::CS422, - 420 => ChromaSubsampling::CS420, - 411 => ChromaSubsampling::CS411, - _ => ChromaSubsampling::Auto, - }; - - parameters -} - pub fn compress( input_path: String, output_path: String, parameters: &CSParameters, -) -> utils::Result<()> { +) -> error::Result<()> { validate_parameters(parameters)?; let file_type = get_filetype_from_path(&input_path); @@ -216,11 +122,15 @@ pub fn compress( SupportedFileTypes::Gif => { gif::compress(input_path, output_path, parameters)?; } + #[cfg(feature = "tiff")] + SupportedFileTypes::Tiff => { + tiff::compress(input_path, output_path, parameters)?; + } _ => { return Err(CaesiumError { message: "Unknown file type".into(), code: 10000, - }) + }); } } @@ -230,7 +140,7 @@ pub fn compress( pub fn compress_in_memory( in_file: Vec, parameters: &mut CSParameters, -) -> utils::Result> { +) -> error::Result> { let file_type = get_filetype_from_memory(in_file.as_slice()); let compressed_file = match file_type { #[cfg(feature = "jpg")] @@ -239,11 +149,13 @@ pub fn compress_in_memory( SupportedFileTypes::Png => png::compress_to_memory(in_file, parameters)?, #[cfg(feature = "webp")] SupportedFileTypes::WebP => webp::compress_to_memory(in_file, parameters)?, + #[cfg(feature = "tiff")] + SupportedFileTypes::Tiff => tiff::compress_to_memory(in_file, parameters)?, _ => { return Err(CaesiumError { message: "Format not supported for compression to size".into(), code: 10200, - }) + }); } }; @@ -255,7 +167,7 @@ pub fn compress_to_size_in_memory( parameters: &mut CSParameters, max_output_size: usize, return_smallest: bool, -) -> utils::Result> { +) -> error::Result> { let file_type = get_filetype_from_memory(&in_file); let tolerance_percentage = 2; @@ -290,11 +202,12 @@ pub fn compress_to_size_in_memory( parameters.webp.quality = quality; webp::compress_to_memory(in_file.clone(), parameters)? //TODO clone } + //TODO Tiff _ => { return Err(CaesiumError { message: "Format not supported for compression to size".into(), code: 10200, - }) + }); } }; @@ -340,7 +253,7 @@ pub fn compress_to_size( parameters: &mut CSParameters, max_output_size: usize, return_smallest: bool, -) -> utils::Result<()> { +) -> error::Result<()> { let in_file = fs::read(input_path.clone()).map_err(|e| CaesiumError { message: e.to_string(), code: 10201, @@ -369,7 +282,7 @@ pub fn compress_to_size( Ok(()) } -fn validate_parameters(parameters: &CSParameters) -> utils::Result<()> { +fn validate_parameters(parameters: &CSParameters) -> error::Result<()> { if parameters.jpeg.quality == 0 || parameters.jpeg.quality > 100 { return Err(CaesiumError { message: "Invalid JPEG quality value".into(), diff --git a/src/png.rs b/src/png.rs index 189a66e..db5756c 100644 --- a/src/png.rs +++ b/src/png.rs @@ -6,8 +6,8 @@ use std::num::NonZeroU8; use image::ImageOutputFormat; use oxipng::Deflaters::{Libdeflater, Zopfli}; +use crate::error::CaesiumError; use crate::resize::resize; -use crate::utils::CaesiumError; use crate::CSParameters; pub fn compress( diff --git a/src/resize.rs b/src/resize.rs index 755e6ca..130b4c7 100644 --- a/src/resize.rs +++ b/src/resize.rs @@ -1,6 +1,6 @@ use std::io::Cursor; -use crate::utils::CaesiumError; +use crate::error::CaesiumError; use image::imageops::FilterType; use image::io::Reader as ImageReader; use image::DynamicImage; diff --git a/src/tiff.rs b/src/tiff.rs new file mode 100644 index 0000000..5eae756 --- /dev/null +++ b/src/tiff.rs @@ -0,0 +1,164 @@ +use crate::error::CaesiumError; +use crate::CSParameters; +use image::ImageFormat::Tiff; +use std::fs::File; +use std::io::{Cursor, Read, Write}; +use tiff::encoder::colortype::{RGB8, RGBA8}; +use tiff::encoder::compression::{Deflate, Lzw, Packbits, Uncompressed}; +use tiff::encoder::TiffEncoder; +use crate::resize::resize_image; + +#[derive(Copy, Clone, PartialEq)] +pub enum TiffCompression { + Lzw, + Deflate, + Packbits, + Uncompressed, +} + +pub fn compress( + input_path: String, + output_path: String, + parameters: &CSParameters, +) -> Result<(), CaesiumError> { + let mut input_file = File::open(input_path).map_err(|e| CaesiumError { + message: e.to_string(), + code: 20500, + })?; + + let mut input_data = Vec::new(); + input_file + .read_to_end(&mut input_data) + .map_err(|e| CaesiumError { + message: e.to_string(), + code: 20501, + })?; + + let mut output_file = File::create(output_path).map_err(|e| CaesiumError { + message: e.to_string(), + code: 20502, + })?; + let compressed_image = compress_to_memory(input_data, parameters)?; + output_file + .write_all(&compressed_image) + .map_err(|e| CaesiumError { + message: e.to_string(), + code: 20503, + })?; + Ok(()) +} + +pub fn compress_to_memory( + in_file: Vec, + parameters: &CSParameters, +) -> Result, CaesiumError> { + let mut image = image::load_from_memory_with_format(in_file.as_slice(), Tiff).map_err(|e| { + CaesiumError { + message: e.to_string(), + code: 20504, + } + })?; + + if parameters.width > 0 || parameters.height > 0 { + image = resize_image(image, parameters.width, parameters.height); + } + + let color_type = image.color(); + let output_buff = vec![]; + let mut output_stream = Cursor::new(output_buff); + let mut encoder = TiffEncoder::new(&mut output_stream).map_err(|e| CaesiumError { + message: e.to_string(), + code: 20505, + })?; + let compression_result = match parameters.tiff.algorithm { + TiffCompression::Deflate => match color_type { + image::ColorType::Rgb8 => encoder.write_image_with_compression::( + image.width(), + image.height(), + Deflate::with_level(parameters.tiff.deflate_level), + image.as_bytes(), + ), + image::ColorType::Rgba8 => encoder.write_image_with_compression::( + image.width(), + image.height(), + Deflate::with_level(parameters.tiff.deflate_level), + image.as_bytes(), + ), + _ => { + return Err(CaesiumError { + message: format!("Unsupported TIFF color type ({:?})", color_type).to_string(), + code: 20506, + }); + } + }, + + TiffCompression::Lzw => match color_type { + image::ColorType::Rgb8 => encoder.write_image_with_compression::( + image.width(), + image.height(), + Lzw, + image.as_bytes(), + ), + image::ColorType::Rgba8 => encoder.write_image_with_compression::( + image.width(), + image.height(), + Lzw, + image.as_bytes(), + ), + _ => { + return Err(CaesiumError { + message: format!("Unsupported TIFF color type ({:?})", color_type).to_string(), + code: 20506, + }); + } + }, + TiffCompression::Packbits => match color_type { + image::ColorType::Rgb8 => encoder.write_image_with_compression::( + image.width(), + image.height(), + Packbits, + image.as_bytes(), + ), + image::ColorType::Rgba8 => encoder.write_image_with_compression::( + image.width(), + image.height(), + Packbits, + image.as_bytes(), + ), + _ => { + return Err(CaesiumError { + message: format!("Unsupported TIFF color type ({:?})", color_type).to_string(), + code: 20506, + }); + } + }, + TiffCompression::Uncompressed => match color_type { + image::ColorType::Rgb8 => encoder.write_image_with_compression::( + image.width(), + image.height(), + Uncompressed, + image.as_bytes(), + ), + image::ColorType::Rgba8 => encoder.write_image_with_compression::( + image.width(), + image.height(), + Uncompressed, + image.as_bytes(), + ), + _ => { + return Err(CaesiumError { + message: format!("Unsupported TIFF color type ({:?})", color_type).to_string(), + code: 20506, + }); + } + }, + }; + + match compression_result { + Ok(_) => Ok(output_stream.get_ref().to_vec()), + Err(e) => Err(CaesiumError { + message: e.to_string(), + code: 20507, + }), + } +} diff --git a/src/utils.rs b/src/utils.rs index 800a556..f8e9005 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,3 @@ -use core::fmt; use infer::Type; pub enum SupportedFileTypes { @@ -6,6 +5,7 @@ pub enum SupportedFileTypes { Png, Gif, WebP, + Tiff, Unkn, } @@ -32,20 +32,7 @@ fn match_supported_filetypes(ft: Type) -> SupportedFileTypes { "image/png" => SupportedFileTypes::Png, "image/gif" => SupportedFileTypes::Gif, "image/webp" => SupportedFileTypes::WebP, + "image/tiff" => SupportedFileTypes::Tiff, _ => SupportedFileTypes::Unkn, } } - -pub type Result = std::result::Result; - -#[derive(Debug, Clone)] -pub struct CaesiumError { - pub message: String, - pub code: u32, -} - -impl fmt::Display for CaesiumError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} [{}]", self.message, self.code) - } -} diff --git a/src/webp.rs b/src/webp.rs index fee04e8..a9ec13b 100644 --- a/src/webp.rs +++ b/src/webp.rs @@ -2,8 +2,8 @@ use std::fs::File; use std::io::{Read, Write}; use std::ops::Deref; +use crate::error::CaesiumError; use crate::resize::resize_image; -use crate::utils::CaesiumError; use crate::CSParameters; pub fn compress( diff --git a/tests/samples/uncompressed_rgba8.tiff b/tests/samples/uncompressed_rgba8.tiff new file mode 100644 index 0000000..e882645 Binary files /dev/null and b/tests/samples/uncompressed_rgba8.tiff differ