Skip to content

Commit 3cfa147

Browse files
committed
Optimize gallery thumbnail generation by using Windows Shell caching
- Prioritize extracting thumbnails from Windows Explorer Shell cache (IShellItemImageFactory) in CImageLoader::LoadThumbnail. - Add LoadShellThumbnail method to handle exactly targetSize or 256x256 fallback sizes, rejecting icon-sized fallbacks. - Add COM initialization (COINIT_MULTITHREADED) to ThumbnailManager worker threads to ensure reliable COM API calls.
1 parent 5747f81 commit 3cfa147

3 files changed

Lines changed: 108 additions & 0 deletions

File tree

QuickView/ImageLoader.cpp

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
21832269
HRESULT 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*
21922278
HRESULT 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;

QuickView/ImageLoader.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,9 @@ class CImageLoader {
393393
static void ReleaseJxlRunner();
394394

395395
private:
396+
// Windows Shell Thumbnail Extractor
397+
HRESULT LoadShellThumbnail(LPCWSTR filePath, int targetSize, ThumbData* pData);
398+
396399
ComPtr<IWICImagingFactory> m_wicFactory;
397400

398401
// [JXL Global Runner] Static singleton

QuickView/ThumbnailManager.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ void ThumbnailManager::TouchLRU(int index) {
197197
}
198198

199199
void ThumbnailManager::WorkerLoopFast() {
200+
// Thumbnail extraction (especially via WIC/Shell) relies on COM components
201+
HRESULT coInitHr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
202+
200203
while (m_running) {
201204
Task task;
202205
{
@@ -227,9 +230,16 @@ void ThumbnailManager::WorkerLoopFast() {
227230
PostMessage(m_hwnd, WM_THUMB_KEY_READY, (WPARAM)task.index, 0);
228231
}
229232
}
233+
234+
if (SUCCEEDED(coInitHr)) {
235+
CoUninitialize();
236+
}
230237
}
231238

232239
void ThumbnailManager::WorkerLoopSlow() {
240+
// Thumbnail extraction (especially via WIC/Shell) relies on COM components
241+
HRESULT coInitHr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
242+
233243
while (m_running) {
234244
Task task;
235245
{
@@ -259,6 +269,10 @@ void ThumbnailManager::WorkerLoopSlow() {
259269
PostMessage(m_hwnd, WM_THUMB_KEY_READY, (WPARAM)task.index, 0);
260270
}
261271
}
272+
273+
if (SUCCEEDED(coInitHr)) {
274+
CoUninitialize();
275+
}
262276
}
263277

264278
// Added to match planned API changes

0 commit comments

Comments
 (0)