use crate::{
Result, formats,
render::{self, RenderOptions},
saved_preview,
};
use image::{DynamicImage, Rgba, RgbaImage};
use std::path::Path;
pub fn thumbnail(path: &Path, options: &RenderOptions) -> Result<RgbaImage> {
let extension = formats::extension(path)?;
if saved_preview::EXTENSIONS.contains(&extension.as_str()) {
validate_dimensions(options)?;
match saved_preview::extract(path) {
Ok(Some(source)) => return Ok(fit_image(&DynamicImage::ImageRgba8(source), options)),
Ok(None) => {}
Err(saved_error)
if formats::NATIVE_GEOMETRY_EXTENSIONS.contains(&extension.as_str()) =>
{
return geometry_thumbnail(path, options).map_err(|geometry_error| {
format!("native geometry failed: {geometry_error}; saved preview unavailable: {saved_error}").into()
});
}
Err(error) => return Err(error),
}
if formats::NATIVE_GEOMETRY_EXTENSIONS.contains(&extension.as_str()) {
return geometry_thumbnail(path, options);
}
return Err(saved_preview::NoSavedPreview.into());
}
if formats::IMAGE_EXTENSIONS.contains(&extension.as_str()) {
return image_thumbnail(path, options);
}
geometry_thumbnail(path, options)
}
pub fn geometry_thumbnail(path: &Path, options: &RenderOptions) -> Result<RgbaImage> {
let scene = formats::load(path)?;
if matches!(formats::extension(path)?.as_str(), "lbrn" | "lbrn2") {
render::render_xy(&scene, options)
} else {
render::render(&scene, options)
}
}
fn validate_dimensions(options: &RenderOptions) -> Result<()> {
if options.width == 0 || options.height == 0 || options.width > 4096 || options.height > 4096 {
return Err("thumbnail dimensions must be between 1 and 4096".into());
}
Ok(())
}
fn image_thumbnail(path: &Path, options: &RenderOptions) -> Result<RgbaImage> {
validate_dimensions(options)?;
let source = if formats::extension(path)? == "dcm" {
formats::dicom::load(path)?
} else {
let data = formats::read_file(path)?;
if matches!(formats::extension(path)?.as_str(), "tif" | "tiff")
&& let Some(scene) = formats::tiff_volume::scene(&data, data.capacity())?
{
drop(data);
return render::render(&scene, options);
}
let format = image::ImageFormat::from_extension(formats::extension(path)?)
.ok_or("unsupported image extension")?;
let mut reader =
image::ImageReader::with_format(std::io::Cursor::new(data.as_slice()), format);
let mut limits = image::Limits::default();
limits.max_alloc = Some(256 * 1024 * 1024);
reader.limits(limits);
reader.decode()?
};
Ok(fit_image(&source, options))
}
fn fit_image(source: &DynamicImage, options: &RenderOptions) -> RgbaImage {
let source = source
.resize(
options.width,
options.height,
image::imageops::FilterType::Lanczos3,
)
.to_rgba8();
let alpha = if options.transparent { 0 } else { 255 };
let mut canvas = RgbaImage::from_pixel(
options.width,
options.height,
Rgba([
options.background[0],
options.background[1],
options.background[2],
alpha,
]),
);
image::imageops::overlay(
&mut canvas,
&source,
i64::from((options.width - source.width()) / 2),
i64::from((options.height - source.height()) / 2),
);
if !options.transparent {
for pixel in canvas.pixels_mut() {
pixel[3] = 255;
}
}
canvas
}