El crate image lee y escribe los formatos habituales sin depender de bibliotecas del sistema. Combinado con rayon, generar miniaturas de una carpeta entera son unas veinte líneas y usa todos los núcleos.
También te puede interesar
Las dependencias
cargo add image cargo add rayon
[dependencies] image = "0.25.10" rayon = "1.12.0"
Miniaturas de toda una carpeta
use image::imageops::FilterType;
use rayon::prelude::*;
use std::fs;
use std::path::PathBuf;
fn main() -> Result<(), Box<dyn std::error::Error>> {
fs::create_dir_all("miniaturas")?;
let fotos: Vec<PathBuf> = fs::read_dir("fotos")?
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|p| p.extension().is_some_and(|x| x == "jpg"))
.collect();
let hechas: Vec<String> = fotos
.par_iter()
.filter_map(|ruta| {
let img = image::open(ruta).ok()?;
let mini = img.resize(320, 320, FilterType::Lanczos3);
let nombre = ruta.file_name()?.to_string_lossy().into_owned();
mini.save(format!("miniaturas/{nombre}")).ok()?;
Some(format!("{nombre}: {}x{} -> {}x{}", img.width(), img.height(), mini.width(), mini.height()))
})
.collect();
let mut ordenadas = hechas;
ordenadas.sort();
for l in &ordenadas { println!("{l}"); }
println!("{} miniaturas generadas", ordenadas.len());
Ok(())
}
cargo run --releasefoto1.jpg: 1600x1200 -> 320x240 foto2.jpg: 2400x1600 -> 320x213 foto3.jpg: 800x800 -> 320x320 3 miniaturas generadas
Fíjate en los tamaños resultantes: ninguno es 320×320 salvo el que ya era cuadrado. resize interpreta los dos números como un marco máximo y conserva la proporción, encajando la imagen dentro. Si lo que quieres es forzar las dimensiones exactas, deformando la imagen, el método es resize_exact.
El par_iter() hace que cada foto se procese en un núcleo distinto, que es donde esto gana de verdad: redimensionar es costoso y las fotos son independientes entre sí. Es el patrón del ejemplo de rayon aplicado a algo útil.
mini.save("x.png") escribe un PNG y mini.save("x.jpg") un JPEG, sin que haya que indicarlo. Como aquí se reutiliza el nombre original, las miniaturas salen en el mismo formato que las fotos. Para forzar otro, se cambia la extensión o se usa save_with_format.Operaciones habituales
| Qué quieres | Cómo |
|---|---|
| Abrir cualquier formato | image::open(ruta)? |
| Encajar en un marco | img.resize(an, al, filtro) |
| Tamaño exacto, deformando | img.resize_exact(an, al, filtro) |
| Recortar | img.crop_imm(x, y, an, al) |
| Girar | img.rotate90() |
| Blanco y negro | img.grayscale() |
| Guardar | img.save("salida.png")? |
FilterType::Nearest es el más rápido y el que peor se ve al reducir; Lanczos3, el usado aquí, es el más lento y el de mejor calidad. Para miniaturas que va a mirar una persona, Lanczos3 o CatmullRom; para una vista previa que se descarta enseguida, Triangle basta. La diferencia de tiempo entre el más rápido y el mejor puede ser de varias veces.El código de esta página se compiló y ejecutó con rustc 1.96.1, image 0.25.10 y rayon 1.12.0 antes de publicarla, sobre tres imágenes reales; la salida está copiada de la ejecución. Documentación oficial: image en docs.rs.