use std::io::{BufReader, Cursor, Read, Seek, SeekFrom};
pub fn convert<F>(file: &mut F) -> Result<Vec<u8>, image::ImageError>
where
F: Seek + Read,
{
let quality = 85;
let speed = 6;
convert_with_quality_speed(file, quality, speed)
}
pub fn convert_with_quality_speed<F>(
file: &mut F,
quality: u8,
speed: u8,
) -> Result<Vec<u8>, image::ImageError>
where
F: Seek + Read,
{
let reader = BufReader::new(file);
let img = image::ImageReader::new(reader)
.with_guessed_format()?
.decode()?;
let mut buffer = Cursor::new(Vec::new());
let avif_encoder =
image::codecs::avif::AvifEncoder::new_with_speed_quality(&mut buffer, speed, quality);
img.write_with_encoder(avif_encoder)?;
let output = buffer.into_inner();
Ok(output)
}
pub fn convert_with_max_size<F>(
file: &mut F,
stride: Option<u8>,
speed: Option<u8>,
max_size: usize,
) -> Result<Vec<u8>, image::ImageError>
where
F: Seek + Read,
{
let mut quality = 100u8;
let stride = stride.unwrap_or(10);
let min_quality = 1u8;
let speed = speed.unwrap_or(6);
loop {
file.seek(SeekFrom::Start(0))?;
let output = convert_with_quality_speed(file, quality, speed)?;
if output.len() <= max_size {
if output.is_empty() {
return Err(image::ImageError::Encoding(
image::error::EncodingError::new(
image::error::ImageFormatHint::Name("AVIF".to_string()),
"Encoding produced zero bytes, possibly due to extremely low quality.",
),
));
}
return Ok(output);
}
if quality <= min_quality || quality < stride {
return Err(image::ImageError::Encoding(
image::error::EncodingError::new(
image::error::ImageFormatHint::Name("AVIF".to_string()),
format!(
"Could not meet max_size constraint ({}) even at lowest practical quality ({})",
max_size, quality
),
),
));
}
quality = quality.saturating_sub(stride);
quality = quality.max(min_quality);
}
}
#[cfg(test)]
mod tests {
use std::fs::File;
use super::*;
fn open_test_file(path: &str) -> File {
File::options()
.read(true)
.open(path)
.unwrap_or_else(|_| panic!("Failed to open test image: {}", path))
}
#[test]
fn test_png2avif() {
let mut file = open_test_file("./test/test.png");
let result = convert(&mut file);
assert!(result.is_ok(), "PNG Conversion failed: {:?}", result.err());
let output_data = result.unwrap();
assert!(!output_data.is_empty(), "PNG Output AVIF data is empty");
}
#[test]
fn test_jpeg2avif() {
let mut file = open_test_file("./test/test.jpg");
let result = convert(&mut file);
assert!(result.is_ok(), "JPEG Conversion failed: {:?}", result.err());
let output_data = result.unwrap();
assert!(!output_data.is_empty(), "JPEG Output AVIF data is empty");
}
#[test]
fn test_webp2avif() {
let mut file = open_test_file("./test/test.webp");
let result = convert(&mut file);
assert!(result.is_ok(), "WEBP Conversion failed: {:?}", result.err());
let output_data = result.unwrap();
assert!(!output_data.is_empty(), "WEBP Output AVIF data is empty");
}
#[test]
fn test_error_file() {
let mut file = open_test_file("./test/error.jpg");
let result = convert(&mut file);
assert!(
result.is_err(),
"Expected an error for unsupported/corrupt file type"
);
}
#[test]
fn test_max_size_success() {
let mut file = open_test_file("./test/test.png");
let max_size = 200 * 1024;
let speed = Some(5u8);
let stride = Some(10u8);
let result = convert_with_max_size(&mut file, stride, speed, max_size);
assert!(
result.is_ok(),
"convert_with_max_size failed when it should succeed: {:?}",
result.err()
);
let output_data = result.unwrap();
assert!(!output_data.is_empty(), "Max size output data is empty");
assert!(
output_data.len() <= max_size,
"Output size {} exceeds max_size {}",
output_data.len(),
max_size
);
println!(
"Max size success: Achieved size {} bytes (limit {})",
output_data.len(),
max_size
);
}
#[test]
fn test_max_size_fail() {
let mut file = open_test_file("./test/test.png");
let very_small_max_size = 100;
let speed = Some(5u8);
let stride = Some(20u8);
let result = convert_with_max_size(&mut file, stride, speed, very_small_max_size);
assert!(
result.is_err(),
"convert_with_max_size succeeded when it should fail (max_size too small)"
);
assert!(
matches!(result.err().unwrap(), image::ImageError::Encoding(_)),
"Expected an Encoding error due to size constraint"
);
println!(
"Max size fail: Correctly failed for max_size {}",
very_small_max_size
);
}
}