Basic TIFF support

This commit is contained in:
Matteo Paonessa 2024-02-15 14:00:54 +01:00
parent ea9452ddf2
commit 77ba5b7bd0
11 changed files with 312 additions and 73 deletions

View File

@ -1,8 +1,9 @@
use crate::jpeg::ChromaSubsampling;
use crate::{compress, compress_to_size, error, initialize_parameters, CSParameters};
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::os::raw::c_char; use std::os::raw::c_char;
use crate::jpeg::ChromaSubsampling;
use crate::{compress, compress_to_size, error, initialize_parameters, CSParameters};
#[repr(C)] #[repr(C)]
pub struct CCSParameters { pub struct CCSParameters {
pub keep_metadata: bool, pub keep_metadata: bool,

View File

@ -1,15 +1,19 @@
extern crate alloc; 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 ::tiff::encoder::compression::DeflateLevel;
use error::CaesiumError;
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::{cmp, fs}; use std::{cmp, fs};
use ::tiff::encoder::compression::DeflateLevel;
use ::tiff::encoder::compression::DeflateLevel::Best;
use error::CaesiumError;
use crate::jpeg::ChromaSubsampling;
use crate::tiff::TiffCompression;
use crate::tiff::TiffCompression::{Deflate, Lzw, Packbits};
use crate::utils::{get_filetype_from_memory, get_filetype_from_path, SupportedFileTypes};
mod error; mod error;
#[cfg(feature = "gif")] #[cfg(feature = "gif")]
mod gif; mod gif;
@ -20,7 +24,7 @@ pub mod jpeg;
mod png; mod png;
mod resize; mod resize;
#[cfg(feature = "tiff")] #[cfg(feature = "tiff")]
mod tiff; pub mod tiff;
mod utils; mod utils;
#[cfg(feature = "webp")] #[cfg(feature = "webp")]
mod webp; mod webp;
@ -128,7 +132,7 @@ pub fn compress(
} }
_ => { _ => {
return Err(CaesiumError { return Err(CaesiumError {
message: "Unknown file type".into(), message: "Unknown file type or file not found".into(),
code: 10000, code: 10000,
}); });
} }
@ -178,70 +182,89 @@ pub fn compress_to_size_in_memory(
let max_tries: u32 = 10; let max_tries: u32 = 10;
let mut tries: u32 = 0; let mut tries: u32 = 0;
let compressed_file = loop { let compressed_file = match file_type {
if tries >= max_tries { #[cfg(feature = "tiff")]
return Err(CaesiumError { SupportedFileTypes::Tiff => {
message: "Max tries reached".into(), let algorithms = [
code: 10201, Lzw,
}); Packbits
];
parameters.tiff.deflate_level = Best;
parameters.tiff.algorithm = Deflate;
let mut smallest_result = tiff::compress_to_memory(in_file.clone(), parameters)?; //TODO clone
for tc in algorithms {
parameters.tiff.algorithm = tc;
let result = tiff::compress_to_memory(in_file.clone(), parameters)?; //TODO clone
if result.len() < smallest_result.len() {
smallest_result = result;
}
}
smallest_result
} }
_ => loop {
let compressed_file = match file_type { if tries >= max_tries {
#[cfg(feature = "jpg")]
SupportedFileTypes::Jpeg => {
parameters.jpeg.quality = quality;
jpeg::compress_to_memory(in_file.clone(), parameters)? //TODO clone
}
#[cfg(feature = "png")]
SupportedFileTypes::Png => {
parameters.png.quality = quality;
png::compress_to_memory(in_file.clone(), parameters)? //TODO clone
}
#[cfg(feature = "webp")]
SupportedFileTypes::WebP => {
parameters.webp.quality = quality;
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: "Max tries reached".into(),
code: 10200, code: 10201,
}); });
} }
};
let compressed_file_size = compressed_file.len(); let compressed_file = match file_type {
#[cfg(feature = "jpg")]
SupportedFileTypes::Jpeg => {
parameters.jpeg.quality = quality;
jpeg::compress_to_memory(in_file.clone(), parameters)? //TODO clone
}
#[cfg(feature = "png")]
SupportedFileTypes::Png => {
parameters.png.quality = quality;
png::compress_to_memory(in_file.clone(), parameters)? //TODO clone
}
#[cfg(feature = "webp")]
SupportedFileTypes::WebP => {
parameters.webp.quality = quality;
webp::compress_to_memory(in_file.clone(), parameters)? //TODO clone
}
_ => {
return Err(CaesiumError {
message: "Format not supported for compression to size".into(),
code: 10200,
});
}
};
if compressed_file_size <= max_output_size let compressed_file_size = compressed_file.len();
&& max_output_size - compressed_file_size < tolerance
{
break compressed_file;
}
if compressed_file_size <= max_output_size { if compressed_file_size <= max_output_size
last_less = quality; && max_output_size - compressed_file_size < tolerance
} else { {
last_high = quality; break compressed_file;
}
let last_quality = quality;
quality = cmp::max(1, cmp::min(100, (last_high + last_less) / 2));
if last_quality == quality {
if quality == 1 && last_high == 1 {
return if return_smallest {
Ok(compressed_file)
} else {
Err(CaesiumError {
message: "Cannot compress to desired quality".into(),
code: 10202,
})
};
} }
break compressed_file; if compressed_file_size <= max_output_size {
} last_less = quality;
} else {
last_high = quality;
}
let last_quality = quality;
quality = cmp::max(1, cmp::min(100, (last_high + last_less) / 2));
if last_quality == quality {
if quality == 1 && last_high == 1 {
return if return_smallest {
Ok(compressed_file)
} else {
Err(CaesiumError {
message: "Cannot compress to desired quality".into(),
code: 10202,
})
};
}
tries += 1; break compressed_file;
}
tries += 1;
},
}; };
Ok(compressed_file) Ok(compressed_file)

View File

@ -15,6 +15,6 @@ fn main() -> ExitCode {
Err(e) => { Err(e) => {
eprintln!("{}", e); eprintln!("{}", e);
ExitCode::FAILURE ExitCode::FAILURE
}, }
} }
} }

View File

@ -1,10 +1,11 @@
use std::io::Cursor; use std::io::Cursor;
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;
use crate::error::CaesiumError;
pub fn resize( pub fn resize(
image_buffer: Vec<u8>, image_buffer: Vec<u8>,
width: u32, width: u32,

View File

@ -1,12 +1,14 @@
use crate::error::CaesiumError;
use crate::CSParameters;
use image::ImageFormat::Tiff;
use std::fs::File; use std::fs::File;
use std::io::{Cursor, Read, Write}; use std::io::{Cursor, Read, Write};
use image::ImageFormat::Tiff;
use tiff::encoder::colortype::{RGB8, RGBA8}; use tiff::encoder::colortype::{RGB8, RGBA8};
use tiff::encoder::compression::{Deflate, Lzw, Packbits, Uncompressed}; use tiff::encoder::compression::{Deflate, Lzw, Packbits, Uncompressed};
use tiff::encoder::TiffEncoder; use tiff::encoder::TiffEncoder;
use crate::error::CaesiumError;
use crate::resize::resize_image; use crate::resize::resize_image;
use crate::CSParameters;
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, PartialEq)]
pub enum TiffCompression { pub enum TiffCompression {
@ -40,7 +42,7 @@ pub fn compress(
message: e.to_string(), message: e.to_string(),
code: 20502, code: 20502,
})?; })?;
output_file output_file
.write_all(&compressed_image) .write_all(&compressed_image)
.map_err(|e| CaesiumError { .map_err(|e| CaesiumError {
@ -72,6 +74,7 @@ pub fn compress_to_memory(
message: e.to_string(), message: e.to_string(),
code: 20505, code: 20505,
})?; })?;
let compression_result = match parameters.tiff.algorithm { let compression_result = match parameters.tiff.algorithm {
TiffCompression::Deflate => match color_type { TiffCompression::Deflate => match color_type {
image::ColorType::Rgb8 => encoder.write_image_with_compression::<RGB8, Deflate>( image::ColorType::Rgb8 => encoder.write_image_with_compression::<RGB8, Deflate>(
@ -163,4 +166,4 @@ pub fn compress_to_memory(
code: 20507, code: 20507,
}), }),
} }
} }

View File

@ -25,12 +25,12 @@ pub fn compress(
})?; })?;
let compressed_image = compress_to_memory(input_data, parameters)?; let compressed_image = compress_to_memory(input_data, parameters)?;
let mut output_file = File::create(output_path).map_err(|e| CaesiumError { let mut output_file = File::create(output_path).map_err(|e| CaesiumError {
message: e.to_string(), message: e.to_string(),
code: 20302, code: 20302,
})?; })?;
output_file output_file
.write_all(&compressed_image) .write_all(&compressed_image)
.map_err(|e| CaesiumError { .map_err(|e| CaesiumError {

BIN
tests/samples/rgb8.tif Normal file

Binary file not shown.

BIN
tests/samples/rgba8.tif Normal file

Binary file not shown.

Binary file not shown.

211
tests/tiff.rs Normal file
View File

@ -0,0 +1,211 @@
use std::sync::Once;
use tiff::encoder::compression::DeflateLevel::Balanced;
use crate::cleanup::remove_compressed_test_file;
mod cleanup;
static INIT: Once = Once::new();
pub fn initialize(file: &str) {
INIT.call_once(|| {
remove_compressed_test_file(file);
});
}
#[test]
fn rgb8_uncompressed() {
let output = "tests/samples/output/uncompressed_rgb8.tif";
initialize(output);
let mut params = caesium::initialize_parameters();
params.tiff.algorithm = caesium::tiff::TiffCompression::Uncompressed;
caesium::compress(
String::from("tests/samples/rgb8.tif"),
String::from(output),
&params,
)
.unwrap();
assert!(std::path::Path::new(output).exists());
assert_eq!(
infer::get_from_path(output).unwrap().unwrap().mime_type(),
"image/tiff"
);
remove_compressed_test_file(output)
}
#[test]
fn rgba8_uncompressed() {
let output = "tests/samples/output/uncompressed_rgba8.tif";
initialize(output);
let mut params = caesium::initialize_parameters();
params.tiff.algorithm = caesium::tiff::TiffCompression::Uncompressed;
caesium::compress(
String::from("tests/samples/rgba8.tif"),
String::from(output),
&params,
)
.unwrap();
assert!(std::path::Path::new(output).exists());
assert_eq!(
infer::get_from_path(output).unwrap().unwrap().mime_type(),
"image/tiff"
);
remove_compressed_test_file(output)
}
#[test]
fn rgb8_deflate() {
let output = "tests/samples/output/deflate_rgb8.tif";
initialize(output);
let mut params = caesium::initialize_parameters();
params.tiff.algorithm = caesium::tiff::TiffCompression::Deflate;
params.tiff.deflate_level = Balanced;
caesium::compress(
String::from("tests/samples/rgb8.tif"),
String::from(output),
&params,
)
.unwrap();
assert!(std::path::Path::new(output).exists());
assert_eq!(
infer::get_from_path(output).unwrap().unwrap().mime_type(),
"image/tiff"
);
remove_compressed_test_file(output)
}
#[test]
fn rgba8_deflate() {
let output = "tests/samples/output/deflate_rgba8.tif";
initialize(output);
let mut params = caesium::initialize_parameters();
params.tiff.algorithm = caesium::tiff::TiffCompression::Deflate;
params.tiff.deflate_level = Balanced;
caesium::compress(
String::from("tests/samples/rgba8.tif"),
String::from(output),
&params,
)
.unwrap();
assert!(std::path::Path::new(output).exists());
assert_eq!(
infer::get_from_path(output).unwrap().unwrap().mime_type(),
"image/tiff"
);
remove_compressed_test_file(output)
}
#[test]
fn rgb8_lzw() {
let output = "tests/samples/output/lzw_rgb8.tif";
initialize(output);
let mut params = caesium::initialize_parameters();
params.tiff.algorithm = caesium::tiff::TiffCompression::Lzw;
caesium::compress(
String::from("tests/samples/rgb8.tif"),
String::from(output),
&params,
)
.unwrap();
assert!(std::path::Path::new(output).exists());
assert_eq!(
infer::get_from_path(output).unwrap().unwrap().mime_type(),
"image/tiff"
);
remove_compressed_test_file(output)
}
#[test]
fn rgba8_lzw() {
let output = "tests/samples/output/lzw_rgba8.tif";
initialize(output);
let mut params = caesium::initialize_parameters();
params.tiff.algorithm = caesium::tiff::TiffCompression::Lzw;
caesium::compress(
String::from("tests/samples/rgba8.tif"),
String::from(output),
&params,
)
.unwrap();
assert!(std::path::Path::new(output).exists());
assert_eq!(
infer::get_from_path(output).unwrap().unwrap().mime_type(),
"image/tiff"
);
remove_compressed_test_file(output)
}
#[test]
fn rgb8_packbits() {
let output = "tests/samples/output/packbits_rgb8.tif";
initialize(output);
let mut params = caesium::initialize_parameters();
params.tiff.algorithm = caesium::tiff::TiffCompression::Packbits;
caesium::compress(
String::from("tests/samples/rgb8.tif"),
String::from(output),
&params,
)
.unwrap();
assert!(std::path::Path::new(output).exists());
assert_eq!(
infer::get_from_path(output).unwrap().unwrap().mime_type(),
"image/tiff"
);
remove_compressed_test_file(output)
}
#[test]
fn rgba8_packbits() {
let output = "tests/samples/output/packbits_rgba8.tif";
initialize(output);
let mut params = caesium::initialize_parameters();
params.tiff.algorithm = caesium::tiff::TiffCompression::Packbits;
caesium::compress(
String::from("tests/samples/rgba8.tif"),
String::from(output),
&params,
)
.unwrap();
assert!(std::path::Path::new(output).exists());
assert_eq!(
infer::get_from_path(output).unwrap().unwrap().mime_type(),
"image/tiff"
);
remove_compressed_test_file(output)
}
#[test]
fn rgb8_downscale() {
let output = "tests/samples/output/downscale_rgb8.tif";
initialize(output);
let mut params = caesium::initialize_parameters();
params.tiff.algorithm = caesium::tiff::TiffCompression::Lzw;
params.width = 50;
params.height = 20;
caesium::compress(
String::from("tests/samples/rgb8.tif"),
String::from(output),
&params,
)
.unwrap();
assert!(std::path::Path::new(output).exists());
assert_eq!(
infer::get_from_path(output).unwrap().unwrap().mime_type(),
"image/tiff"
);
assert_eq!(image::image_dimensions(output).unwrap(), (50, 20));
remove_compressed_test_file(output)
}
#[test]
fn unsupported() {
let output = "tests/samples/output/unsupported.tif";
initialize(output);
let mut params = caesium::initialize_parameters();
params.tiff.algorithm = caesium::tiff::TiffCompression::Lzw;
assert!(caesium::compress(
String::from("tests/samples/unsupported.tif"),
String::from(output),
&params,
).is_err());
}