Image tools now actually process images: - Compress: re-encode JPEG with configurable quality - Resize: Lanczos3 resize with fit modes (inside/fill/crop) - Convert: cross-format conversion (JPEG/PNG/WebP/GIF/BMP) PDF tools: - Merge: combine multiple PDFs into one document - Split: extract page ranges via pagespec (1-3,5,7-9) - Images to PDF: embed images as JPEG in A4 pages with scaling - Compress: re-save PDF with optimized cross-reference table Also fix metrics format to emit default zero values. Co-Authored-By: Kilo <kilo@kilo.ai>
78 lines
2.7 KiB
Rust
78 lines
2.7 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use image::{DynamicImage, ImageFormat};
|
|
use image::imageops::FilterType;
|
|
|
|
use crate::nats::progress::ProgressReporter;
|
|
|
|
pub async fn process(
|
|
img: &DynamicImage,
|
|
options: &serde_json::Value,
|
|
output_dir: &PathBuf,
|
|
_progress: &ProgressReporter,
|
|
) -> Result<PathBuf, Box<dyn std::error::Error + Send + Sync>> {
|
|
let width = options.get("width").and_then(|v| v.as_u64()).map(|v| v as u32);
|
|
let height = options.get("height").and_then(|v| v.as_u64()).map(|v| v as u32);
|
|
let quality = options.get("quality").and_then(|v| v.as_u64()).unwrap_or(85) as u8;
|
|
let fit = options.get("fit").and_then(|v| v.as_str()).unwrap_or("inside");
|
|
let fmt = options.get("format").and_then(|v| v.as_str()).unwrap_or("jpeg");
|
|
|
|
let (new_w, new_h) = match (width, height) {
|
|
(Some(w), Some(h)) => (w, h),
|
|
(Some(w), None) => {
|
|
let ratio = w as f64 / img.width() as f64;
|
|
(w, (img.height() as f64 * ratio).round() as u32)
|
|
}
|
|
(None, Some(h)) => {
|
|
let ratio = h as f64 / img.height() as f64;
|
|
((img.width() as f64 * ratio).round() as u32, h)
|
|
}
|
|
(None, None) => (img.width(), img.height()),
|
|
};
|
|
|
|
let new_w = new_w.max(1).min(10000);
|
|
let new_h = new_h.max(1).min(10000);
|
|
|
|
let resized = match fit {
|
|
"fill" => img.resize_exact(new_w, new_h, FilterType::Lanczos3),
|
|
"crop" => img.resize_to_fill(new_w, new_h, FilterType::Lanczos3),
|
|
_ => img.resize(new_w, new_h, FilterType::Lanczos3), // "inside" = fit within bounds
|
|
};
|
|
|
|
let image_format = match fmt {
|
|
"png" => ImageFormat::Png,
|
|
"webp" => ImageFormat::WebP,
|
|
"gif" => ImageFormat::Gif,
|
|
"bmp" => ImageFormat::Bmp,
|
|
_ => ImageFormat::Jpeg,
|
|
};
|
|
|
|
let ext = match image_format {
|
|
ImageFormat::Jpeg => "jpg",
|
|
ImageFormat::Png => "png",
|
|
ImageFormat::WebP => "webp",
|
|
ImageFormat::Gif => "gif",
|
|
ImageFormat::Bmp => "bmp",
|
|
_ => "bin",
|
|
};
|
|
|
|
let output_path = output_dir.join(format!("resized.{}", ext));
|
|
|
|
// Save with appropriate encoder
|
|
match image_format {
|
|
ImageFormat::Jpeg => {
|
|
let mut rgb = resized.to_rgb8();
|
|
let mut file = std::fs::File::create(&output_path)?;
|
|
let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut file, quality);
|
|
encoder.encode(rgb.as_raw(), resized.width(), resized.height(), image::ExtendedColorType::Rgb8)?;
|
|
}
|
|
ImageFormat::Png | ImageFormat::WebP | ImageFormat::Gif | ImageFormat::Bmp => {
|
|
resized.save(&output_path)?;
|
|
}
|
|
_ => {
|
|
resized.save(&output_path)?;
|
|
}
|
|
}
|
|
|
|
Ok(output_path)
|
|
} |