TIFF draft

This commit is contained in:
Matteo Paonessa 2024-02-13 20:17:05 +01:00
parent 09eb085bf3
commit 4c958aac64
12 changed files with 337 additions and 145 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "libcaesium" name = "libcaesium"
version = "0.14.1" version = "0.15.0"
authors = ["Matteo Paonessa <matteo.paonessa@gmail.com>"] authors = ["Matteo Paonessa <matteo.paonessa@gmail.com>"]
edition = "2021" edition = "2021"
categories = ["multimedia::images"] categories = ["multimedia::images"]
@ -21,11 +21,12 @@ repository = "https://github.com/Lymphatus/libcaesium"
license = "Apache-2.0" license = "Apache-2.0"
[features] [features]
default = ["jpg", "png", "webp", "gif", "parallel"] default = ["jpg", "png", "webp", "gif", "tiff", "parallel"]
jpg = ["dep:mozjpeg-sys", "image/jpeg"] jpg = ["dep:mozjpeg-sys", "image/jpeg"]
png = ["dep:oxipng", "dep:lodepng", "dep:imagequant", "image/png"] png = ["dep:oxipng", "dep:lodepng", "dep:imagequant", "image/png"]
webp = ["dep:webp", "image/webp"] webp = ["dep:webp", "image/webp"]
gif = ["dep:gifsicle", "image/gif"] gif = ["dep:gifsicle", "image/gif"]
tiff = ["dep:tiff", "image/tiff"]
parallel = ["oxipng?/parallel", "imagequant?/threads", "dssim/threads"] parallel = ["oxipng?/parallel", "imagequant?/threads", "dssim/threads"]
[dependencies] [dependencies]
@ -39,7 +40,8 @@ image = { version = "0.24.8", default-features = false }
img-parts = "0.3" img-parts = "0.3"
bytes = "1.5" bytes = "1.5"
lodepng = { version = "3.10", optional = true } 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] [dev-dependencies]
dssim = { version = "3.3", default-features = false, features = ["no-macos-vimage"] } dssim = { version = "3.3", default-features = false, features = ["no-macos-vimage"] }

15
src/error.rs Normal file
View File

@ -0,0 +1,15 @@
use core::fmt;
pub type Result<T> = std::result::Result<T, CaesiumError>;
#[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)
}
}

View File

@ -1,7 +1,7 @@
use std::ffi::CString; use std::ffi::CString;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use crate::utils::CaesiumError; use crate::error::CaesiumError;
use crate::CSParameters; use crate::CSParameters;
pub fn compress( pub fn compress(

111
src/interface.rs Normal file
View File

@ -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(),
&parameters,
))
}
#[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
}

View File

@ -10,8 +10,8 @@ use img_parts::{ImageEXIF, ImageICC};
use libc::free; use libc::free;
use mozjpeg_sys::*; use mozjpeg_sys::*;
use crate::error::CaesiumError;
use crate::resize::resize; use crate::resize::resize;
use crate::utils::CaesiumError;
use crate::CSParameters; use crate::CSParameters;
static mut JPEG_ERROR: c_int = 0; static mut JPEG_ERROR: c_int = 0;

View File

@ -1,47 +1,30 @@
extern crate alloc; extern crate alloc;
use crate::jpeg::ChromaSubsampling; 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 crate::utils::{get_filetype_from_memory, get_filetype_from_path, SupportedFileTypes};
use alloc::ffi::CString; use ::tiff::encoder::compression::DeflateLevel;
use std::ffi::CStr; use error::CaesiumError;
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::os::raw::c_char;
use std::{cmp, fs}; use std::{cmp, fs};
use utils::CaesiumError;
mod error;
#[cfg(feature = "gif")] #[cfg(feature = "gif")]
mod gif; mod gif;
mod interface;
#[cfg(feature = "jpg")] #[cfg(feature = "jpg")]
pub mod jpeg; pub mod jpeg;
#[cfg(feature = "png")] #[cfg(feature = "png")]
mod png; mod png;
mod resize; mod resize;
#[cfg(feature = "tiff")]
mod tiff;
mod utils; mod utils;
#[cfg(feature = "webp")] #[cfg(feature = "webp")]
mod 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)] #[derive(Copy, Clone)]
pub struct JpegParameters { pub struct JpegParameters {
pub quality: u32, pub quality: u32,
@ -64,12 +47,19 @@ pub struct WebPParameters {
pub quality: u32, pub quality: u32,
} }
#[derive(Copy, Clone)]
pub struct TiffParameters {
pub algorithm: TiffCompression,
pub deflate_level: DeflateLevel,
}
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct CSParameters { pub struct CSParameters {
pub jpeg: JpegParameters, pub jpeg: JpegParameters,
pub png: PngParameters, pub png: PngParameters,
pub gif: GifParameters, pub gif: GifParameters,
pub webp: WebPParameters, pub webp: WebPParameters,
pub tiff: TiffParameters,
pub keep_metadata: bool, pub keep_metadata: bool,
pub optimize: bool, pub optimize: bool,
pub width: u32, pub width: u32,
@ -82,21 +72,23 @@ pub fn initialize_parameters() -> CSParameters {
quality: 80, quality: 80,
chroma_subsampling: ChromaSubsampling::Auto, chroma_subsampling: ChromaSubsampling::Auto,
}; };
let png = PngParameters { let png = PngParameters {
quality: 80, quality: 80,
force_zopfli: false, force_zopfli: false,
}; };
let gif = GifParameters { quality: 80 }; let gif = GifParameters { quality: 80 };
let webp = WebPParameters { quality: 80 }; let webp = WebPParameters { quality: 80 };
let tiff = TiffParameters {
algorithm: Deflate,
deflate_level: DeflateLevel::Balanced,
};
CSParameters { CSParameters {
jpeg, jpeg,
png, png,
gif, gif,
webp, webp,
tiff,
keep_metadata: false, keep_metadata: false,
optimize: false, optimize: false,
width: 0, 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(),
&parameters,
))
}
#[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( pub fn compress(
input_path: String, input_path: String,
output_path: String, output_path: String,
parameters: &CSParameters, parameters: &CSParameters,
) -> utils::Result<()> { ) -> error::Result<()> {
validate_parameters(parameters)?; validate_parameters(parameters)?;
let file_type = get_filetype_from_path(&input_path); let file_type = get_filetype_from_path(&input_path);
@ -216,11 +122,15 @@ pub fn compress(
SupportedFileTypes::Gif => { SupportedFileTypes::Gif => {
gif::compress(input_path, output_path, parameters)?; gif::compress(input_path, output_path, parameters)?;
} }
#[cfg(feature = "tiff")]
SupportedFileTypes::Tiff => {
tiff::compress(input_path, output_path, parameters)?;
}
_ => { _ => {
return Err(CaesiumError { return Err(CaesiumError {
message: "Unknown file type".into(), message: "Unknown file type".into(),
code: 10000, code: 10000,
}) });
} }
} }
@ -230,7 +140,7 @@ pub fn compress(
pub fn compress_in_memory( pub fn compress_in_memory(
in_file: Vec<u8>, in_file: Vec<u8>,
parameters: &mut CSParameters, parameters: &mut CSParameters,
) -> utils::Result<Vec<u8>> { ) -> error::Result<Vec<u8>> {
let file_type = get_filetype_from_memory(in_file.as_slice()); let file_type = get_filetype_from_memory(in_file.as_slice());
let compressed_file = match file_type { let compressed_file = match file_type {
#[cfg(feature = "jpg")] #[cfg(feature = "jpg")]
@ -239,11 +149,13 @@ pub fn compress_in_memory(
SupportedFileTypes::Png => png::compress_to_memory(in_file, parameters)?, SupportedFileTypes::Png => png::compress_to_memory(in_file, parameters)?,
#[cfg(feature = "webp")] #[cfg(feature = "webp")]
SupportedFileTypes::WebP => webp::compress_to_memory(in_file, parameters)?, SupportedFileTypes::WebP => webp::compress_to_memory(in_file, parameters)?,
#[cfg(feature = "tiff")]
SupportedFileTypes::Tiff => tiff::compress_to_memory(in_file, parameters)?,
_ => { _ => {
return Err(CaesiumError { return Err(CaesiumError {
message: "Format not supported for compression to size".into(), message: "Format not supported for compression to size".into(),
code: 10200, code: 10200,
}) });
} }
}; };
@ -255,7 +167,7 @@ pub fn compress_to_size_in_memory(
parameters: &mut CSParameters, parameters: &mut CSParameters,
max_output_size: usize, max_output_size: usize,
return_smallest: bool, return_smallest: bool,
) -> utils::Result<Vec<u8>> { ) -> error::Result<Vec<u8>> {
let file_type = get_filetype_from_memory(&in_file); let file_type = get_filetype_from_memory(&in_file);
let tolerance_percentage = 2; let tolerance_percentage = 2;
@ -290,11 +202,12 @@ pub fn compress_to_size_in_memory(
parameters.webp.quality = quality; parameters.webp.quality = quality;
webp::compress_to_memory(in_file.clone(), parameters)? //TODO clone webp::compress_to_memory(in_file.clone(), parameters)? //TODO clone
} }
//TODO Tiff
_ => { _ => {
return Err(CaesiumError { return Err(CaesiumError {
message: "Format not supported for compression to size".into(), message: "Format not supported for compression to size".into(),
code: 10200, code: 10200,
}) });
} }
}; };
@ -340,7 +253,7 @@ pub fn compress_to_size(
parameters: &mut CSParameters, parameters: &mut CSParameters,
max_output_size: usize, max_output_size: usize,
return_smallest: bool, return_smallest: bool,
) -> utils::Result<()> { ) -> error::Result<()> {
let in_file = fs::read(input_path.clone()).map_err(|e| CaesiumError { let in_file = fs::read(input_path.clone()).map_err(|e| CaesiumError {
message: e.to_string(), message: e.to_string(),
code: 10201, code: 10201,
@ -369,7 +282,7 @@ pub fn compress_to_size(
Ok(()) Ok(())
} }
fn validate_parameters(parameters: &CSParameters) -> utils::Result<()> { fn validate_parameters(parameters: &CSParameters) -> error::Result<()> {
if parameters.jpeg.quality == 0 || parameters.jpeg.quality > 100 { if parameters.jpeg.quality == 0 || parameters.jpeg.quality > 100 {
return Err(CaesiumError { return Err(CaesiumError {
message: "Invalid JPEG quality value".into(), message: "Invalid JPEG quality value".into(),

View File

@ -6,8 +6,8 @@ use std::num::NonZeroU8;
use image::ImageOutputFormat; use image::ImageOutputFormat;
use oxipng::Deflaters::{Libdeflater, Zopfli}; use oxipng::Deflaters::{Libdeflater, Zopfli};
use crate::error::CaesiumError;
use crate::resize::resize; use crate::resize::resize;
use crate::utils::CaesiumError;
use crate::CSParameters; use crate::CSParameters;
pub fn compress( pub fn compress(

View File

@ -1,6 +1,6 @@
use std::io::Cursor; use std::io::Cursor;
use crate::utils::CaesiumError; use crate::error::CaesiumError;
use image::imageops::FilterType; use image::imageops::FilterType;
use image::io::Reader as ImageReader; use image::io::Reader as ImageReader;
use image::DynamicImage; use image::DynamicImage;

164
src/tiff.rs Normal file
View File

@ -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<u8>,
parameters: &CSParameters,
) -> Result<Vec<u8>, 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::<RGB8, Deflate>(
image.width(),
image.height(),
Deflate::with_level(parameters.tiff.deflate_level),
image.as_bytes(),
),
image::ColorType::Rgba8 => encoder.write_image_with_compression::<RGBA8, Deflate>(
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::<RGB8, Lzw>(
image.width(),
image.height(),
Lzw,
image.as_bytes(),
),
image::ColorType::Rgba8 => encoder.write_image_with_compression::<RGBA8, Lzw>(
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::<RGB8, Packbits>(
image.width(),
image.height(),
Packbits,
image.as_bytes(),
),
image::ColorType::Rgba8 => encoder.write_image_with_compression::<RGBA8, Packbits>(
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::<RGB8, Uncompressed>(
image.width(),
image.height(),
Uncompressed,
image.as_bytes(),
),
image::ColorType::Rgba8 => encoder.write_image_with_compression::<RGBA8, Uncompressed>(
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,
}),
}
}

View File

@ -1,4 +1,3 @@
use core::fmt;
use infer::Type; use infer::Type;
pub enum SupportedFileTypes { pub enum SupportedFileTypes {
@ -6,6 +5,7 @@ pub enum SupportedFileTypes {
Png, Png,
Gif, Gif,
WebP, WebP,
Tiff,
Unkn, Unkn,
} }
@ -32,20 +32,7 @@ fn match_supported_filetypes(ft: Type) -> SupportedFileTypes {
"image/png" => SupportedFileTypes::Png, "image/png" => SupportedFileTypes::Png,
"image/gif" => SupportedFileTypes::Gif, "image/gif" => SupportedFileTypes::Gif,
"image/webp" => SupportedFileTypes::WebP, "image/webp" => SupportedFileTypes::WebP,
"image/tiff" => SupportedFileTypes::Tiff,
_ => SupportedFileTypes::Unkn, _ => SupportedFileTypes::Unkn,
} }
} }
pub type Result<T> = std::result::Result<T, CaesiumError>;
#[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)
}
}

View File

@ -2,8 +2,8 @@ use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::ops::Deref; use std::ops::Deref;
use crate::error::CaesiumError;
use crate::resize::resize_image; use crate::resize::resize_image;
use crate::utils::CaesiumError;
use crate::CSParameters; use crate::CSParameters;
pub fn compress( pub fn compress(

Binary file not shown.