diff --git a/Cargo.lock b/Cargo.lock index 4101a37..86c816b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -393,9 +393,9 @@ dependencies = [ [[package]] name = "filament-bindings" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e145118476967672593f62423ace40fcb5e9be2fd307eef0d4f697ea9673b03" +checksum = "293c8a3a4964614b9d9af38b6579166f425c117925cfd25ab5a00309affdb129" dependencies = [ "bindgen", "bitflags", @@ -1205,6 +1205,7 @@ dependencies = [ name = "space-thumbnails-windows" version = "0.1.1" dependencies = [ + "image", "lazy_static", "space-thumbnails", "windows", diff --git a/README.md b/README.md index f295c8f..b01be33 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,6 @@ Generates preview thumbnails for 3D model files. Provide a Windows Explorer exte **Ensure thumbnails are generally enabled.** Are thumbnails working with other file types on your system, e.g. photos? If not, you may have disabled them altogether. - 1. open any folder 2. open the `Folder Options` @@ -38,8 +37,6 @@ Generates preview thumbnails for 3D model files. Provide a Windows Explorer exte 3. Select the `View` tab 4. in `Advanced settings`, make sure the `Always show icons, never thumbnails` option is not checked - - **Clear your thumbnail cache.** This forces Explorer to request new thumbnails instead of relying on outdated data. 1. click the `Start` button and type `cleanmgr.exe` @@ -47,6 +44,16 @@ Generates preview thumbnails for 3D model files. Provide a Windows Explorer exte 3. check `Thumbnails` and confirm 4. reboot +### Speed + +Rendering thumbnails for 3D models may not be that fast. To keep your explorer smooth and available, we have made some limits here, if the model file size is larger than `300MB` or takes longer than `5 seconds` to load and render, it will be cancelled and display this image below. + + + +If there is an error loading the file (corrupt or illegal file), it will display this image below. + + + ## Links * [google / filament](https://github.com/google/filament): 3D rendering engine, and [the rust bindings](https://github.com/EYHN/rust-filament) diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index ffb7c15..2601c92 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.1" edition = "2021" [dependencies] -filament-bindings = "0.2.1" +filament-bindings = "0.2.2" [dev-dependencies] image = "0.24" diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 8aa8795..f075316 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -2,7 +2,7 @@ use core::panic; use std::{cell::Cell, ffi::OsStr, fs, path::Path, rc::Rc}; use filament_bindings::{ - assimp::AssimpAsset, + assimp::{post_process, AssimpAsset}, backend::{Backend, PixelBufferDescriptor, PixelDataFormat, PixelDataType}, filament::{ self, sRGBColor, Aabb, Camera, ClearOptions, Engine, Fov, IndirectLight, @@ -19,6 +19,15 @@ use filament_bindings::{ const IDL_TEXTURE_DATA: &'static [u8] = include_bytes!("lightroom_14b_ibl.ktx"); +const ASSIMP_FLAGS: u32 = post_process::GEN_SMOOTH_NORMALS + | post_process::CALC_TANGENT_SPACE + | post_process::GEN_UV_COORDS + | post_process::FIND_INSTANCES + | post_process::OPTIMIZE_MESHES + | post_process::IMPROVE_CACHE_LOCALITY + | post_process::SORT_BY_P_TYPE + | post_process::TRIANGULATE; + pub struct SpaceThumbnailsRenderer { // need release engine: Engine, @@ -142,7 +151,8 @@ impl SpaceThumbnailsRenderer { Some(filepath.as_ref()), ) } else { - let asset = AssimpAsset::from_file(&mut self.engine, filepath).ok()?; + let asset = + AssimpAsset::from_file_with_flags(&mut self.engine, filepath, ASSIMP_FLAGS).ok()?; self.load_assimp_asset(asset) } } @@ -156,9 +166,13 @@ impl SpaceThumbnailsRenderer { { self.load_gltf_asset(buffer, filename.as_ref(), None) } else { - let asset = - AssimpAsset::from_memory(&mut self.engine, buffer, filename.as_ref().to_str()?) - .ok()?; + let asset = AssimpAsset::from_memory_with_flags( + &mut self.engine, + buffer, + filename.as_ref().to_str()?, + ASSIMP_FLAGS, + ) + .ok()?; self.load_assimp_asset(asset) } } @@ -423,8 +437,6 @@ mod test { ) .unwrap(); - let mut renderer = SpaceThumbnailsRenderer::new(RendererBackend::Vulkan, 800, 800); - for entry in models { let entry = entry.unwrap(); @@ -435,6 +447,11 @@ mod test { let filepath = entry.path(); let filename = filepath.file_name().unwrap().to_str().unwrap(); + let now = Instant::now(); + let mut renderer = SpaceThumbnailsRenderer::new(RendererBackend::Vulkan, 800, 800); + let elapsed = now.elapsed(); + println!("Initialize renderer, Elapsed: {:.2?}", elapsed); + let now = Instant::now(); renderer.load_asset_from_file(&filepath).unwrap(); let elapsed = now.elapsed(); diff --git a/crates/windows/Cargo.toml b/crates/windows/Cargo.toml index b670d38..aed6a94 100644 --- a/crates/windows/Cargo.toml +++ b/crates/windows/Cargo.toml @@ -23,3 +23,6 @@ features = [ "Win32_UI_Shell", "Win32_UI_Shell_PropertiesSystem" ] + +[build-dependencies] +image = "0.24" diff --git a/crates/windows/assets/error256x256.png b/crates/windows/assets/error256x256.png new file mode 100644 index 0000000..bb74719 Binary files /dev/null and b/crates/windows/assets/error256x256.png differ diff --git a/crates/windows/assets/timeout256x256.png b/crates/windows/assets/timeout256x256.png new file mode 100644 index 0000000..36bdbbc Binary files /dev/null and b/crates/windows/assets/timeout256x256.png differ diff --git a/crates/windows/assets/toolarge256x256.png b/crates/windows/assets/toolarge256x256.png new file mode 100644 index 0000000..36bdbbc Binary files /dev/null and b/crates/windows/assets/toolarge256x256.png differ diff --git a/crates/windows/build.rs b/crates/windows/build.rs new file mode 100644 index 0000000..fedf2a3 --- /dev/null +++ b/crates/windows/build.rs @@ -0,0 +1,39 @@ +use std::{ + env, fs, + path::{Path, PathBuf}, +}; + +fn main() { + println!("cargo:rerun-if-changed=assets/error256x256.png"); + png2argb( + "assets/error256x256.png", + PathBuf::from(env::var("OUT_DIR").unwrap()).join("error256x256.bin"), + ); + + println!("cargo:rerun-if-changed=assets/timeout256x256.png"); + png2argb( + "assets/timeout256x256.png", + PathBuf::from(env::var("OUT_DIR").unwrap()).join("timeout256x256.bin"), + ); + + println!("cargo:rerun-if-changed=assets/toolarge256x256.png"); + png2argb( + "assets/toolarge256x256.png", + PathBuf::from(env::var("OUT_DIR").unwrap()).join("toolarge256x256.bin"), + ); +} + +fn png2argb(source: impl AsRef, out: impl AsRef) { + let img = image::open(source).unwrap(); + let rgba = img.to_rgba8(); + let mut argb = Vec::with_capacity(rgba.len()); + + for (_, _, pixel) in rgba.enumerate_pixels() { + argb.push(pixel.0[2]); + argb.push(pixel.0[1]); + argb.push(pixel.0[0]); + argb.push(pixel.0[3]); + } + + fs::write(out, argb).unwrap(); +} diff --git a/crates/windows/src/constant.rs b/crates/windows/src/constant.rs index 995d344..a0cf5b5 100644 --- a/crates/windows/src/constant.rs +++ b/crates/windows/src/constant.rs @@ -46,3 +46,7 @@ lazy_static! { )) ]; } + +pub const ERROR_256X256_ARGB: &'static [u8] = include_bytes!(concat!(env!("OUT_DIR"), "/error256x256.bin")); +pub const TIMEOUT_256X256_ARGB: &'static [u8] = include_bytes!(concat!(env!("OUT_DIR"), "/timeout256x256.bin")); +pub const TOOLARGE_256X256_ARGB: &'static [u8] = include_bytes!(concat!(env!("OUT_DIR"), "/toolarge256x256.bin")); diff --git a/crates/windows/src/lib.rs b/crates/windows/src/lib.rs index 3c4dcb1..1d157fd 100644 --- a/crates/windows/src/lib.rs +++ b/crates/windows/src/lib.rs @@ -3,6 +3,5 @@ extern crate lazy_static; pub mod providers; pub mod registry; -pub mod win_stream; pub mod constant; pub mod utils; diff --git a/crates/windows/src/providers/thumbnail.rs b/crates/windows/src/providers/thumbnail.rs index 620d33f..b71c73e 100644 --- a/crates/windows/src/providers/thumbnail.rs +++ b/crates/windows/src/providers/thumbnail.rs @@ -1,4 +1,4 @@ -use std::{cell::Cell, io::Read, mem::size_of, time::Duration}; +use std::{cell::Cell, io, time::Duration}; use space_thumbnails::{RendererBackend, SpaceThumbnailsRenderer}; use windows::{ @@ -12,9 +12,9 @@ use windows::{ }; use crate::{ + constant::{ERROR_256X256_ARGB, TIMEOUT_256X256_ARGB, TOOLARGE_256X256_ARGB}, registry::{register_clsid, RegistryData, RegistryKey, RegistryValue}, - utils::run_timeout, - win_stream::WinStream, + utils::{create_argb_bitmap, run_timeout, WinStream}, }; use super::Provider; @@ -98,9 +98,28 @@ impl IThumbnailProvider_Impl for ThumbnailHandler { .stream .take() .ok_or(windows::core::Error::from(E_FAIL))?; + + let filesize = stream.size()?; + + if filesize > 300 * 1024 * 1024 + /* 300 MB */ + { + unsafe { + let mut p_bits: *mut core::ffi::c_void = core::ptr::null_mut(); + let hbmp = create_argb_bitmap(256, 256, &mut p_bits); + std::ptr::copy( + TOOLARGE_256X256_ARGB.as_ptr(), + p_bits as *mut _, + TOOLARGE_256X256_ARGB.len(), + ); + phbmp.write(hbmp); + pdwalpha.write(WTSAT_ARGB); + } + return Ok(()); + } + let mut buffer = Vec::new(); - stream - .read_to_end(&mut buffer) + io::Read::read_to_end(&mut stream, &mut buffer) .ok() .ok_or(windows::core::Error::from(E_FAIL))?; @@ -122,26 +141,8 @@ impl IThumbnailProvider_Impl for ThumbnailHandler { if let Ok(Some(screenshot_buffer)) = timeout_result { unsafe { - let bmi = BITMAPINFO { - bmiHeader: BITMAPINFOHEADER { - biSize: size_of::() as u32, - biWidth: cx as i32, - biHeight: -(cx as i32), - biPlanes: 1, - biBitCount: 32, - ..Default::default() - }, - ..Default::default() - }; let mut p_bits: *mut core::ffi::c_void = core::ptr::null_mut(); - let hbmp = CreateDIBSection( - core::mem::zeroed::(), - &bmi, - DIB_RGB_COLORS, - &mut p_bits, - core::mem::zeroed::(), - 0, - ); + let hbmp = create_argb_bitmap(cx, cx, &mut p_bits); for x in 0..cx { for y in 0..cx { let index = ((x * cx + y) * 4) as usize; @@ -157,8 +158,32 @@ impl IThumbnailProvider_Impl for ThumbnailHandler { pdwalpha.write(WTSAT_ARGB); } Ok(()) + } else if matches!(timeout_result, Err(err) if err.kind() == io::ErrorKind::TimedOut) { + unsafe { + let mut p_bits: *mut core::ffi::c_void = core::ptr::null_mut(); + let hbmp = create_argb_bitmap(256, 256, &mut p_bits); + std::ptr::copy( + TIMEOUT_256X256_ARGB.as_ptr(), + p_bits as *mut _, + TIMEOUT_256X256_ARGB.len(), + ); + phbmp.write(hbmp); + pdwalpha.write(WTSAT_ARGB); + } + Ok(()) } else { - Err(windows::core::Error::from(E_FAIL)) + unsafe { + let mut p_bits: *mut core::ffi::c_void = core::ptr::null_mut(); + let hbmp = create_argb_bitmap(256, 256, &mut p_bits); + std::ptr::copy( + ERROR_256X256_ARGB.as_ptr(), + p_bits as *mut _, + ERROR_256X256_ARGB.len(), + ); + phbmp.write(hbmp); + pdwalpha.write(WTSAT_ARGB); + } + Ok(()) } } } diff --git a/crates/windows/src/providers/thumbnail_file.rs b/crates/windows/src/providers/thumbnail_file.rs index 1292a6c..46fc6d1 100644 --- a/crates/windows/src/providers/thumbnail_file.rs +++ b/crates/windows/src/providers/thumbnail_file.rs @@ -1,15 +1,11 @@ -use std::{ - cell::Cell, ffi::OsString, mem::size_of, os::windows::prelude::OsStringExt, time::Duration, -}; +use std::{cell::Cell, ffi::OsString, fs, io, os::windows::prelude::OsStringExt, time::Duration}; use space_thumbnails::{RendererBackend, SpaceThumbnailsRenderer}; use windows::{ core::{implement, IUnknown, Interface, GUID}, Win32::{ Foundation::E_FAIL, - Graphics::Gdi::{ - CreateDIBSection, BITMAPINFO, BITMAPINFOHEADER, DIB_RGB_COLORS, HBITMAP, HDC, - }, + Graphics::Gdi::HBITMAP, UI::Shell::{ IThumbnailProvider_Impl, PropertiesSystem::IInitializeWithFile_Impl, WTSAT_ARGB, WTS_ALPHATYPE, @@ -18,8 +14,9 @@ use windows::{ }; use crate::{ + constant::{ERROR_256X256_ARGB, TIMEOUT_256X256_ARGB, TOOLARGE_256X256_ARGB}, registry::{register_clsid, RegistryData, RegistryKey, RegistryValue}, - utils::run_timeout, + utils::{create_argb_bitmap, run_timeout}, }; use super::Provider; @@ -102,6 +99,22 @@ impl IThumbnailProvider_Impl for ThumbnailFileHandler { return Err(windows::core::Error::from(E_FAIL)); } + if matches!(fs::metadata(&filepath), Ok(metadata) if metadata.len() > 300 * 1024 * 1024 /* 300 MB */) + { + unsafe { + let mut p_bits: *mut core::ffi::c_void = core::ptr::null_mut(); + let hbmp = create_argb_bitmap(256, 256, &mut p_bits); + std::ptr::copy( + TOOLARGE_256X256_ARGB.as_ptr(), + p_bits as *mut _, + TOOLARGE_256X256_ARGB.len(), + ); + phbmp.write(hbmp); + pdwalpha.write(WTSAT_ARGB); + } + return Ok(()) + } + let timeout_result = run_timeout( move || { let mut renderer = SpaceThumbnailsRenderer::new(RendererBackend::Vulkan, cx, cx); @@ -115,26 +128,8 @@ impl IThumbnailProvider_Impl for ThumbnailFileHandler { if let Ok(Some(screenshot_buffer)) = timeout_result { unsafe { - let bmi = BITMAPINFO { - bmiHeader: BITMAPINFOHEADER { - biSize: size_of::() as u32, - biWidth: cx as i32, - biHeight: -(cx as i32), - biPlanes: 1, - biBitCount: 32, - ..Default::default() - }, - ..Default::default() - }; let mut p_bits: *mut core::ffi::c_void = core::ptr::null_mut(); - let hbmp = CreateDIBSection( - core::mem::zeroed::(), - &bmi, - DIB_RGB_COLORS, - &mut p_bits, - core::mem::zeroed::(), - 0, - ); + let hbmp = create_argb_bitmap(cx, cx, &mut p_bits); for x in 0..cx { for y in 0..cx { let index = ((x * cx + y) * 4) as usize; @@ -150,8 +145,32 @@ impl IThumbnailProvider_Impl for ThumbnailFileHandler { pdwalpha.write(WTSAT_ARGB); } Ok(()) + } else if matches!(timeout_result, Err(err) if err.kind() == io::ErrorKind::TimedOut) { + unsafe { + let mut p_bits: *mut core::ffi::c_void = core::ptr::null_mut(); + let hbmp = create_argb_bitmap(256, 256, &mut p_bits); + std::ptr::copy( + TIMEOUT_256X256_ARGB.as_ptr(), + p_bits as *mut _, + TIMEOUT_256X256_ARGB.len(), + ); + phbmp.write(hbmp); + pdwalpha.write(WTSAT_ARGB); + } + Ok(()) } else { - Err(windows::core::Error::from(E_FAIL)) + unsafe { + let mut p_bits: *mut core::ffi::c_void = core::ptr::null_mut(); + let hbmp = create_argb_bitmap(256, 256, &mut p_bits); + std::ptr::copy( + ERROR_256X256_ARGB.as_ptr(), + p_bits as *mut _, + ERROR_256X256_ARGB.len(), + ); + phbmp.write(hbmp); + pdwalpha.write(WTSAT_ARGB); + } + Ok(()) } } } diff --git a/crates/windows/src/utils.rs b/crates/windows/src/utils.rs index 9f18b4f..e454840 100644 --- a/crates/windows/src/utils.rs +++ b/crates/windows/src/utils.rs @@ -1,10 +1,15 @@ use std::{ - io, + io, mem, sync::{atomic::AtomicBool, Arc}, thread, time::{Duration, Instant}, }; +use windows::Win32::{ + Graphics::Gdi::{CreateDIBSection, BITMAPINFO, BITMAPINFOHEADER, DIB_RGB_COLORS, HBITMAP, HDC}, + System::Com::{IStream, STATSTG}, +}; + pub fn run_timeout( func: impl FnOnce() -> T + Send + 'static, timeout: Duration, @@ -35,3 +40,66 @@ pub fn run_timeout( } } } + +pub struct WinStream { + stream: IStream, +} + +impl WinStream { + pub fn size(&self) -> windows::core::Result { + unsafe { + let mut stats = STATSTG::default(); + self.stream.Stat(&mut stats, 0)?; + Ok(stats.cbSize) + } + } +} + +impl From for WinStream { + fn from(stream: IStream) -> Self { + Self { stream } + } +} + +impl io::Read for WinStream { + fn read(&mut self, buf: &mut [u8]) -> Result { + let mut bytes_read = 0u32; + unsafe { + self.stream + .Read(buf.as_mut_ptr() as _, buf.len() as u32, &mut bytes_read) + } + .map_err(|err| { + std::io::Error::new( + io::ErrorKind::Other, + format!("IStream::Read failed: {}", err.code().0), + ) + })?; + Ok(bytes_read as usize) + } +} + +pub unsafe fn create_argb_bitmap( + width: u32, + height: u32, + p_bits: &mut *mut core::ffi::c_void, +) -> HBITMAP { + let bmi = BITMAPINFO { + bmiHeader: BITMAPINFOHEADER { + biSize: mem::size_of::() as u32, + biWidth: width as i32, + biHeight: -(height as i32), + biPlanes: 1, + biBitCount: 32, + ..Default::default() + }, + ..Default::default() + }; + CreateDIBSection( + core::mem::zeroed::(), + &bmi, + DIB_RGB_COLORS, + p_bits, + core::mem::zeroed::(), + 0, + ) +} diff --git a/crates/windows/src/win_stream.rs b/crates/windows/src/win_stream.rs deleted file mode 100644 index e7249e4..0000000 --- a/crates/windows/src/win_stream.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::io::{ErrorKind, Read}; - -use windows::Win32::System::Com::IStream; - -pub struct WinStream { - stream: IStream, -} - -impl From for WinStream { - fn from(stream: IStream) -> Self { - Self { stream } - } -} - -impl Read for WinStream { - fn read(&mut self, buf: &mut [u8]) -> Result { - let mut bytes_read = 0u32; - unsafe { - self.stream - .Read(buf.as_mut_ptr() as _, buf.len() as u32, &mut bytes_read) - } - .map_err(|err| { - std::io::Error::new( - ErrorKind::Other, - format!("IStream::Read failed: {}", err.code().0), - ) - })?; - Ok(bytes_read as usize) - } -}