#include "shellprv.h" #include "filefldr.h" #include "ids.h" #include "prop.h" #include "copy.h" // If these values are modified, the logic in Extract() must be modified too. #define SMALLEST_THUMBNAIL_WITH_4_PREVIEWS 96 #define MAX_MINIPREVIEWS_COLLECT 8 // collect more than we're going to show, just in case one fails #define MAX_MINIPREVIEWS 4 #define FOLDER_GUID TEXT("{A42CD7B6-E9B9-4D02-B7A6-288B71AD28BA}") // Function in defview void SHGetThumbnailSize(SIZE *psize); typedef enum { MINIPREVIEW_LAYOUT_1 = 0, MINIPREVIEW_LAYOUT_4 = 1, } MINIPREVIEW_LAYOUT; // The size of the mini-thumbnails for each thumbnail size. For each thumbnail // size, there is a mini-thumbnail size for single layout and 2x2 layout. LONG const alFolder120MinipreviewSize[] = {104, 48}; LONG const alFolder96MinipreviewSize[] = {82, 40}; LONG const alFolder80MinipreviewSize[] = {69, 32}; // These are the margins at which the mini-thumbnails appear within the main thumbnail. // For thumbnails with only one large minipreview, we can just use x1,y1. LONG const alFolder120MinipreviewOffsets[] = { 8, 64, 13, 67 }; // x1, x2, y1, y2 LONG const alFolder96MinipreviewOffsets[] = { 7, 49, 11, 52 }; // x1, x2, y1, y2 LONG const alFolder80MinipreviewOffsets[] = { 5, 42, 9, 45 }; // x1, x2, y1, y2 void FreeMiniPreviewPidls(LPITEMIDLIST apidlPreviews[], UINT cpidlPreviews); // Helper functions MINIPREVIEW_LAYOUT _GetMiniPreviewLayout(SIZE size); void _GetMiniPreviewLocations(MINIPREVIEW_LAYOUT uLayout, SIZE sizeRequested, SIZE *psizeFolderBmp, POINT aptOrigins[], SIZE *psizeMiniPreview); HRESULT _DrawMiniPreviewBackground(HDC hdc, SIZE sizeFolderBmp, BOOL fAlpha, BOOL* pIsAlpha, RGBQUAD *prgb); HBITMAP _CreateDIBSection(HDC hdcBmp, int cx, int cy); HRESULT _CreateMainRenderingDC(HDC* phdc, HBITMAP* phBmpThumbnail, HBITMAP* phbmpOld, int cx, int cy, RGBQUAD** pprgb); void _DestroyMainRenderingDC(HDC hdc, HBITMAP hbmpOld); HRESULT _AddBitmap(HDC hdc, HBITMAP hbmpSub, POINT ptMargin, SIZE sizeDest, SIZE sizeSource, BOOL fAlphaSource, BOOL fAlphaDest, RGBQUAD *prgbDest, SIZE cxFolderSize); // The files that can serve as thumbnails for folders: const LPCWSTR c_szFolderThumbnailPaths[] = { L"folder.jpg", L"folder.gif" }; // We always have four now. MINIPREVIEW_LAYOUT _GetMiniPreviewLayout(SIZE size) { return MINIPREVIEW_LAYOUT_4; } void FreeMiniPreviewPidls(LPITEMIDLIST apidlPreviews[], UINT cpidlPreviews) { for (UINT u = 0; u < cpidlPreviews; u++) { ILFree(apidlPreviews[u]); } } /** * In: uLayout - The layout (1 or 4 mini previews) * sizeRequested - The size of the thumbnail we are trying to generate * * Out: * -psizeFolderBmp is set to * the size of the bitmap. * * -aptOrigins array is filled in with the locations of the n minipreviews * (note, aptOrigins is assumed to have MAX_MINIPREVIEWS cells) * The size of the minipreviews (square) is returned in pSizeMinipreview; */ void _GetMiniPreviewLocations(MINIPREVIEW_LAYOUT uLayout, SIZE sizeRequested, SIZE *psizeFolderBmp, POINT aptOrigins[], SIZE *psizeMiniPreview) { const LONG *alOffsets; LONG lSize; // One of the standard sizes, that we have a folder bitmap for. LONG lSmallestDimension = min(sizeRequested.cx, sizeRequested.cy); if (lSmallestDimension > 96) // For stuff bigger than 96, we use the 120 size { lSize = 120; alOffsets = alFolder120MinipreviewOffsets; psizeMiniPreview->cx = psizeMiniPreview->cy = alFolder120MinipreviewSize[uLayout]; } else if (lSmallestDimension > 80) // For stuff bigger than 80, but <= 96, we use the 96 size. { lSize = 96; alOffsets = alFolder96MinipreviewOffsets; psizeMiniPreview->cx = psizeMiniPreview->cy = alFolder96MinipreviewSize[uLayout]; } else // For stuff <= 80, we use 80. { lSize = 80; alOffsets = alFolder80MinipreviewOffsets; psizeMiniPreview->cx = psizeMiniPreview->cy = alFolder80MinipreviewSize[uLayout]; } psizeFolderBmp->cx = psizeFolderBmp->cy = lSize; COMPILETIME_ASSERT(4 == MAX_MINIPREVIEWS); aptOrigins[0].x = alOffsets[0]; aptOrigins[0].y = alOffsets[2]; aptOrigins[1].x = alOffsets[1]; aptOrigins[1].y = alOffsets[2]; aptOrigins[2].x = alOffsets[0]; aptOrigins[2].y = alOffsets[3]; aptOrigins[3].x = alOffsets[1]; aptOrigins[3].y = alOffsets[3]; } HBITMAP _CreateDIBSection(HDC h, int cx, int cy, RGBQUAD** pprgb) { BITMAPINFO bi = {0}; bi.bmiHeader.biSize = sizeof(bi.bmiHeader); bi.bmiHeader.biWidth = cx; bi.bmiHeader.biHeight = cy; bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 32; bi.bmiHeader.biCompression = BI_RGB; return CreateDIBSection(h, &bi, DIB_RGB_COLORS, (void**)pprgb, NULL, 0); } // Pre multiplies alpha channel void PreProcessDIB(int cx, int cy, RGBQUAD* pargb) { int cTotal = cx * cy; for (int i = 0; i < cTotal; i++) { RGBQUAD* prgb = &pargb[i]; if (prgb->rgbReserved != 0) { prgb->rgbRed = ((prgb->rgbRed * prgb->rgbReserved) + 128) / 255; prgb->rgbGreen = ((prgb->rgbGreen * prgb->rgbReserved) + 128) / 255; prgb->rgbBlue = ((prgb->rgbBlue * prgb->rgbReserved) + 128) / 255; } else { *((DWORD*)prgb) = 0; } } } // Is there an alpha channel? Check for a non-zero alpha byte. BOOL _HasAlpha(RECT rc, int cx, RGBQUAD *pargb) { for (int y = rc.top; y < rc.bottom; y++) { for (int x = rc.left; x < rc.right; x++) { int iOffset = y * cx; if (pargb[x + iOffset].rgbReserved != 0) return TRUE; } } return FALSE; } /** In: * fAlpha: Do we want the folder background to have an alpha channel? * sizeFolderBmp: size of the thumbnail * * Out: * pIsAlpha: Did we get what we wanted, if we wanted an alpha channel? * (e.g. we won't get it if we're in < 24bit mode.) */ HRESULT _DrawMiniPreviewBackground(HDC hdc, SIZE sizeFolderBmp, BOOL fAlpha, BOOL* pfIsAlpha, RGBQUAD *prgb) { HRESULT hr = E_FAIL; HICON hicon = (HICON)LoadImage(HINST_THISDLL, MAKEINTRESOURCE(IDI_FOLDER), IMAGE_ICON, sizeFolderBmp.cx, sizeFolderBmp.cy, 0); if (hicon) { *pfIsAlpha = FALSE; if (fAlpha) { // Try to blt an alpha channel icon into the dc ICONINFO io; if (GetIconInfo(hicon, &io)) { BITMAP bm; if (GetObject(io.hbmColor, sizeof(bm), &bm)) { if (bm.bmBitsPixel == 32) { HDC hdcSrc = CreateCompatibleDC(hdc); if (hdcSrc) { HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcSrc, io.hbmColor); BitBlt(hdc, 0, 0, sizeFolderBmp.cx, sizeFolderBmp.cy, hdcSrc, 0, 0, SRCCOPY); // Preprocess the alpha PreProcessDIB(sizeFolderBmp.cx, sizeFolderBmp.cy, prgb); *pfIsAlpha = TRUE; SelectObject(hdcSrc, hbmpOld); DeleteDC(hdcSrc); } } } DeleteObject(io.hbmColor); DeleteObject(io.hbmMask); } } if (!*pfIsAlpha) { // Didn't create an alpha bitmap // We're filling the background with background window color. RECT rc = { 0, 0, (long)sizeFolderBmp.cx + 1, (long)sizeFolderBmp.cy + 1}; SHFillRectClr(hdc, &rc, GetSysColor(COLOR_WINDOW)); // Then drawing the icon on top. DrawIconEx(hdc, 0, 0, hicon, sizeFolderBmp.cx, sizeFolderBmp.cy, 0, NULL, DI_NORMAL); // This may have resulted in an alpha channel - we need to know. (If it // did, then when we add a nonalpha minibitmap to this main one, we need to restore // the nuked out alpha channel) // Check if we have alpha (prgb is the bits for the DIB of size sizeFolderBmp): rc.right = sizeFolderBmp.cx; rc.bottom = sizeFolderBmp.cy; *pfIsAlpha = _HasAlpha(rc, sizeFolderBmp.cx, prgb); } DestroyIcon(hicon); hr = S_OK; } return hr; } BOOL DoesFolderContainLogo(LPCITEMIDLIST pidlFull) { BOOL bRet = FALSE; IPropertyBag * pPropBag; if (SUCCEEDED(SHGetViewStatePropertyBag(pidlFull, VS_BAGSTR_EXPLORER, SHGVSPB_PERUSER | SHGVSPB_PERFOLDER, IID_PPV_ARG(IPropertyBag, &pPropBag)))) { TCHAR szLogo[MAX_PATH]; szLogo[0] = 0; if (SUCCEEDED(SHPropertyBag_ReadStr(pPropBag, TEXT("Logo"), szLogo, ARRAYSIZE(szLogo))) && szLogo[0]) { bRet = TRUE; } pPropBag->Release(); } return bRet; } BOOL DoesFolderContainFolderJPG(IShellFolder *psf, LPCITEMIDLIST pidl) { BOOL bRet = FALSE; // return false if there's not folder.jpg, or if folder.jpg is a folder (doh!) IShellFolder *psfSubfolder; // SHBTO can deal with NULL psf, he turns it into psfDesktop if (SUCCEEDED(SHBindToObject(psf, IID_X_PPV_ARG(IShellFolder, pidl, &psfSubfolder)))) { for (int i = 0; i < ARRAYSIZE(c_szFolderThumbnailPaths); i++) { DWORD dwFlags = SFGAO_FILESYSTEM | SFGAO_FOLDER; LPITEMIDLIST pidlItem; if (SUCCEEDED(psfSubfolder->ParseDisplayName(NULL, NULL, (LPOLESTR)c_szFolderThumbnailPaths[i], NULL, &pidlItem, &dwFlags))) { ILFree(pidlItem); if ((dwFlags & (SFGAO_FILESYSTEM | SFGAO_FOLDER)) == SFGAO_FILESYSTEM) { bRet = TRUE; break; } } } psfSubfolder->Release(); } return bRet; } BOOL _IsShortcutTargetACandidate(IShellFolder *psf, LPCITEMIDLIST pidlPreview, BOOL *pbTryCached) { BOOL bRet = FALSE; *pbTryCached = TRUE; IShellLink *psl; if (SUCCEEDED(psf->GetUIObjectOf(NULL, 1, &pidlPreview, IID_PPV_ARG_NULL(IShellLink, &psl)))) { LPITEMIDLIST pidlTarget = NULL; if (SUCCEEDED(psl->GetIDList(&pidlTarget)) && pidlTarget) { DWORD dwTargetFlags = SFGAO_FOLDER; if (SUCCEEDED(SHGetNameAndFlags(pidlTarget, 0, NULL, 0, &dwTargetFlags))) { // return true if its not a folder, or if the folder contains a logo // note that this is kinda like recursing into the below function again bRet = (0 == (dwTargetFlags & SFGAO_FOLDER)); if (!bRet) { bRet = (DoesFolderContainLogo(pidlTarget) || DoesFolderContainFolderJPG(NULL, pidlTarget)); if (bRet) { // It's a logo folder, don't try the cached image. *pbTryCached = FALSE; } } } ILFree(pidlTarget); } psl->Release(); } return bRet; } BOOL _IsMiniPreviewCandidate(IShellFolder *psf, LPCITEMIDLIST pidl, BOOL *pbTryCached) { BOOL bRet = FALSE; DWORD dwAttr = SHGetAttributes(psf, pidl, SFGAO_FOLDER | SFGAO_LINK | SFGAO_FILESYSANCESTOR); *pbTryCached = TRUE; // if its a folder, check and see if its got a logo // note that folder shortcuts will have both folder and link, and since we check folder first, we won't recurse into folder shortcuts // dont do anything unless pidl is a folder on a real filesystem (i.e. dont walk into zip/cab) if ((dwAttr & (SFGAO_FOLDER | SFGAO_FILESYSANCESTOR)) == (SFGAO_FOLDER | SFGAO_FILESYSANCESTOR)) { LPITEMIDLIST pidlParent; if (SUCCEEDED(SHGetIDListFromUnk(psf, &pidlParent))) { LPITEMIDLIST pidlFull; if (SUCCEEDED(SHILCombine(pidlParent, pidl, &pidlFull))) { bRet = DoesFolderContainLogo(pidlFull); ILFree(pidlFull); } ILFree(pidlParent); } if (!bRet) { // no logo image, check for a "folder.jpg" // if its not there, then don't display pidl as a mini-preview, as it would recurse and produce dumb-looking 1/16 scale previews bRet = DoesFolderContainFolderJPG(psf, pidl); } if (bRet) { // For logo folders, we don't look for a cached image (cached image won't have alpha, which we want) *pbTryCached = FALSE; } } else { // Only if its not a link, or if its a link to a valid candidate, then we can get its extractor if (0 == (dwAttr & SFGAO_LINK) || _IsShortcutTargetACandidate(psf, pidl, pbTryCached)) { IExtractImage *pei; if (SUCCEEDED(psf->GetUIObjectOf(NULL, 1, &pidl, IID_X_PPV_ARG(IExtractImage, NULL, &pei)))) { bRet = TRUE; pei->Release(); } } } return bRet; } // We return the bits to the dibsection in the dc, if asked for. We need this for preprocessing the alpha channel, // if one exists. HRESULT _CreateMainRenderingDC(HDC* phdc, HBITMAP* phbmp, HBITMAP* phbmpOld, int cx, int cy, RGBQUAD** pprgb) { HRESULT hr = E_OUTOFMEMORY; HDC hdc = GetDC(NULL); if (hdc) { *phdc = CreateCompatibleDC(hdc); if (*phdc) { RGBQUAD *prgbDummy; *phbmp = _CreateDIBSection(*phdc, cx, cy, &prgbDummy); if (*phbmp) { *phbmpOld = (HBITMAP) SelectObject(*phdc, *phbmp); if (pprgb) *pprgb = prgbDummy; hr = S_OK; } else { DeleteDC(*phdc); } } ReleaseDC(NULL, hdc); } return hr; } void _DestroyMainRenderingDC(HDC hdc, HBITMAP hbmpOld) // Unselects the bitmap, and deletes the Dc { if (hbmpOld) SelectObject(hdc, hbmpOld); DeleteDC(hdc); } // We just blt'd a nonalpha guy into an alpha'd bitmap. This nuked out the alpha channel. // Repair it by setting alpha channel to 0xff (opaque). void _SetAlpha(RECT rc, SIZE sizeBmp, RGBQUAD *pargb) { for (int y = (sizeBmp.cy - rc.bottom); y < (sizeBmp.cy - rc.top); y++) // Origin at bottom left. { int iOffset = y * sizeBmp.cx; for (int x = rc.left; x < rc.right; x++) { pargb[x + iOffset].rgbReserved = 0xff; } } } /** * In * hbmpSub - little bitmap that we're adding to the thumbnail bitmap. * ptMargin - where we're adding it on the destination thumbnail bitmap. * sizeDest - how big it needs to be on the destination thumbnail bitmap. * sizeSource - how bit it is. * fAlphaSource - does the bitmap we're adding have an alpha channel? * fAlphaDest - does what we're adding it to, have an alpha channel? * prgbDest - the bits of the destination bitmap - needed if we add a non-alpha bitmap * to an alpha background, so we can reset the alpha. * sizeFolderBmp - the size of the destination bitmap - need this along with prgbDest. */ HRESULT _AddBitmap(HDC hdc, HBITMAP hbmpSub, POINT ptMargin, SIZE sizeDest, SIZE sizeSource, BOOL fAlphaSource, BOOL fAlphaDest, RGBQUAD *prgbDest, SIZE sizeFolderBmp) { HRESULT hr = E_OUTOFMEMORY; HDC hdcFrom = CreateCompatibleDC(hdc); if (hdcFrom) { // Select the bitmap into the source hdc. HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcFrom, hbmpSub); if (hbmpOld) { // Adjust destination size to preserve aspect ratio SIZE sizeDestActual; if ((1000 * sizeDest.cx / sizeSource.cx) < // 1000 -> float simulation (1000 * sizeDest.cy / sizeSource.cy)) { // Keep destination width sizeDestActual.cy = sizeSource.cy * sizeDest.cx / sizeSource.cx; sizeDestActual.cx = sizeDest.cx; ptMargin.y += (sizeDest.cy - sizeDestActual.cy) / 2; // Center } else { // Keep destination height sizeDestActual.cx = sizeSource.cx * sizeDest.cy / sizeSource.cy; sizeDestActual.cy = sizeDest.cy; ptMargin.x += (sizeDest.cx - sizeDestActual.cx) / 2; // Center } // Now blt the image onto our folder background. // Three alpha possibilities: // Dest: no alpha, Src: no alpha -> the normal case // Dest: no alpha, Src: alpha -> one of the minipreviews is a logo-ized folder. // Dest: alpha, Src: no alpha -> we're a logoized folder being rendered as a minipreview in // the parent folder's thumbnail. // If we got back an alpha image, we need to alphablend it. if (fAlphaSource) { // We shouldn't have gotten back an alpha image, if we're alpha'd too. That would imply we're // doing a minipreview of a minipreview (1/16 scale). //ASSERT(!fAlphaDest); BLENDFUNCTION bf; bf.BlendOp = AC_SRC_OVER; bf.SourceConstantAlpha = 255; bf.AlphaFormat = AC_SRC_ALPHA; bf.BlendFlags = 0; if (AlphaBlend(hdc, ptMargin.x, ptMargin.y, sizeDestActual.cx, sizeDestActual.cy, hdcFrom, 0 ,0, sizeSource.cx, sizeSource.cy, bf)) hr = S_OK; } else { // Otherwise, just blt it. int iModeSave = SetStretchBltMode(hdc, HALFTONE); if (StretchBlt(hdc, ptMargin.x, ptMargin.y, sizeDestActual.cx, sizeDestActual.cy, hdcFrom, 0 ,0, sizeSource.cx, sizeSource.cy, SRCCOPY)) hr = S_OK; SetStretchBltMode(hdc, iModeSave); // Are we alpha'd? We didn't have an alpha source, so where we blt'd it, we've // lost the alpha channel. Restore it. if (fAlphaDest) { // Set the alpha channel over where we just blt'd. RECT rc = {ptMargin.x, ptMargin.y, ptMargin.x + sizeDestActual.cx, ptMargin.y + sizeDestActual.cy}; _SetAlpha(rc, sizeFolderBmp, prgbDest); } } SelectObject(hdcFrom, hbmpOld); } DeleteDC(hdcFrom); } return hr; } class CFolderExtractImage : public IExtractImage2, public IPersistPropertyBag, public IAlphaThumbnailExtractor, public IRunnableTask { public: CFolderExtractImage(); STDMETHOD (QueryInterface)(REFIID riid, void **ppv); STDMETHOD_(ULONG, AddRef) (); STDMETHOD_(ULONG, Release) (); // IExtractImage/IExtractLogo STDMETHOD (GetLocation)(LPWSTR pszPath, DWORD cch, DWORD *pdwPriority, const SIZE *prgSize, DWORD dwRecClrDepth, DWORD *pdwFlags); STDMETHOD (Extract)(HBITMAP *phbm); // IExtractImage2 STDMETHOD (GetDateStamp)(FILETIME *pftDateStamp); // IPersist STDMETHOD(GetClassID)(CLSID *pClassID); // IPersistPropertyBag STDMETHOD(InitNew)(); STDMETHOD(Load)(IPropertyBag *ppb, IErrorLog *pErr); STDMETHOD(Save)(IPropertyBag *ppb, BOOL fClearDirty, BOOL fSaveAll) { return E_NOTIMPL; } // IRunnableTask STDMETHOD (Run)(void); STDMETHOD (Kill)(BOOL fWait); STDMETHOD (Suspend)(void); STDMETHOD (Resume)(void); STDMETHOD_(ULONG, IsRunning)(void); // IAlphaThumbnailExtractor STDMETHOD (RequestAlphaThumbnail)(void); STDMETHOD(Init)(IShellFolder *psf, LPCITEMIDLIST pidl); private: ~CFolderExtractImage(); LPCTSTR _GetImagePath(UINT cx); HRESULT _CreateWithMiniPreviews(IShellFolder *psf, const LPCITEMIDLIST *apidlPreviews, BOOL *abTryCached, UINT cpidlPreviews, MINIPREVIEW_LAYOUT uLayout, IShellImageStore *pImageStore, HBITMAP *phBmpThumbnail); HRESULT _FindMiniPreviews(LPITEMIDLIST apidlPreviews[], BOOL abTryCached[], UINT *cpidlPreviews); HRESULT _CheckThumbnailCache(HBITMAP *phbmp); void _CacheThumbnail(HBITMAP hbmp); IExtractImage *_pExtract; IRunnableTask *_pRun; long _cRef; TCHAR _szFolder[MAX_PATH]; TCHAR _szLogo[MAX_PATH]; TCHAR _szWideLogo[MAX_PATH]; IShellFolder2 *_psf; SIZE _size; LPITEMIDLIST _pidl; IPropertyBag *_ppb; LONG _lState; BOOL _fAlpha; DWORD _dwPriority; DWORD _dwRecClrDepth; DWORD _dwExtractFlags; }; STDAPI CFolderExtractImage_Create(IShellFolder *psf, LPCITEMIDLIST pidl, REFIID riid, void **ppv) { HRESULT hr = E_OUTOFMEMORY; CFolderExtractImage *pfei = new CFolderExtractImage; if (pfei) { hr = pfei->Init(psf, pidl); if (SUCCEEDED(hr)) hr = pfei->QueryInterface(riid, ppv); pfei->Release(); } return hr; } CFolderExtractImage::CFolderExtractImage() : _cRef(1), _lState(IRTIR_TASK_NOT_RUNNING) { } CFolderExtractImage::~CFolderExtractImage() { ATOMICRELEASE(_pExtract); ATOMICRELEASE(_psf); ILFree(_pidl); ATOMICRELEASE(_ppb); } STDMETHODIMP CFolderExtractImage::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT (CFolderExtractImage, IExtractImage2), QITABENTMULTI (CFolderExtractImage, IExtractImage, IExtractImage2), QITABENTMULTI2(CFolderExtractImage, IID_IExtractLogo, IExtractImage2), QITABENT (CFolderExtractImage, IPersistPropertyBag), QITABENT (CFolderExtractImage, IRunnableTask), QITABENT (CFolderExtractImage, IAlphaThumbnailExtractor), QITABENTMULTI (CFolderExtractImage, IPersist, IPersistPropertyBag), { 0 }, }; return QISearch(this, qit, riid, ppv); } STDMETHODIMP_(ULONG) CFolderExtractImage::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CFolderExtractImage::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } STDMETHODIMP CFolderExtractImage::GetDateStamp(FILETIME *pftDateStamp) { HANDLE h = CreateFile(_szFolder, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); HRESULT hr = (h != INVALID_HANDLE_VALUE) ? S_OK : E_FAIL; if (SUCCEEDED(hr)) { hr = GetFileTime(h, NULL, NULL, pftDateStamp) ? S_OK : E_FAIL; CloseHandle(h); } return hr; } HRESULT CFolderExtractImage::InitNew() { IPropertyBag *ppb; // load up the default property bag for peruser perfolder // may have problems down the line with thumbs.db being alluser. if (SUCCEEDED(SHGetViewStatePropertyBag(_pidl, VS_BAGSTR_EXPLORER, SHGVSPB_PERUSER | SHGVSPB_PERFOLDER, IID_PPV_ARG(IPropertyBag, &ppb)))) { IUnknown_Set((IUnknown**)&_ppb, ppb); ppb->Release(); } // return success always -- SHGVSPB can fail if _pidl is on a removable drive, // but we still want to do our thing. return S_OK; } HRESULT CFolderExtractImage::Load(IPropertyBag *ppb, IErrorLog *pErr) { IUnknown_Set((IUnknown**)&_ppb, ppb); return S_OK; } LPCTSTR CFolderExtractImage::_GetImagePath(UINT cx) { if (!_szLogo[0]) { if (_ppb && SUCCEEDED(SHPropertyBag_ReadStr(_ppb, TEXT("Logo"), _szLogo, ARRAYSIZE(_szLogo))) && _szLogo[0]) { if (SUCCEEDED(SHPropertyBag_ReadStr(_ppb, TEXT("WideLogo"), _szWideLogo, ARRAYSIZE(_szWideLogo))) && _szWideLogo[0]) if (!PathCombine(_szWideLogo, _szFolder, _szWideLogo)) // relative path support ZeroMemory(_szWideLogo, sizeof(_szWideLogo)); if (!PathCombine(_szLogo, _szFolder, _szLogo)) // relative path support ZeroMemory(_szLogo, sizeof(_szLogo)); } else { TCHAR szFind[MAX_PATH]; for (int i = 0; i < ARRAYSIZE(c_szFolderThumbnailPaths); i++) { if (PathCombine(szFind, _szFolder, c_szFolderThumbnailPaths[i])) { if (PathFileExists(szFind)) { if (SUCCEEDED(StringCchCopy(_szLogo, ARRAYSIZE(_szLogo), szFind))) { break; } else { ZeroMemory(_szLogo,(sizeof(_szLogo))); } } } } } } LPCTSTR psz = ((cx > 120) && _szWideLogo[0]) ? _szWideLogo : _szLogo; return *psz ? psz : NULL; } STDMETHODIMP CFolderExtractImage::RequestAlphaThumbnail() { _fAlpha = TRUE; return S_OK; } STDMETHODIMP CFolderExtractImage::GetLocation(LPWSTR pszPath, DWORD cch, DWORD *pdwPriority, const SIZE *prgSize, DWORD dwRecClrDepth, DWORD *pdwFlags) { HRESULT hr; hr = StringCchCopy(pszPath, cch, _szFolder); if (SUCCEEDED(hr)) { _size = *prgSize; _dwRecClrDepth = dwRecClrDepth; _dwExtractFlags = *pdwFlags; if (pdwFlags) { if (*pdwFlags & IEIFLAG_ASYNC) hr = E_PENDING; *pdwFlags &= ~IEIFLAG_CACHE; // We handle the caching of this thumbnail inside the folder *pdwFlags |= IEIFLAG_REFRESH; // We still want to handle the refresh verb } if (pdwPriority) { _dwPriority = *pdwPriority; *pdwPriority = 1; // very low } } return hr; } STDMETHODIMP CFolderExtractImage::Extract(HBITMAP *phbm) { // Set it to running (only if we're in the not running state). LONG lResOld = InterlockedCompareExchange(&_lState, IRTIR_TASK_RUNNING, IRTIR_TASK_NOT_RUNNING); if (lResOld != IRTIR_TASK_NOT_RUNNING) { // If we weren't in the not running state, bail. return E_FAIL; } // If we have an extractor, use that. HRESULT hr = E_FAIL; hr = _CheckThumbnailCache(phbm); if (FAILED(hr)) { LPITEMIDLIST apidlPreviews[MAX_MINIPREVIEWS_COLLECT]; BOOL abTryCached[MAX_MINIPREVIEWS_COLLECT]; UINT cpidlPreviews = 0; LPCTSTR pszLogo = _GetImagePath(_size.cx); if (pszLogo) { // Don't do the standard mini-previews - we've got a special thumbnail ATOMICRELEASE(_pExtract); LPITEMIDLIST pidl; hr = SHILCreateFromPath(pszLogo, &pidl, NULL); if (SUCCEEDED(hr)) { LPCITEMIDLIST pidlChild; IShellFolder* psfLogo; hr = SHBindToIDListParent(pidl, IID_PPV_ARG(IShellFolder, &psfLogo), &pidlChild); if (SUCCEEDED(hr)) { hr = _CreateWithMiniPreviews(psfLogo, &pidlChild, NULL, 1, MINIPREVIEW_LAYOUT_1, NULL, phbm); psfLogo->Release(); } ILFree(pidl); } } else { const struct { int csidl; int res; } thumblist[] = { {CSIDL_PERSONAL, IDI_MYDOCS}, {CSIDL_MYMUSIC, IDI_MYMUSIC}, {CSIDL_MYPICTURES, IDI_MYPICS}, {CSIDL_MYVIDEO, IDI_MYVIDEOS}, {CSIDL_COMMON_DOCUMENTS, IDI_MYDOCS}, {CSIDL_COMMON_MUSIC, IDI_MYMUSIC}, {CSIDL_COMMON_PICTURES, IDI_MYPICS}, {CSIDL_COMMON_VIDEO, IDI_MYVIDEOS} }; BOOL bFound = FALSE; for (int i=0; i < ARRAYSIZE(thumblist) && !bFound; i++) { TCHAR szPath[MAX_PATH]; SHGetFolderPath(NULL, thumblist[i].csidl, NULL, 0, szPath); if (!lstrcmp(_szFolder, szPath)) { // We return failure in this case so that the requestor can do // the default action. hr = E_FAIL; bFound = TRUE; } } if (!bFound) { // Mini-previews. IShellImageStore *pDiskCache = NULL; // It's ok if this fails. if (!SHRestricted(REST_NOTHUMBNAILCACHE) && !SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("DisableThumbnailCache"), 0, FALSE) && !(_dwExtractFlags & IEIFLAG_QUALITY)) { LoadFromFile(CLSID_ShellThumbnailDiskCache, _szFolder, IID_PPV_ARG(IShellImageStore, &pDiskCache)); } cpidlPreviews = ARRAYSIZE(apidlPreviews); hr = _FindMiniPreviews(apidlPreviews, abTryCached, &cpidlPreviews); if (SUCCEEDED(hr)) { if (cpidlPreviews) { hr = _CreateWithMiniPreviews(_psf, apidlPreviews, abTryCached, cpidlPreviews, _GetMiniPreviewLayout(_size), pDiskCache, phbm); FreeMiniPreviewPidls(apidlPreviews, cpidlPreviews); } else { // We return failure in this case so that the requestor can do // the default action hr = E_FAIL; } } ATOMICRELEASE(pDiskCache); } } if (SUCCEEDED(hr) && *phbm) { _CacheThumbnail(*phbm); } } return hr; } STDMETHODIMP CFolderExtractImage::GetClassID(CLSID *pClassID) { return E_NOTIMPL; } STDMETHODIMP CFolderExtractImage::Init(IShellFolder *psf, LPCITEMIDLIST pidl) { HRESULT hr = DisplayNameOf(psf, pidl, SHGDN_FORPARSING, _szFolder, ARRAYSIZE(_szFolder)); if (SUCCEEDED(hr)) { LPITEMIDLIST pidlFolder; hr = SHGetIDListFromUnk(psf, &pidlFolder); if (SUCCEEDED(hr)) { hr = SHILCombine(pidlFolder, pidl, &_pidl); if (SUCCEEDED(hr)) { // hold the _psf for this guy so we can enum hr = psf->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder2, &_psf)); if (SUCCEEDED(hr)) { hr = InitNew(); } } ILFree(pidlFolder); } } return hr; } // Not necessary --- IExtractImage::Extract() starts us up. STDMETHODIMP CFolderExtractImage::Run(void) { return E_NOTIMPL; } STDMETHODIMP CFolderExtractImage::Kill(BOOL fWait) { // Try to kill the current subextraction task that's running, if any. if (_pRun != NULL) { _pRun->Kill(fWait); // If it didn't work, no big deal, we'll complete this subextraction task, // and bail before starting the next one. } // If we're running, set to pending. LONG lResOld = InterlockedCompareExchange(&_lState, IRTIR_TASK_PENDING, IRTIR_TASK_RUNNING); if (lResOld == IRTIR_TASK_RUNNING) { // We've now set it to pending - ready to die. return S_OK; } else if (lResOld == IRTIR_TASK_PENDING || lResOld == IRTIR_TASK_FINISHED) { // We've already been killed. return S_FALSE; } return E_FAIL; } STDMETHODIMP CFolderExtractImage::Suspend(void) { return E_NOTIMPL; } STDMETHODIMP CFolderExtractImage::Resume(void) { return E_NOTIMPL; } STDMETHODIMP_(ULONG) CFolderExtractImage::IsRunning(void) { return _lState; } HRESULT CFolderExtractImage::_CreateWithMiniPreviews(IShellFolder *psf, const LPCITEMIDLIST *apidlPreviews, BOOL *abTryCached, UINT cpidlPreviews, MINIPREVIEW_LAYOUT uLayout, IShellImageStore *pImageStore, HBITMAP *phBmpThumbnail) { *phBmpThumbnail = NULL; HBITMAP hbmpOld; HDC hdc; SIZE sizeOriginal; // Size of the source bitmaps that go into the minipreview. SIZE sizeFolderBmp; // Size of the folder bmp we use for the background. SIZE sizeMiniPreview; // The size calculated for the minipreviews POINT aptOrigins[MAX_MINIPREVIEWS]; RGBQUAD* prgb; // the bits of the destination bitmap. _GetMiniPreviewLocations(uLayout, _size, &sizeFolderBmp, aptOrigins, &sizeMiniPreview); // sizeFolderBmp is the size of the folder background bitmap that we're working with, // not the size of the final thumbnail. HRESULT hr = _CreateMainRenderingDC(&hdc, phBmpThumbnail, &hbmpOld, sizeFolderBmp.cx, sizeFolderBmp.cy, &prgb); if (SUCCEEDED(hr)) { BOOL fIsAlphaBackground; hr = _DrawMiniPreviewBackground(hdc, sizeFolderBmp, _fAlpha, &fIsAlphaBackground, prgb); if (SUCCEEDED(hr)) { ULONG uPreviewLocation = 0; // Extract the images for the minipreviews for (ULONG i = 0 ; i < cpidlPreviews && uPreviewLocation < ARRAYSIZE(aptOrigins) ; i++) { BOOL bFoundAlphaImage = FALSE; // If we've been killed, stop the processing the minipreviews: // PENDING?, we're now FINISHED. InterlockedCompareExchange(&_lState, IRTIR_TASK_FINISHED, IRTIR_TASK_PENDING); if (_lState == IRTIR_TASK_FINISHED) { // Get out. hr = E_FAIL; break; } HBITMAP hbmpSubs; BOOL bFoundImage = FALSE; // Try the image store first DWORD dwLock; HRESULT hr2 = (pImageStore && abTryCached[i]) ? pImageStore->Open(STGM_READ, &dwLock) : E_FAIL; if (SUCCEEDED(hr2)) { // Get the fullpidl of this guy. TCHAR szSubPath[MAX_PATH]; if (SUCCEEDED(DisplayNameOf(psf, apidlPreviews[i], SHGDN_INFOLDER | SHGDN_FORPARSING, szSubPath, MAX_PATH))) { if (SUCCEEDED(pImageStore->GetEntry(szSubPath, STGM_READ, &hbmpSubs))) { bFoundImage = TRUE; } } pImageStore->ReleaseLock(&dwLock); } // Resort to calling extractor if the image was not in the cache. if (!bFoundImage) { IExtractImage *peiSub; hr2 = psf->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&apidlPreviews[i], IID_X_PPV_ARG(IExtractImage, NULL, &peiSub)); if (SUCCEEDED(hr2)) { // Now extract the image. DWORD dwPriority = 0; DWORD dwFlags = IEIFLAG_ORIGSIZE | IEIFLAG_QUALITY;// ORIGSIZE -> preserve aspect ratio WCHAR szPathBuffer[MAX_PATH]; hr2 = peiSub->GetLocation(szPathBuffer, ARRAYSIZE(szPathBuffer), &dwPriority, &sizeMiniPreview, 24, &dwFlags); if (SUCCEEDED(hr2) && StrCmpIW(szPathBuffer, _szFolder)) // Avoid simple recursion { IAlphaThumbnailExtractor *pati; if (SUCCEEDED(peiSub->QueryInterface(IID_PPV_ARG(IAlphaThumbnailExtractor, &pati)))) { if (SUCCEEDED(pati->RequestAlphaThumbnail())) { bFoundAlphaImage = TRUE; } pati->Release(); } // After we check for IRTIR_TASK_PENDING, but before // we call peiSub->Extract, it is possible someone calls // Kill on us. // Since _pRun will be NULL, we will not kill // the subtask, but will instead continue and call extract // on it, and not bail until we try the next subthumbnail. // Oh well. // We could add another check here to reduce the window of // opportunity in which this could happen. // Try to get an IRunnableTask so that we can stop execution // of this subtask if necessary. peiSub->QueryInterface(IID_PPV_ARG(IRunnableTask, &_pRun)); if (SUCCEEDED(peiSub->Extract(&hbmpSubs))) { bFoundImage = TRUE; } ATOMICRELEASE(_pRun); } peiSub->Release(); } } // Add the extracted bitmap to the main one... if (bFoundImage) { // The bitmap will of course need to be resized: BITMAP rgBitmap; if (::GetObject((HGDIOBJ)hbmpSubs, sizeof(rgBitmap), &rgBitmap)) { sizeOriginal.cx = rgBitmap.bmWidth; sizeOriginal.cy = rgBitmap.bmHeight; // We need to check if this is really an alpha bitmap. It's possible that the // extractor said it could generate one, but ended up not being able to. if (bFoundAlphaImage) { RECT rc = {0, 0, rgBitmap.bmWidth, rgBitmap.bmHeight}; bFoundAlphaImage = (rgBitmap.bmBitsPixel == 32) && _HasAlpha(rc, rgBitmap.bmWidth, (RGBQUAD*)rgBitmap.bmBits); } } else { // Couldn't get the info, oh well, no resize. // alpha may also be screwed up here, but oh well. sizeOriginal = sizeMiniPreview; } if (SUCCEEDED(_AddBitmap(hdc, hbmpSubs, aptOrigins[uPreviewLocation], sizeMiniPreview, sizeOriginal, bFoundAlphaImage, fIsAlphaBackground, prgb, sizeFolderBmp))) { uPreviewLocation++; } DeleteObject(hbmpSubs); } } if (!uPreviewLocation) { // For whatever reason, we have no mini thumbnails to show, so fail this entire extraction. hr = E_FAIL; } } if (SUCCEEDED(hr)) { // Is the requested size one of the sizes of the folder background bitmaps? // Test against smallest requested dimension, because we're square, and we'll fit into that rectangle int iSmallestDimension = min(_size.cx, _size.cy); if ((sizeFolderBmp.cx != iSmallestDimension) || (sizeFolderBmp.cy != iSmallestDimension)) { // Nope - we need to do some scaling. // Create another dc and bitmap the size of the requested bitmap HBITMAP hBmpThumbnailFinal = NULL; HBITMAP hbmpOld2; HDC hdcFinal; RGBQUAD *prgbFinal; hr = _CreateMainRenderingDC(&hdcFinal, &hBmpThumbnailFinal, &hbmpOld2, iSmallestDimension, iSmallestDimension, &prgbFinal); if (SUCCEEDED(hr)) { // Now scale it. if (fIsAlphaBackground) { BLENDFUNCTION bf; bf.BlendOp = AC_SRC_OVER; bf.SourceConstantAlpha = 255; bf.AlphaFormat = AC_SRC_ALPHA; bf.BlendFlags = 0; if (AlphaBlend(hdcFinal, 0, 0, iSmallestDimension, iSmallestDimension, hdc, 0 ,0, sizeFolderBmp.cx, sizeFolderBmp.cy, bf)) hr = S_OK; } else { int iModeSave = SetStretchBltMode(hdcFinal, HALFTONE); if (StretchBlt(hdcFinal, 0, 0, iSmallestDimension, iSmallestDimension, hdc, 0 ,0, sizeFolderBmp.cx, sizeFolderBmp.cy, SRCCOPY)) hr = S_OK; SetStretchBltMode(hdcFinal, iModeSave); } // Destroy the dc. _DestroyMainRenderingDC(hdcFinal, hbmpOld2); // Now do a switcheroo // Don't need to check for success here. Down below, we'll delete *phBmpThumbnail // if StretchBlt FAILED - and in that case, *pbBmpThumbnail will be hBmpThumbnailFinal. DeleteObject(*phBmpThumbnail); // delete this, we don't need it. *phBmpThumbnail = hBmpThumbnailFinal; // This is the one we want. } } } _DestroyMainRenderingDC(hdc, hbmpOld); } if (FAILED(hr) && *phBmpThumbnail) // Something didn't work? Make sure we delete our bmp { DeleteObject(*phBmpThumbnail); } return hr; } /** * In/Out: cpidlPreviews - the number of preview items we should look for. Returns the number found. * number of pidls returned is cpidlPreviews. * Out: apidlPreviews - array of pidls found. The caller must free them. */ HRESULT CFolderExtractImage::_FindMiniPreviews(LPITEMIDLIST apidlPreviews[], BOOL abTryCached[], UINT *pcpidlPreviews) { UINT cMaxPreviews = *pcpidlPreviews; int uNumPreviewsSoFar = 0; BOOL bKilled = FALSE; // Make sure our aFileTimes array is the right size... ASSERT(MAX_MINIPREVIEWS_COLLECT == cMaxPreviews); *pcpidlPreviews = 0; // start with none in case of failure IEnumIDList *penum; if (S_OK == _psf->EnumObjects(NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &penum)) { FILETIME aFileTimes[MAX_MINIPREVIEWS_COLLECT] = {0}; LPITEMIDLIST pidl; BOOL bTryCached; while (S_OK == penum->Next(1, &pidl, NULL)) { // _IsMiniPreviewCandidate is a potentially expensive operation, so before // doing it, we'll check to see if anyone has killed us. // Are we PENDING? Then we're FINISHED. InterlockedCompareExchange(&_lState, IRTIR_TASK_FINISHED, IRTIR_TASK_PENDING); // Get out? bKilled = (_lState == IRTIR_TASK_FINISHED); if (!bKilled && _IsMiniPreviewCandidate(_psf, pidl, &bTryCached)) { // Get file time of this guy. FILETIME ft; if (SUCCEEDED(GetDateProperty(_psf, pidl, &SCID_WRITETIME, &ft))) { for (int i = 0; i < uNumPreviewsSoFar; i++) { if (CompareFileTime(&aFileTimes[i], &ft) < 0) { int j; // Put it in this slot. First, move guys down by one. // No need to copy last guy: if (uNumPreviewsSoFar == (int)cMaxPreviews) { j = (cMaxPreviews - 2); // And we must free the pidl we're nuking. ILFree(apidlPreviews[cMaxPreviews - 1]); apidlPreviews[cMaxPreviews - 1] = NULL; } else { j = uNumPreviewsSoFar - 1; uNumPreviewsSoFar++; } for (; j >= i; j--) { apidlPreviews[j+1] = apidlPreviews[j]; abTryCached[j+1] = abTryCached[j]; aFileTimes[j+1] = aFileTimes[j]; } aFileTimes[i] = ft; apidlPreviews[i] = pidl; abTryCached[i] = bTryCached; pidl = NULL; // don't free break; // for loop } } // Did we complete the loop? if (i == uNumPreviewsSoFar) { if (i < (int)cMaxPreviews) { // We still have room for more previews, so tack this on at the end. uNumPreviewsSoFar++; aFileTimes[i] = ft; apidlPreviews[i] = pidl; abTryCached[i] = bTryCached; pidl = NULL; // don't free below } } *pcpidlPreviews = uNumPreviewsSoFar; } } ILFree(pidl); // NULL pidl OK if (bKilled) { break; } } penum->Release(); } if (bKilled) { FreeMiniPreviewPidls(apidlPreviews, *pcpidlPreviews); *pcpidlPreviews = 0; return E_FAIL; } else { return (uNumPreviewsSoFar > 0) ? S_OK : S_FALSE; } } HRESULT CFolderExtractImage::_CheckThumbnailCache(HBITMAP* phbmp) { HRESULT hr = E_FAIL; if (!SHRestricted(REST_NOTHUMBNAILCACHE) && !SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("DisableThumbnailCache"), 0, FALSE) && !(_dwExtractFlags & IEIFLAG_QUALITY)) { IShellImageStore *pDiskCache = NULL; hr = LoadFromFile(CLSID_ShellThumbnailDiskCache, _szFolder, IID_PPV_ARG(IShellImageStore, &pDiskCache)); if (SUCCEEDED(hr)) { DWORD dwLock; hr = pDiskCache->Open(STGM_READ, &dwLock); if (SUCCEEDED(hr)) { FILETIME ftTimeStamp = {0,0}; hr = GetDateStamp(&ftTimeStamp); if (SUCCEEDED(hr)) { FILETIME ftTimeStampCache = {0,0}; hr = pDiskCache->IsEntryInStore(FOLDER_GUID, &ftTimeStampCache); if (SUCCEEDED(hr)) { if (hr == S_OK && (0 == CompareFileTime(&ftTimeStampCache, &ftTimeStamp))) { hr = pDiskCache->GetEntry(FOLDER_GUID, STGM_READ, phbmp); } else { hr = E_FAIL; } } } pDiskCache->ReleaseLock(&dwLock); pDiskCache->Close(NULL); } pDiskCache->Release(); } } TraceMsg(TF_DEFVIEW, "CFolderExtractImage::_CheckThumbnailCache (%s, %x)", _szFolder, hr); return hr; } void CFolderExtractImage::_CacheThumbnail(HBITMAP hbmp) { HRESULT hr = E_UNEXPECTED; if (!SHRestricted(REST_NOTHUMBNAILCACHE) && !SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("DisableThumbnailCache"), 0, FALSE)) { SIZE sizeThumbnail; SHGetThumbnailSize(&sizeThumbnail); // Don't cache the mini-thumbnail preview if (sizeThumbnail.cx == _size.cx && sizeThumbnail.cy == _size.cy) { IShellImageStore *pDiskCache = NULL; hr = LoadFromIDList(CLSID_ShellThumbnailDiskCache, _pidl, IID_PPV_ARG(IShellImageStore, &pDiskCache)); if (SUCCEEDED(hr)) { DWORD dwLock; hr = pDiskCache->Open(STGM_READWRITE, &dwLock); if (hr == STG_E_FILENOTFOUND) { if (!IsCopyEngineRunning()) { hr = pDiskCache->Create(STGM_WRITE, &dwLock); } } if (SUCCEEDED(hr)) { FILETIME ftTimeStamp = {0,0}; hr = GetDateStamp(&ftTimeStamp); if (SUCCEEDED(hr)) { hr = pDiskCache->AddEntry(FOLDER_GUID, &ftTimeStamp, STGM_WRITE, hbmp); } pDiskCache->ReleaseLock(&dwLock); pDiskCache->Close(NULL); } pDiskCache->Release(); } } } TraceMsg(TF_DEFVIEW, "CFolderExtractImage::_CacheThumbnail (%s, %x)", _szFolder, hr); }