From 0186cb55eb388446fddfb7e8b6feb229ef2b389f Mon Sep 17 00:00:00 2001 From: elwrcl Date: Wed, 30 Sep 2026 14:27:07 +0300 Subject: [PATCH] fix: detect the preview image format from its contents --- src/host.rs | 4 +--- src/thumbnail.rs | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/host.rs b/src/host.rs index eda927c..55bc62f 100644 --- a/src/host.rs +++ b/src/host.rs @@ -341,9 +341,7 @@ fn handle(state: &mut State, id: u64, request: Request) -> Reply { } fn add(state: &mut State, path: &Path, output_name: &str) -> Result<()> { - let image = image::open(path) - .with_context(|| format!("could not read {}", path.display()))? - .to_rgba8(); + let image = crate::thumbnail::load(path)?; let outputs = state.output_list(); let output = outputs .iter() diff --git a/src/thumbnail.rs b/src/thumbnail.rs index d59c9a9..f669120 100644 --- a/src/thumbnail.rs +++ b/src/thumbnail.rs @@ -1,6 +1,6 @@ //! One preview thumbnail: its layer surface, pixels, animation and gestures. -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::time::Instant; use anyhow::{Context, Result}; @@ -29,6 +29,18 @@ pub const NAMESPACE: &str = "valw-preview"; /// Premultiplied BGRA of the 1 px border: #e0e0e0 at 80% opacity. const BORDER: [u8; 4] = [179, 179, 179, 204]; +/// Loads the screenshot a thumbnail shows. The format comes from the file's +/// contents, not its name: save.filename and -o needn't end in .png. +pub fn load(path: &Path) -> Result { + let read = || -> Result { + Ok(image::ImageReader::open(path)? + .with_guessed_format()? + .decode()? + .to_rgba8()) + }; + read().with_context(|| format!("could not read {}", path.display())) +} + /// The thumbnail image for `img` on an output with `scale` physical pixels /// per logical pixel: returns the physical-size image and its logical size. pub fn downscale(img: &RgbaImage, scale: f64) -> (RgbaImage, (u32, u32)) { @@ -372,6 +384,16 @@ fn canvas_width(image_width: u32, scale: f64) -> u32 { mod tests { use super::*; + #[test] + fn loads_png_without_extension() { + // save.filename and -o don't have to end in .png; the bytes always are PNG. + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("shot"); + let img = RgbaImage::from_pixel(3, 2, image::Rgba([1, 2, 3, 255])); + std::fs::write(&path, crate::output::encode_png(&img).unwrap()).unwrap(); + assert_eq!(load(&path).unwrap(), img); + } + #[test] fn downscale_fits_long_edge_at_scale_one() { let img = RgbaImage::new(1920, 1080);