@@ -43,6 +43,7 @@ using namespace QuickView;
4343#include " SIMDUtils.h"
4444#include < thread>
4545#include " PreviewExtractor.h"
46+ #include < shobjidl.h> // [Add] for IShellItemImageFactory
4647#include " MappedFile.h" // [Opt]
4748#if defined(__has_include)
4849#if __has_include(<simd>)
@@ -2180,6 +2181,91 @@ static void ApplyOrientationToThumbData(CImageLoader::ThumbData* pData, int orie
21802181
21812182
21822183
2184+ HRESULT CImageLoader::LoadShellThumbnail (LPCWSTR filePath, int targetSize, ThumbData* pData) {
2185+ if (!filePath || !pData || !m_wicFactory) return E_INVALIDARG;
2186+
2187+ ComPtr<IShellItemImageFactory> imageFactory;
2188+ HRESULT hr = SHCreateItemFromParsingName (filePath, nullptr , IID_PPV_ARGS (&imageFactory));
2189+ if (FAILED (hr) || !imageFactory) return hr;
2190+
2191+ SIZE size = { targetSize, targetSize };
2192+ HBITMAP hBitmap = nullptr ;
2193+
2194+ // Step 1: Request from shell cache (exactly target size) and strictly NO Icon fallback (SIIGBF_THUMBNAILONLY)
2195+ hr = imageFactory->GetImage (size, static_cast <SIIGBF>(SIIGBF_THUMBNAILONLY | SIIGBF_INCACHEONLY), &hBitmap);
2196+
2197+ // Step 2: Fallback to System Large Icon Cache size (256) if the requested size failed
2198+ if (FAILED (hr) || !hBitmap) {
2199+ SIZE fallbackSize = { 256 , 256 };
2200+ hr = imageFactory->GetImage (fallbackSize, static_cast <SIIGBF>(SIIGBF_THUMBNAILONLY | SIIGBF_INCACHEONLY), &hBitmap);
2201+ }
2202+
2203+ if (FAILED (hr) || !hBitmap) return E_FAIL;
2204+
2205+ // Reject standard icon sizes to avoid meaningless fallback
2206+ BITMAP bm;
2207+ if (GetObject (hBitmap, sizeof (bm), &bm)) {
2208+ if (bm.bmWidth == bm.bmHeight && (bm.bmWidth == 16 || bm.bmWidth == 32 || bm.bmWidth == 48 || bm.bmWidth == 64 || bm.bmWidth == 128 )) {
2209+ DeleteObject (hBitmap);
2210+ return E_FAIL; // It's an icon, reject it
2211+ }
2212+ } else {
2213+ DeleteObject (hBitmap);
2214+ return E_FAIL;
2215+ }
2216+
2217+ ComPtr<IWICBitmap> wicBitmap;
2218+ hr = m_wicFactory->CreateBitmapFromHBITMAP (hBitmap, nullptr , WICBitmapUsePremultipliedAlpha, &wicBitmap);
2219+ DeleteObject (hBitmap);
2220+
2221+ if (FAILED (hr) || !wicBitmap) return hr;
2222+
2223+ ComPtr<IWICBitmapSource> sourceToCopy = wicBitmap;
2224+ WICPixelFormatGUID srcFormat = {};
2225+ if (SUCCEEDED (wicBitmap->GetPixelFormat (&srcFormat)) && !IsEqualGUID (srcFormat, GUID_WICPixelFormat32bppPBGRA)) {
2226+ ComPtr<IWICFormatConverter> converter;
2227+ if (SUCCEEDED (m_wicFactory->CreateFormatConverter (&converter)) && converter) {
2228+ hr = converter->Initialize (wicBitmap.Get (), GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, nullptr , 0 .0f , WICBitmapPaletteTypeCustom);
2229+ if (SUCCEEDED (hr)) {
2230+ sourceToCopy = converter;
2231+ }
2232+ }
2233+ }
2234+
2235+ UINT w = 0 , h = 0 ;
2236+ if (FAILED (sourceToCopy->GetSize (&w, &h)) || w == 0 || h == 0 ) return E_FAIL;
2237+
2238+ UINT stride = QuickView::CalculateAlignedStride (w, 4 );
2239+ size_t byteCount = static_cast <size_t >(stride) * h;
2240+
2241+ try {
2242+ pData->pixels .resize (byteCount);
2243+ } catch (...) {
2244+ return E_OUTOFMEMORY;
2245+ }
2246+
2247+ hr = sourceToCopy->CopyPixels (nullptr , stride, static_cast <UINT>(byteCount), pData->pixels .data ());
2248+ if (FAILED (hr)) {
2249+ pData->pixels .clear ();
2250+ return hr;
2251+ }
2252+
2253+ pData->width = static_cast <int >(w);
2254+ pData->height = static_cast <int >(h);
2255+ pData->stride = static_cast <int >(stride);
2256+ pData->isValid = true ;
2257+ pData->loaderName = L" Shell Cache" ;
2258+
2259+ WIN32_FILE_ATTRIBUTE_DATA fad;
2260+ if (GetFileAttributesExW (filePath, GetFileExInfoStandard, &fad)) {
2261+ pData->fileSize = (static_cast <uint64_t >(fad.nFileSizeHigh ) << 32 ) | fad.nFileSizeLow ;
2262+ }
2263+ pData->origWidth = pData->width ;
2264+ pData->origHeight = pData->height ;
2265+
2266+ return S_OK;
2267+ }
2268+
21832269HRESULT CImageLoader::LoadThumbJPEG (LPCWSTR filePath, int targetSize, ThumbData* pData) {
21842270 if (!pData) return E_INVALIDARG;
21852271
@@ -2192,6 +2278,11 @@ HRESULT CImageLoader::LoadThumbJPEG(LPCWSTR filePath, int targetSize, ThumbData*
21922278HRESULT CImageLoader::LoadThumbnail (LPCWSTR filePath, int targetSize, ThumbData* pData, bool allowSlow) {
21932279 if (!filePath || !pData) return E_INVALIDARG;
21942280 pData->isValid = false ;
2281+
2282+ // 0. Highest Priority: Windows Shell Thumbnail Cache (Insanely fast for pre-cached heavy RAWs)
2283+ if (SUCCEEDED (LoadShellThumbnail (filePath, targetSize, pData))) {
2284+ return S_OK;
2285+ }
21952286
21962287 // 1. Unified Codec Dispatch (Primary)
21972288 DecodeContext ctx;
0 commit comments