// ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #include "thumbnail.h" #include #include #define WIN32_LEAN_AND_MEAN #include #include #include #define STRICT_TYPED_ITEMIDS //better type safety for IDLists #include #include #include #include #include #include #include #include //#include using namespace zen; namespace { thumb::ImageData* allocImageData(int width, int height) //throw SysError; return value always bound! { ZEN_COM_ASSERT(width >= 0 && height >= 0); //throw SysError std::unique_ptr idata = make_unique(); idata->width = width; idata->height = height; idata->rgb = new unsigned char[width * height * 4]; idata->alpha = idata->rgb + width * height * 3; return idata.release(); } void releaseImageData_impl(const thumb::ImageData* id) { if (id) { delete [] id->rgb; delete id; } } //caller takes ownership! HICON createIconFromBitmap(HBITMAP bitmap) //throw SysError { 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 SysError(formatSystemError(L"CreateIconIndirect", getLastError())); return result; } //caller takes ownership! thumb::ImageData* convertToImageData(HBITMAP bmp) //throw SysError { //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(&bi), //_In_ const BITMAPINFO *pbmi, DIB_RGB_COLORS, //_In_ UINT iUsage, reinterpret_cast(&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 SysError(formatSystemError(L"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 SysError 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) { 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 SysError { const std::wstring filenameStr(filename); ComPtr desktopFolder; ZEN_COM_CHECK(::SHGetDesktopFolder(desktopFolder.init())); //throw SysError ZEN_COM_ASSERT(desktopFolder); //throw SysError -> better safe than sorry? PIDLIST_RELATIVE pidlFolder = nullptr; { std::wstring pathName = beforeLast(filenameStr, L'\\'); ZEN_COM_CHECK(desktopFolder->ParseDisplayName(nullptr, // [in] HWND hwnd, nullptr, // [in] IBindCtx *pbc, const_cast(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 imageFolder; 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; { std::wstring shortName = afterLast(filenameStr, L'\\'); ZEN_COM_CHECK(imageFolder->ParseDisplayName(nullptr, // [in] HWND hwnd, nullptr, // [in] IBindCtx *pbc, const_cast(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 extractImage; ZEN_COM_CHECK(imageFolder->GetUIObjectOf(nullptr, // [in] HWND hwndOwner, 1, // [in] UINT cidl, reinterpret_cast(&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(extractImage.init()))); // [out] void **ppv ZEN_COM_ASSERT(extractImage); { wchar_t pathBuffer[MAX_PATH]; DWORD priority = 0; const SIZE prgSize = { requestedSize, requestedSize }; DWORD clrDepth = 32; //"recommended color depth" DWORD flags = IEIFLAG_SCREEN | IEIFLAG_OFFLINE; 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 SysError, 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 SysError { //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. { 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; } } ComPtr 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); int srcWidth = 0; int srcHeight = 0; ZEN_COM_CHECK(imageList->GetIconSize(&srcWidth, &srcHeight)); int targetWidth = srcWidth; int targetHeight = srcHeight; bool needDownScale = false; //scale down if required (e.g Vista Jumbo icons/user-customized icon sizes) ZEN_COM_ASSERT(srcWidth > 0 && srcHeight > 0 && requestedSize > 0); const int maxExtent = std::max(srcWidth, srcHeight); if (requestedSize < maxExtent) { needDownScale = true; targetWidth = srcWidth * requestedSize / maxExtent; targetHeight = srcHeight * requestedSize / maxExtent; } 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; return ::CreateDIBSection(hScreenDC, //_In_ HDC hdc, &bi, //_In_ const BITMAPINFO *pbmi, DIB_RGB_COLORS, //_In_ UINT iUsage, reinterpret_cast(rawBits), //_Out_ VOID **ppvBits, nullptr, //_In_ HANDLE hSection, 0); //_In_ DWORD dwOffset }; 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) { drawParams.fStyle |= ILD_SCALE; drawParams.cx = targetWidth; drawParams.cy = targetHeight; } //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 SysError 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(numeric::confineCpy(tmp / 3, 0, 255)); auto calcColor = [&](unsigned char c_black, unsigned char c_white) { return static_cast(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 SysError } catch (const SysError&) { return nullptr; } } const thumb::ImageData* thumb::getIconByIndex(int iconIndex, thumb::IconSizeType st) //return 0 on failure, caller takes ownership! { try { return getIconByIndex_impl(iconIndex, st); //throw SysError } catch (const SysError&) { return nullptr; } } void thumb::releaseImageData(const thumb::ImageData* id) { releaseImageData_impl(id); }