Skip to main content

dicom_viewer/dcm/
thumbnail.rs

1//! Series preview thumbnails for the sidebar.
2//!
3//! Decodes the first instance of a series at heavily-decimated resolution
4//! and applies an auto window/level. Result is a small RGBA buffer ready
5//! for `ColorImage::from_rgba_unmultiplied`.
6
7use anyhow::Result;
8use std::path::Path;
9
10use crate::dcm::pixel::{auto_window, load_raw};
11
12/// Small RGBA buffer ready for `ColorImage::from_rgba_unmultiplied`.
13pub struct Thumbnail {
14    /// Width in pixels.
15    pub width: u32,
16    /// Height in pixels.
17    pub height: u32,
18    /// RGBA8 bytes, row-major.
19    pub rgba: Vec<u8>,
20}
21
22/// Render a square-ish thumbnail no larger than `max_dim` per side. We
23/// reuse [`load_raw`] (which already caps at `MAX_DISPLAY_DIM`) and then
24/// decimate further by nearest-neighbour. This avoids dragging in any
25/// extra image-resize crate.
26pub fn load_thumbnail(path: &Path, max_dim: u32) -> Result<Thumbnail> {
27    let raw = load_raw(path)?;
28    let max_side = raw.width.max(raw.height);
29    let scale = max_side.div_ceil(max_dim).max(1);
30    let w = (raw.width / scale).max(1);
31    let h = (raw.height / scale).max(1);
32
33    let (center, width) = auto_window(&raw);
34    let w_f = width.max(1e-3);
35    let lo = center - w_f / 2.0;
36    let inv_w = 255.0 / w_f;
37    let invert = raw.photometric_invert;
38
39    let mut out = vec![0u8; (w * h * 4) as usize];
40    let src_w = raw.width as usize;
41    for y in 0..h {
42        let sy = (y * scale) as usize;
43        for x in 0..w {
44            let sx = (x * scale) as usize;
45            let v = raw.values[sy * src_w + sx] as f64;
46            let g = ((v - lo) * inv_w).round().clamp(0.0, 255.0) as u8;
47            let g = if invert { 255 - g } else { g };
48            let o = ((y * w + x) * 4) as usize;
49            out[o] = g;
50            out[o + 1] = g;
51            out[o + 2] = g;
52            out[o + 3] = 255;
53        }
54    }
55
56    Ok(Thumbnail {
57        width: w,
58        height: h,
59        rgba: out,
60    })
61}