pub mod animated;
pub mod avif;
pub mod jxl;
pub mod prefetch;
pub mod process;
pub mod webp;
use crate::config::{ExtraParams, JxlifyConfig};
use crate::negotiation::NegotiatedFormat;
use image::ImageReader;
use std::path::Path;
use tracing::debug;
pub fn convert_to_target(
raw_path: &Path,
target_format: NegotiatedFormat,
output_path: &Path,
extra: &ExtraParams,
config: &JxlifyConfig,
) -> Result<(), String> {
let ext = crate::helper::get_extension(&raw_path.to_string_lossy());
if ext == "gif" && animated::is_animated_gif(raw_path) {
debug!("Processing animated GIF: {}", raw_path.display());
match target_format {
NegotiatedFormat::Webp => {
return animated::convert_animated_gif_to_webp(raw_path, output_path, config.quality);
}
NegotiatedFormat::Avif => {
return animated::convert_animated_gif_to_avif(raw_path, output_path, config.quality);
}
NegotiatedFormat::Jxl => {
return animated::convert_animated_gif_to_jxl(raw_path, output_path, config.quality);
}
NegotiatedFormat::Raw => {
return std::fs::copy(raw_path, output_path)
.map(|_| ())
.map_err(|e| format!("Failed to copy raw image: {}", e));
}
}
}
let dynamic_img = load_dynamic_image(raw_path)?;
let processed_img = process::preprocess_image(dynamic_img, extra, config);
match target_format {
NegotiatedFormat::Jxl => {
jxl::encode_jxl(&processed_img, output_path, config.quality)
}
NegotiatedFormat::Avif => {
avif::encode_avif(&processed_img, output_path, config.quality)
}
NegotiatedFormat::Webp => {
webp::encode_webp(&processed_img, output_path, config.quality)
}
NegotiatedFormat::Raw => {
if !extra.is_empty() {
let img_format = match ext.as_str() {
"png" => image::ImageFormat::Png,
"gif" => image::ImageFormat::Gif,
"bmp" => image::ImageFormat::Bmp,
_ => image::ImageFormat::Jpeg,
};
let mut out_bytes = Vec::new();
processed_img
.write_to(&mut std::io::Cursor::new(&mut out_bytes), img_format)
.map_err(|e| format!("Failed to encode resized raw image: {}", e))?;
crate::helper::atomic_write(output_path, &out_bytes)
.map_err(|e| format!("Failed to write resized raw image: {}", e))
} else {
std::fs::copy(raw_path, output_path)
.map(|_| ())
.map_err(|e| format!("Failed to copy raw image: {}", e))
}
}
}
}
pub fn load_dynamic_image(raw_path: &Path) -> Result<image::DynamicImage, String> {
let bytes = std::fs::read(raw_path)
.map_err(|e| format!("Failed to read image {}: {}", raw_path.display(), e))?;
let ext = crate::helper::get_extension(&raw_path.to_string_lossy());
load_dynamic_image_from_bytes(&bytes, Some(&ext))
}
pub fn load_dynamic_image_from_bytes(
bytes: &[u8],
ext_hint: Option<&str>,
) -> Result<image::DynamicImage, String> {
let guessed_ext = infer::get(bytes).map(|t| t.extension());
let effective_ext = guessed_ext
.or(ext_hint)
.unwrap_or_default()
.to_lowercase();
match effective_ext.as_str() {
"avif" => {
if let Ok(img) = decode_avif_bytes(bytes) {
return Ok(img);
}
}
"jxl" => {
if let Ok(img) = jxl::decode_jxl(bytes) {
return Ok(img);
}
}
_ => {}
}
let reader_res = ImageReader::new(std::io::Cursor::new(bytes))
.with_guessed_format();
if let Ok(reader) = reader_res {
if let Ok(img) = reader.decode() {
return Ok(img);
}
}
if effective_ext != "avif" {
if let Ok(img) = decode_avif_bytes(bytes) {
return Ok(img);
}
}
if effective_ext != "jxl" {
if let Ok(img) = jxl::decode_jxl(bytes) {
return Ok(img);
}
}
Err("Failed to decode image: unsupported format or corrupted data".to_string())
}
pub fn decode_avif_bytes(bytes: &[u8]) -> Result<image::DynamicImage, String> {
use zenpixels_convert::PixelBufferConvertTypedExt;
let decoded = zenavif::decode(bytes)
.map_err(|e| format!("zenavif failed to decode AVIF: {:?}", e))?;
let rgba_buf = decoded.to_rgba8();
let width = rgba_buf.width();
let height = rgba_buf.height();
let raw_bytes = rgba_buf.copy_to_contiguous_bytes();
let img_buffer = image::RgbaImage::from_raw(width, height, raw_bytes)
.ok_or_else(|| "Failed to construct RgbaImage from decoded AVIF buffer".to_string())?;
Ok(image::DynamicImage::ImageRgba8(img_buffer))
}