summaryrefslogtreecommitdiff
path: root/lib/Thumbnail
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Thumbnail')
-rw-r--r--lib/Thumbnail/Thumbnail.vcxproj12
-rw-r--r--lib/Thumbnail/thumbnail.cpp514
-rw-r--r--lib/Thumbnail/thumbnail.h45
3 files changed, 449 insertions, 122 deletions
diff --git a/lib/Thumbnail/Thumbnail.vcxproj b/lib/Thumbnail/Thumbnail.vcxproj
index de9b22ae..2045f10e 100644
--- a/lib/Thumbnail/Thumbnail.vcxproj
+++ b/lib/Thumbnail/Thumbnail.vcxproj
@@ -64,10 +64,10 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
- <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\</OutDir>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\</OutDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
<LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</LinkIncremental>
- <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\</OutDir>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\</OutDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
<LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</LinkIncremental>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\</OutDir>
@@ -87,7 +87,7 @@
</BuildLog>
<ClCompile>
<Optimization>Disabled</Optimization>
- <PreprocessorDefinitions>FFS_WIN;_DEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>FFS_WIN;FFS_WIN;_DEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>true</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
@@ -122,7 +122,7 @@
</Midl>
<ClCompile>
<Optimization>Disabled</Optimization>
- <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>FFS_WIN;_DEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>true</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
@@ -155,7 +155,7 @@
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
- <PreprocessorDefinitions>FFS_WIN;NDEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>FFS_WIN;FFS_WIN;NDEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader>
@@ -192,7 +192,7 @@
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
- <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>FFS_WIN;NDEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader>
diff --git a/lib/Thumbnail/thumbnail.cpp b/lib/Thumbnail/thumbnail.cpp
index 284d1093..b00ce81d 100644
--- a/lib/Thumbnail/thumbnail.cpp
+++ b/lib/Thumbnail/thumbnail.cpp
@@ -5,169 +5,481 @@
// **************************************************************************
#include "thumbnail.h"
+#include <algorithm>
#include <string>
#define WIN32_LEAN_AND_MEAN
#include <zen/win.h>
+#include <zen/win_ver.h>
#define STRICT_TYPED_ITEMIDS //better type safety for IDLists
#include <Shlobj.h>
#include <Shellapi.h>
#include <CommonControls.h>
+
+#include <zen/com_error.h>
#include <zen/com_ptr.h>
#include <zen/string_tools.h>
#include <zen/scope_guard.h>
+#include <zen/basic_math.h>
+//#include <zen/perf.h>
using namespace zen;
+namespace
+{
+thumb::ImageData* allocImageData(int width, int height) //throw ComError; return value always bound!
+{
+ ZEN_COM_ASSERT(width >= 0 && height >= 0); //throw ComError
+
+ std::unique_ptr<thumb::ImageData> idata = make_unique<thumb::ImageData>();
+
+ idata->width = width;
+ idata->height = height;
+ idata->rgb = new unsigned char[width * height * 4];
+ idata->alpha = idata->rgb + width * height * 3;
+
+ return idata.release();
+}
-thumb::HICON thumb::getThumbnail(const wchar_t* filename, int requestedSize) //return 0 on failure, caller takes ownership!
+void releaseImageData_impl(const thumb::ImageData* id)
{
- const std::wstring filenameStr(filename);
+ if (id)
+ {
+ delete [] id->rgb;
+ delete id;
+ }
+}
- ComPtr<IShellFolder> desktopFolder;
+
+//caller takes ownership!
+HICON createIconFromBitmap(HBITMAP bitmap) //throw ComError
+{
+ BITMAP bmpInfo = {};
+ ZEN_COM_ASSERT(::GetObject(bitmap, //__in HGDIOBJ hgdiobj,
+ sizeof(bmpInfo), //__in int cbBuffer,
+ &bmpInfo)); //__out LPVOID lpvObject
+ //no documented extended error info
+
+ HDC hScreenDC = ::GetDC(nullptr);
+ ZEN_COM_ASSERT(hScreenDC); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::ReleaseDC(nullptr, hScreenDC));
+
+ HBITMAP bitmapMask = ::CreateCompatibleBitmap(hScreenDC, bmpInfo.bmWidth, bmpInfo.bmHeight);
+ ZEN_COM_ASSERT(bitmapMask); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::DeleteObject(bitmapMask));
+
+ ICONINFO iconInfo = {};
+ iconInfo.fIcon = true;
+ iconInfo.hbmColor = bitmap;
+ iconInfo.hbmMask = bitmapMask;
+
+ HICON result = ::CreateIconIndirect(&iconInfo);
+ if (!result) throw ComError(L"Error calling \"CreateIconIndirect\".", GetLastError());
+ return result;
+}
+
+
+//caller takes ownership!
+thumb::ImageData* convertToImageData(HBITMAP bmp) //throw ComError
+{
+ //GetDIBits ????
+
+ BITMAP bmpInfo = {};
+ ZEN_COM_ASSERT(::GetObject(bmp, //__in HGDIOBJ hgdiobj,
+ sizeof(BITMAP), //__in int cbBuffer,
+ &bmpInfo)); //__out LPVOID lpvObject
+
+ HDC hScreenDC = ::GetDC(nullptr);
+ ZEN_COM_ASSERT(hScreenDC); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::ReleaseDC(nullptr, hScreenDC));
+
+ //32-bit RGB with alpha channel support
+ BITMAPV5HEADER bi = {};
+ bi.bV5Size = sizeof(bi);
+ bi.bV5Width = bmpInfo.bmWidth;
+ bi.bV5Height = -bmpInfo.bmHeight; //negative for top left origin
+ bi.bV5Planes = 1;
+ bi.bV5BitCount = 32;
+ bi.bV5Compression = BI_BITFIELDS;
+ bi.bV5AlphaMask = 0xFF000000;
+ bi.bV5RedMask = 0x00FF0000;
+ bi.bV5GreenMask = 0x0000FF00;
+ bi.bV5BlueMask = 0x000000FF;
+ unsigned char* bitsRgbBmp = nullptr;
+
+ HBITMAP rgbBmp = ::CreateDIBSection(hScreenDC, //_In_ HDC hdc,
+ reinterpret_cast<const BITMAPINFO*>(&bi), //_In_ const BITMAPINFO *pbmi,
+ DIB_RGB_COLORS, //_In_ UINT iUsage,
+ reinterpret_cast<VOID**>(&bitsRgbBmp), //_Out_ VOID **ppvBits,
+ nullptr, //_In_ HANDLE hSection,
+ 0); //_In_ DWORD dwOffset
+ ZEN_COM_ASSERT(rgbBmp); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::DeleteObject(rgbBmp););
+ ZEN_COM_ASSERT(bitsRgbBmp); //check after rgbBmp is owned by us
+
+ HDC memDCSrc = ::CreateCompatibleDC(hScreenDC);
+ ZEN_COM_ASSERT(memDCSrc); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::DeleteDC(memDCSrc));
+
+ HDC memDCTrg = ::CreateCompatibleDC(hScreenDC);
+ ZEN_COM_ASSERT(memDCTrg); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::DeleteDC(memDCTrg));
+
+ HGDIOBJ hgdiSrcOld = ::SelectObject(memDCSrc, bmp);
+ ZEN_COM_ASSERT(hgdiSrcOld); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::SelectObject(memDCSrc, hgdiSrcOld));
+
+ HGDIOBJ hgdiTrgOld = ::SelectObject(memDCTrg, rgbBmp);
+ ZEN_COM_ASSERT(hgdiTrgOld); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::SelectObject(memDCTrg, hgdiTrgOld));
+
+ if (!::BitBlt(memDCTrg, //_In_ HDC hdcDest,
+ 0, //_In_ int nXDest,
+ 0, //_In_ int nYDest,
+ bmpInfo.bmWidth, //_In_ int nWidth,
+ bmpInfo.bmHeight, //_In_ int nHeight,
+ memDCSrc, //_In_ HDC hdcSrc,
+ 0, //_In_ int nXSrc,
+ 0, //_In_ int nYSrc,
+ SRCCOPY)) //_In_ DWORD dwRop
+ throw ComError(L"Error calling \"BitBlt\".", GetLastError());
+
+ //CreateDIBSection: "Access to the bitmap must be synchronized. [...]. This applies to any use of the pointer to the bitmap bit values."
+ /*bool rv = */
+ ::GdiFlush();
+
+ thumb::ImageData* imgOut = allocImageData(bmpInfo.bmWidth, bmpInfo.bmHeight); //throw ComError
+ ScopeGuard guardImgData = zen::makeGuard([&] { releaseImageData_impl(imgOut); });
+
+ unsigned char* rgbPtr = imgOut->rgb;
+ unsigned char* alphaPtr = imgOut->alpha;
+
+ for (int i = 0; i < bmpInfo.bmWidth * bmpInfo.bmHeight; ++i)
{
- HRESULT hr = ::SHGetDesktopFolder(desktopFolder.init());
- if (FAILED(hr) || !desktopFolder)
- return nullptr;
+ unsigned char b = *bitsRgbBmp++;
+ unsigned char g = *bitsRgbBmp++;
+ unsigned char r = *bitsRgbBmp++;
+ unsigned char a = *bitsRgbBmp++;
+
+ *rgbPtr++ = r;
+ *rgbPtr++ = g;
+ *rgbPtr++ = b;
+ *alphaPtr++ = a;
}
+ guardImgData.dismiss();
+ return imgOut;
+}
+
+
+//caller takes ownership!
+const thumb::ImageData* getThumbnail_impl(const wchar_t* filename, int requestedSize) //throw ComError
+{
+ const std::wstring filenameStr(filename);
+
+ ComPtr<IShellFolder> desktopFolder;
+ ZEN_COM_CHECK(::SHGetDesktopFolder(desktopFolder.init())); //throw ComError
+ ZEN_COM_ASSERT(desktopFolder); //throw ComError -> better safe than sorry?
+
PIDLIST_RELATIVE pidlFolder = nullptr;
{
- const std::wstring& pathName = beforeLast(filenameStr, L'\\');
- HRESULT hr = desktopFolder->ParseDisplayName(nullptr, // [in] HWND hwnd,
- nullptr, // [in] IBindCtx *pbc,
- const_cast<LPWSTR>(pathName.c_str()), // [in] LPWSTR pszDisplayName,
- nullptr, // [out] ULONG *pchEaten,
- &pidlFolder, // [out] PIDLIST_RELATIVE* ppidl,
- nullptr); // [in, out] ULONG *pdwAttributes
- if (FAILED(hr) || !pidlFolder)
- return nullptr;
+ std::wstring pathName = beforeLast(filenameStr, L'\\');
+ ZEN_COM_CHECK(desktopFolder->ParseDisplayName(nullptr, // [in] HWND hwnd,
+ nullptr, // [in] IBindCtx *pbc,
+ const_cast<LPWSTR>(pathName.c_str()), // [in] LPWSTR pszDisplayName,
+ nullptr, // [out] ULONG *pchEaten,
+ &pidlFolder, // [out] PIDLIST_RELATIVE* ppidl,
+ nullptr)); // [in, out] ULONG *pdwAttributes
}
+ ZEN_COM_ASSERT(pidlFolder);
ZEN_ON_SCOPE_EXIT(::ILFree(pidlFolder)); //older version: ::CoTaskMemFree
ComPtr<IShellFolder> imageFolder;
- {
- HRESULT hr = desktopFolder->BindToObject(pidlFolder, // [in] PCUIDLIST_RELATIVE pidl,
- nullptr, // [in] IBindCtx *pbc,
- IID_PPV_ARGS(imageFolder.init()));
- if (FAILED(hr) || !imageFolder)
- return nullptr;
- }
+ ZEN_COM_CHECK(desktopFolder->BindToObject(pidlFolder, // [in] PCUIDLIST_RELATIVE pidl,
+ nullptr, // [in] IBindCtx *pbc,
+ IID_PPV_ARGS(imageFolder.init())));
+ ZEN_COM_ASSERT(imageFolder);
PIDLIST_RELATIVE pidImage = nullptr;
{
- const std::wstring& shortName = afterLast(filenameStr, L'\\');
- HRESULT hr = imageFolder->ParseDisplayName(nullptr, // [in] HWND hwnd,
- nullptr, // [in] IBindCtx *pbc,
- const_cast<LPWSTR>(shortName.c_str()), // [in] LPWSTR pszDisplayName,
- nullptr, // [out] ULONG *pchEaten,
- &pidImage, // [out] PIDLIST_RELATIVE *ppidl,
- nullptr); // [in, out] ULONG *pdwAttributes
- if (FAILED(hr) || !pidImage)
- return nullptr;
+ std::wstring shortName = afterLast(filenameStr, L'\\');
+ ZEN_COM_CHECK(imageFolder->ParseDisplayName(nullptr, // [in] HWND hwnd,
+ nullptr, // [in] IBindCtx *pbc,
+ const_cast<LPWSTR>(shortName.c_str()), // [in] LPWSTR pszDisplayName,
+ nullptr, // [out] ULONG *pchEaten,
+ &pidImage, // [out] PIDLIST_RELATIVE *ppidl,
+ nullptr)); // [in, out] ULONG *pdwAttributes
}
+ ZEN_COM_ASSERT(pidImage);
ZEN_ON_SCOPE_EXIT(::ILFree(pidImage)); //older version: ::CoTaskMemFree
ComPtr<IExtractImage> extractImage;
- {
- PCUITEMID_CHILD_ARRAY pidlIn = reinterpret_cast<PCUITEMID_CHILD_ARRAY>(&pidImage);
- //this is where STRICT_TYPED_ITEMIDS gets us ;)
-
- HRESULT hr = imageFolder->GetUIObjectOf(nullptr, // [in] HWND hwndOwner,
- 1, // [in] UINT cidl,
- pidlIn, // [in] PCUITEMID_CHILD_ARRAY apidl,
- IID_IExtractImage, // [in] REFIID riid,
- nullptr, // [in, out] UINT *rgfReserved,
- reinterpret_cast<void**>(extractImage.init())); // [out] void **ppv
- if (FAILED(hr) || !extractImage)
- return nullptr;
- }
+ ZEN_COM_CHECK(imageFolder->GetUIObjectOf(nullptr, // [in] HWND hwndOwner,
+ 1, // [in] UINT cidl,
+ reinterpret_cast<PCUITEMID_CHILD_ARRAY>(&pidImage), // [in] PCUITEMID_CHILD_ARRAY apidl,
+ //this is where STRICT_TYPED_ITEMIDS gets us ;)
+ IID_IExtractImage, // [in] REFIID riid,
+ nullptr, // [in, out] UINT *rgfReserved,
+ reinterpret_cast<void**>(extractImage.init()))); // [out] void **ppv
+ ZEN_COM_ASSERT(extractImage);
{
wchar_t pathBuffer[MAX_PATH];
DWORD priority = 0;
- const SIZE prgSize = { requestedSize, requestedSize }; //preferred size only!
- DWORD clrDepth = 32;
+ const SIZE prgSize = { requestedSize, requestedSize };
+ DWORD clrDepth = 32; //"recommended color depth"
DWORD flags = IEIFLAG_SCREEN | IEIFLAG_OFFLINE;
- HRESULT hr = extractImage->GetLocation(pathBuffer, // [out] LPWSTR pszPathBuffer,
- MAX_PATH, // [in] DWORD cchMax,
- &priority, // [out] DWORD *pdwPriority,
- &prgSize, // [in] const SIZE *prgSize,
- clrDepth, // [in] DWORD dwRecClrDepth,
- &flags); // [in, out] DWORD *pdwFlags
- if (FAILED(hr))
- return nullptr;
+ ZEN_COM_CHECK(extractImage->GetLocation(pathBuffer, // [out] LPWSTR pszPathBuffer,
+ MAX_PATH, // [in] DWORD cchMax,
+ &priority, // [out] DWORD *pdwPriority,
+ &prgSize, // [in] const SIZE *prgSize,
+ clrDepth, // [in] DWORD dwRecClrDepth,
+ &flags)); // [in, out] DWORD *pdwFlags
}
HBITMAP bitmap = nullptr;
+ ZEN_COM_CHECK(extractImage->Extract(&bitmap));
+ ZEN_COM_ASSERT(bitmap);
+ ZEN_ON_SCOPE_EXIT(::DeleteObject(bitmap));
+
+ return convertToImageData(bitmap); //throw ComError, pass ownership
+}
+
+
+const bool wereVistaOrLater = vistaOrLater(); //thread-safety: init at startup
+
+//caller takes ownership!
+const thumb::ImageData* getIconByIndex_impl(int iconIndex, thumb::IconSizeType st) //throw ComError
+{
+ //Note:
+ //- using IExtractIcon::Extract is *no* alternative, just as ::SHGetFileInfo(), it only supports small (16x16) and large (32x32) icons
+ //- IShellItemImageFactory::GetImage requires Vista or later
+
+ using namespace thumb;
+ int requestedSize = 16;
+ int shilIconType = SHIL_SMALL; //16x16, size can be customized by the user.
{
- HRESULT hr = extractImage->Extract(&bitmap);
- if (FAILED(hr) || !bitmap)
- return nullptr;
+ if (!wereVistaOrLater && //XP doesn't have jumbo icons
+ (st == ICON_SIZE_128 ||
+ st == ICON_SIZE_256))
+ st = ICON_SIZE_48;
+
+ switch (st)
+ {
+ case ICON_SIZE_16:
+ break;
+ case ICON_SIZE_32:
+ requestedSize = 32;
+ shilIconType = SHIL_LARGE; //32x32, may be 48x48 if "Use large icon" option is set in Display Properties
+ break;
+ case ICON_SIZE_48:
+ requestedSize = 48;
+ shilIconType = SHIL_EXTRALARGE; //48x48, size can be customized by the user.
+ break;
+ case ICON_SIZE_128:
+ requestedSize = 128;
+ shilIconType = SHIL_JUMBO; //256x256 pixels -> scale down!
+ break;
+ case ICON_SIZE_256:
+ requestedSize = 256;
+ shilIconType = SHIL_JUMBO; //256x256 pixels; Vista and later only
+ break;
+ }
}
- ZEN_ON_SCOPE_EXIT(::DeleteObject(bitmap));
+ ComPtr<IImageList> imageList; //perf: 0,12 µs only to get the image list
+ ZEN_COM_CHECK(::SHGetImageList(shilIconType, //__in int iImageList,
+ IID_PPV_ARGS(imageList.init())));
+ ZEN_COM_ASSERT(imageList);
- BITMAP bmpInfo = {};
- if (::GetObject(bitmap, //__in HGDIOBJ hgdiobj,
- sizeof(bmpInfo), //__in int cbBuffer,
- &bmpInfo) == 0) //__out LPVOID lpvObject
- return nullptr;
+ int srcWidth = 0;
+ int srcHeight = 0;
+ ZEN_COM_CHECK(imageList->GetIconSize(&srcWidth, &srcHeight));
- HDC hDC = ::GetDC(nullptr);
- if (!hDC)
- return nullptr;
- ZEN_ON_SCOPE_EXIT(::ReleaseDC(nullptr, hDC));
+ int targetWidth = srcWidth;
+ int targetHeight = srcHeight;
+ bool needDownScale = false; //scale down if required (e.g Vista Jumbo icons/user-customized icon sizes)
- HBITMAP bitmapMask = ::CreateCompatibleBitmap(hDC, bmpInfo.bmWidth, bmpInfo.bmHeight);
- if (!bitmapMask)
- return nullptr;
- ZEN_ON_SCOPE_EXIT(::DeleteObject(bitmapMask));
+ ZEN_COM_ASSERT(srcWidth > 0 && srcHeight > 0 && requestedSize > 0);
- ICONINFO iconInfo = {};
- iconInfo.fIcon = true;
- iconInfo.hbmColor = bitmap;
- iconInfo.hbmMask = bitmapMask;
+ const int maxExtent = std::max(srcWidth, srcHeight);
+ if (requestedSize < maxExtent)
+ {
+ needDownScale = true;
+ targetWidth = srcWidth * requestedSize / maxExtent;
+ targetHeight = srcHeight * requestedSize / maxExtent;
+ }
- return ::CreateIconIndirect(&iconInfo);
-}
+ HDC hScreenDC = ::GetDC(nullptr);
+ ZEN_COM_ASSERT(hScreenDC); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::ReleaseDC(nullptr, hScreenDC));
+ auto createRGBDib = [&](unsigned char** rawBits) -> HBITMAP
+ {
+ BITMAPINFO bi = {};
+ bi.bmiHeader.biSize = sizeof(bi);
+ bi.bmiHeader.biWidth = targetWidth;
+ bi.bmiHeader.biHeight = -targetHeight; //negative for top left origin
+ bi.bmiHeader.biPlanes = 1;
+ bi.bmiHeader.biBitCount = 24; //we don't want an alpha channel
+ bi.bmiHeader.biCompression = BI_RGB;
-thumb::HICON thumb::getIconByIndex(int iconIndex, int shilIconType) //return 0 on failure, caller takes ownership!
-{
- //Note: using IExtractIcon::Extract is *no* alternative, just as ::SHGetFileInfo(), it only supports small (16x16) and large (32x32) icons
+ return ::CreateDIBSection(hScreenDC, //_In_ HDC hdc,
+ &bi, //_In_ const BITMAPINFO *pbmi,
+ DIB_RGB_COLORS, //_In_ UINT iUsage,
+ reinterpret_cast<VOID**>(rawBits), //_Out_ VOID **ppvBits,
+ nullptr, //_In_ HANDLE hSection,
+ 0); //_In_ DWORD dwOffset
+ };
- ComPtr<IImageList> imageList; //perf: 0,12 µs only to get the image list
+ unsigned char* bitsBlackBg = nullptr;
+ HBITMAP bmpBlackBg = createRGBDib(&bitsBlackBg);
+ ZEN_COM_ASSERT(bmpBlackBg); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::DeleteObject(bmpBlackBg););
+ ZEN_COM_ASSERT(bitsBlackBg); //check after bmpBlackBg is owned by us
+
+ HDC memDC = ::CreateCompatibleDC(hScreenDC);
+ ZEN_COM_ASSERT(memDC); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::DeleteDC(memDC));
+
+ HGDIOBJ hgdiOld = ::SelectObject(memDC, bmpBlackBg);
+ ZEN_COM_ASSERT(hgdiOld); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::SelectObject(memDC, hgdiOld));
+
+ IMAGELISTDRAWPARAMS drawParams = {};
+ drawParams.cbSize = sizeof(drawParams);
+ drawParams.hdcDst = memDC;
+ drawParams.i = iconIndex;
+ drawParams.rgbBk = 0x000000; //black
+ drawParams.fStyle = ILD_NORMAL;
+ //other flags: http://msdn.microsoft.com/en-us/library/windows/desktop/bb775230(v=vs.85).aspx
+
+ if (needDownScale)
{
- HRESULT hr = ::SHGetImageList(shilIconType, //__in int iImageList,
- IID_PPV_ARGS(imageList.init()));
- if (FAILED(hr) || !imageList)
- return nullptr;
+ drawParams.fStyle |= ILD_SCALE;
+ drawParams.cx = targetWidth;
+ drawParams.cy = targetHeight;
}
- bool hasAlpha = false; //perf: 0,14 µs
+ //IDO_SHGIOI_LINK does not draw properly in some cases:
+ //Win7: draws link overlay *twice* if SHIL_JUMBO is requested, but icon does not have this size
+ //XP: drawing IDO_SHGIOI_LINK generally draws corrupted icons and links
+ //if (addShortcutOverlay)
+ //{
+ // int linkOverlay = ::SHGetIconOverlayIndex(nullptr, IDO_SHGIOI_LINK); //-1 on error
+ // if (linkOverlay != -1)
+ // {
+ // //int imgIndex = 0;
+ // //if (SUCCEEDED(imageList->GetOverlayImage(linkOverlay, &imgIndex)))
+ // //{
+ // // drawParams.i = imgIndex;
+ // // ZEN_COM_CHECK(imageList->Draw(&drawParams));
+ // //}
+
+ // drawParams.fStyle |= INDEXTOOVERLAYMASK(linkOverlay);
+ // }
+ //}
+
+ ZEN_COM_CHECK(imageList->Draw(&drawParams));
+
+ //-----------------------------------------------
+ //we draw the icon twice on different backgrounds to extract the alpha channel:
+ //- IImageList::GetIcon doesn't properly render SHIL_JUMBO for icons that don't have jumbo sizes, but IImageList::Draw does!
+ //- minor: each HICON consumes 3 GDI handles
+ //- IImageList::Draw does not reliably support alpha channel on device context (Windows XP)
+ //- wxBitmap created from HBITMAP is very unreliable; often drawn incorrectly by wxDC::DrawBitmap => support wxImage instead
+
+ unsigned char* bitsWhiteBg = nullptr;
+ HBITMAP bmpWhiteBg = createRGBDib(&bitsWhiteBg);
+ ZEN_COM_ASSERT(bmpWhiteBg); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::DeleteObject(bmpWhiteBg));
+ ZEN_COM_ASSERT(bitsWhiteBg); //check after bmpWhiteBg is owned by us
+
+ HGDIOBJ hgdiOld2 = ::SelectObject(memDC, bmpWhiteBg);
+ ZEN_COM_ASSERT(hgdiOld2); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::SelectObject(memDC, hgdiOld2));
+
+ drawParams.rgbBk = 0xFFFFFF; //white
+
+ ZEN_COM_CHECK(imageList->Draw(&drawParams));
+
+ //#####################################################################################
+
+ //"Access to the bitmap must be synchronized. [...]. This applies to any use of the pointer to the bitmap bit values."
+ /*bool rv = */
+ ::GdiFlush();
+
+ ImageData* imgOut = allocImageData(targetWidth, targetHeight); //throw ComError
+ ScopeGuard guardImgData = zen::makeGuard([&] { releaseImageData_impl(imgOut); });
+
+ unsigned char* rgbPtr = imgOut->rgb;
+ unsigned char* alphaPtr = imgOut->alpha;
+
+ for (int i = 0; i < targetWidth * targetHeight; ++i)
+ {
+ unsigned char b_black = *bitsBlackBg++;
+ unsigned char g_black = *bitsBlackBg++;
+ unsigned char r_black = *bitsBlackBg++;
+
+ unsigned char b_white = *bitsWhiteBg++;
+ unsigned char g_white = *bitsWhiteBg++;
+ unsigned char r_white = *bitsWhiteBg++;
+
+ const int tmp = 255 + r_black - r_white + //mixed mode arithmetics!
+ 255 + g_black - g_white +
+ 255 + b_black - b_white;
+ unsigned char alpha = static_cast<unsigned char>(numeric::confineCpy(tmp / 3, 0, 255));
+
+ auto calcColor = [&](unsigned char c_black, unsigned char c_white)
+ {
+ return static_cast<unsigned char>(tmp == 0 ? 0 : numeric::confineCpy
+ (255 * (3 * (-255 + c_white + c_black) + tmp) / (2 * tmp), //mixed mode arithmetics!
+ 0, 255));
+ };
+
+ *rgbPtr++ = calcColor(r_black, r_white);
+ *rgbPtr++ = calcColor(g_black, g_white);
+ *rgbPtr++ = calcColor(b_black, b_white);
+ *alphaPtr++ = alpha;
+ }
+
+ guardImgData.dismiss();
+ return imgOut;
+}
+}
+
+
+const thumb::ImageData* thumb::getThumbnail(const wchar_t* filename, int requestedSize) //return 0 on failure, caller takes ownership!
+{
+ try
+ {
+ return getThumbnail_impl(filename, requestedSize); //throw ComError
+ }
+ catch (const ComError&)
{
- DWORD flags = 0;
- HRESULT hr = imageList->GetItemFlags(iconIndex, //[in] int i,
- &flags); //[out] DWORD *dwFlags
- if (SUCCEEDED(hr))
- hasAlpha = flags & ILIF_ALPHA;
+ return nullptr;
}
+}
- ::HICON hIcon = nullptr; //perf: 1,5 ms - the dominant block
+
+const thumb::ImageData* thumb::getIconByIndex(int iconIndex, thumb::IconSizeType st) //return 0 on failure, caller takes ownership!
+{
+ try
{
- HRESULT hr = imageList->GetIcon(iconIndex, // [in] int i,
- (hasAlpha ? ILD_IMAGE : ILD_NORMAL), // [in] UINT flags,
- //ILD_IMAGE -> do not draw mask - fixes glitch with ILD_NORMAL where both mask *and* alpha channel are applied, e.g. for ffs_batch and folder icon
- //other flags: http://msdn.microsoft.com/en-us/library/windows/desktop/bb775230(v=vs.85).aspx
- &hIcon); // [out] HICON *picon
- if (FAILED(hr) || !hIcon)
- return nullptr;
+ return getIconByIndex_impl(iconIndex, st); //throw ComError
}
+ catch (const ComError&)
+ {
+ return nullptr;
+ }
+}
- return hIcon;
-} \ No newline at end of file
+
+void thumb::releaseImageData(const thumb::ImageData* id)
+{
+ releaseImageData_impl(id);
+}
diff --git a/lib/Thumbnail/thumbnail.h b/lib/Thumbnail/thumbnail.h
index b2be2239..307fc7cc 100644
--- a/lib/Thumbnail/thumbnail.h
+++ b/lib/Thumbnail/thumbnail.h
@@ -30,34 +30,49 @@ PREREQUISITES:
/*--------------
|declarations|
--------------*/
-typedef void* HICON;
+struct ImageData //consider alignment!
+{
+ unsigned char* rgb; //rgb-byte order for use with wxImage
+ unsigned char* alpha;
+ int width;
+ int height;
+};
DLL_FUNCTION_DECLARATION
-HICON getThumbnail(const wchar_t* filename, int requestedSize); //return 0 on failure, caller takes ownership!
-//Note: not all file types support thumbnails! make sure to implement fallback to file icon!
+const ImageData* getThumbnail(const wchar_t* filename, int requestedSize); //return nullptr on failure, release after use
+//Note: not all file types support thumbnails! implement fallback to file icon!
+enum IconSizeType
+{
+ ICON_SIZE_16,
+ ICON_SIZE_32,
+ ICON_SIZE_48,
+ ICON_SIZE_128,
+ ICON_SIZE_256,
+};
+//"iconIndex" as returned by ::SHGetFileInfo()
DLL_FUNCTION_DECLARATION
-HICON getIconByIndex(int iconIndex, int shilIconType); //return 0 on failure, caller takes ownership!
-/*
-"iconType" refers to parameter "iImageList" of ::SHGetImageList(); sample values:
- SHIL_SMALL - 16x16, but the size can be customized by the user.
- SHIL_EXTRALARGE - 48x48, but the size can be customized by the user.
- SHIL_JUMBO - 256x256 pixels; Vista and later only
-"iconIndex" as returned by ::SHGetFileInfo()
-*/
+const ImageData* getIconByIndex(int iconIndex, IconSizeType st); //return nullptr on failure, release after use
+
+DLL_FUNCTION_DECLARATION
+void releaseImageData(const ImageData* id);
+
/*----------
|typedefs|
----------*/
-typedef HICON (*FunType_getThumbnail )(const wchar_t* filename, int requestedSize);
-typedef HICON (*FunType_getIconByIndex)(int iconIndex, int shilIconType);
+typedef const ImageData* (*FunType_getThumbnail )(const wchar_t* filename, int requestedSize);
+typedef const ImageData* (*FunType_getIconByIndex )(int iconIndex, IconSizeType st);
+typedef void (*FunType_releaseImageData)(const ImageData* id);
+
/*--------------
|symbol names|
--------------*/
//(use const pointers to ensure internal linkage)
-const char funName_getThumbnail [] = "getThumbnail";
-const char funName_getIconByIndex[] = "getIconByIndex";
+const char funName_getThumbnail [] = "getThumbnail";
+const char funName_getIconByIndex [] = "getIconByIndex";
+const char funName_releaseImageData[] = "releaseImageData";
/*---------------
|library names|
bgstack15