diff options
Diffstat (limited to 'lib/Thumbnail')
-rw-r--r-- | lib/Thumbnail/Thumbnail.vcxproj | 12 | ||||
-rw-r--r-- | lib/Thumbnail/thumbnail.cpp | 514 | ||||
-rw-r--r-- | lib/Thumbnail/thumbnail.h | 45 |
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| |