Skip to main content

thumb_rs/
lib.rs

1use std::path::Path;
2
3pub mod error;
4pub use error::ThumbsError;
5
6#[cfg(target_os = "macos")]
7pub mod platform;
8
9#[cfg(target_os = "windows")]
10pub mod platform;
11
12/// Raw thumbnail data returned by the library.
13///
14/// Contains uncompressed RGBA8 pixel data in row-major order.
15/// Each pixel is 4 bytes: [R, G, B, A].
16/// Total byte length = `width * height * 4`.
17#[derive(Debug, Clone)]
18pub struct Thumbnail {
19    /// Raw RGBA8 pixel data, row-major, no padding.
20    pub rgba: Vec<u8>,
21
22    /// Width of the thumbnail in pixels.
23    pub width: u32,
24
25    /// Height of the thumbnail in pixels.
26    pub height: u32,
27}
28
29impl Thumbnail {
30    /// Create a new Thumbnail from raw pixel data.
31    ///
32    /// # Panics
33    /// Panics if `rgba.len() != (width * height * 4) as usize`.
34    pub fn new(rgba: Vec<u8>, width: u32, height: u32) -> Self {
35        assert_eq!(
36            rgba.len(),
37            (width as usize) * (height as usize) * 4,
38            "rgba length must be width * height * 4"
39        );
40        Self {
41            rgba,
42            width,
43            height,
44        }
45    }
46}
47
48const BASE_PX: u32 = 256;
49
50/// Thumbnail size as a scale multiplier.
51///
52/// `scale` multiplies a 256px base: `1` = 256×256, `2` = 512×512, etc.
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct ThumbnailScale(pub u32);
55
56impl Default for ThumbnailScale {
57    fn default() -> Self {
58        Self(1)
59    }
60}
61
62impl ThumbnailScale {
63    /// Maximum thumbnail dimension in pixels (square).
64    pub fn px(&self) -> u32 {
65        self.0 * BASE_PX
66    }
67}
68
69/// Generate a thumbnail for any file type the OS can preview.
70///
71/// On macOS uses QuickLook (`QLThumbnailGenerator`).
72/// On Windows uses the Shell API (`IShellItemImageFactory`).
73/// Produces the same thumbnails you see in Finder/Explorer.
74///
75/// # Arguments
76/// * `file_path` — Path to the source file. Can be any type (image, video, PDF, etc.).
77/// * `scale` — Size multiplier. `1` = 256×256, `2` = 512×512, etc.
78///
79/// # Errors
80/// - `ThumbsError::FileNotFound` if the file doesn't exist.
81/// - `ThumbsError::ThumbnailGenerationFailed` if the OS can't generate a thumbnail.
82/// - `ThumbsError::PlatformError` for OS-level failures.
83/// - `ThumbsError::PlatformNotSupported` on unsupported platforms (e.g., Linux).
84pub fn get_thumbnail<P: AsRef<Path>>(
85    file_path: P,
86    scale: ThumbnailScale,
87) -> Result<Thumbnail, ThumbsError> {
88    let file_path = file_path.as_ref();
89
90    if !file_path.exists() {
91        return Err(ThumbsError::FileNotFound(file_path.display().to_string()));
92    }
93
94    #[cfg(target_os = "macos")]
95    {
96        platform::macos::generate_thumbnail(file_path, scale)
97    }
98
99    #[cfg(target_os = "windows")]
100    {
101        platform::windows::generate_thumbnail(file_path, scale)
102    }
103
104    #[cfg(not(any(target_os = "macos", target_os = "windows")))]
105    {
106        Err(ThumbsError::PlatformNotSupported)
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn test_thumbnail_scale_default() {
116        let scale = ThumbnailScale::default();
117        assert_eq!(scale.0, 1);
118        assert_eq!(scale.px(), 256);
119    }
120
121    #[test]
122    fn test_thumbnail_scale_multiplier() {
123        let scale = ThumbnailScale(2);
124        assert_eq!(scale.px(), 512);
125    }
126
127    #[test]
128    fn test_thumbnail_new_valid() {
129        let rgba = vec![0u8; 256 * 256 * 4];
130        let thumb = Thumbnail::new(rgba, 256, 256);
131        assert_eq!(thumb.width, 256);
132        assert_eq!(thumb.height, 256);
133    }
134
135    #[test]
136    #[should_panic(expected = "rgba length must be")]
137    fn test_thumbnail_new_invalid_size() {
138        let rgba = vec![0u8; 100];
139        Thumbnail::new(rgba, 256, 256);
140    }
141}