Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=52673 Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com ---
Notes: v1 -> v2: no changes
v3 -> v4: - Remove patch "shell32: Factor out ShellItem_get_uiobject." - Use IShellItem2_BindToHandler instead of factoring out ShellItem_get_uiobject - Use CreateCompatibleDC(NULL) instead of GetDC(NULL) - Remove new dependency on win32u, use GetIconInfo instead - Remove new dependency on windowcodecs, use gdiplus instead
dlls/shell32/shellitem.c | 209 ++++++++++++++++++++++++++++++++- dlls/shell32/tests/shlfolder.c | 2 +- 2 files changed, 207 insertions(+), 4 deletions(-)
diff --git a/dlls/shell32/shellitem.c b/dlls/shell32/shellitem.c index 1a0bed55c1e..47a1eeb5701 100644 --- a/dlls/shell32/shellitem.c +++ b/dlls/shell32/shellitem.c @@ -31,6 +31,7 @@ #include "pidl.h" #include "shell32_main.h" #include "debughlp.h" +#include "gdiplus.h"
WINE_DEFAULT_DEBUG_CHANNEL(shell);
@@ -565,17 +566,219 @@ static ULONG WINAPI ShellItem_IShellItemImageFactory_Release(IShellItemImageFact return IShellItem2_Release(&This->IShellItem2_iface); }
+static HRESULT ShellItem_get_icons(ShellItem *This, SIZE size, HICON *big_icon, HICON *small_icon) +{ + HRESULT hr; + IBindCtx *pbc; + IExtractIconW *ei; + WCHAR icon_file[MAX_PATH]; + INT source_index; + UINT gil_in_flags = 0, gil_out_flags; + INT iconsize; + + iconsize = min(size.cx, size.cy); + if (iconsize <= 0 || iconsize > 0x7fff) + iconsize = 0x7fff; + + hr = CreateBindCtx(0, &pbc); + if (FAILED(hr)) goto done; + + hr = IShellItem2_BindToHandler(&This->IShellItem2_iface, pbc, &BHID_SFUIObject, + &IID_IExtractIconW, (void **)&ei); + IBindCtx_Release(pbc); + if (FAILED(hr)) goto done; + + hr = IExtractIconW_GetIconLocation(ei, gil_in_flags, icon_file, MAX_PATH, &source_index, &gil_out_flags); + if (FAILED(hr)) goto free_ei; + + if (!(gil_out_flags & GIL_NOTFILENAME)) + { + UINT ei_res; + + if (source_index == -1) + source_index = 0; /* special case for some control panel applications */ + + FIXME("%s %d\n", debugstr_w(icon_file), source_index); + ei_res = ExtractIconExW(icon_file, source_index, big_icon, small_icon, 1); + if (!ei_res || ei_res == (UINT)-1) + { + WARN("Failed to extract icon.\n"); + hr = E_FAIL; + } + } + else + { + hr = IExtractIconW_Extract(ei, icon_file, source_index, big_icon, small_icon, MAKELONG(iconsize, iconsize)); + } + +free_ei: + IExtractIconW_Release(ei); +done: + return hr; +} + +static HICON choose_best_icon(HICON *icons, UINT count, SIZE size_limit, SIZE *out_size) +{ + HICON best_icon = NULL; + SIZE best_size = {0, 0}; + UINT i; + + for (i = 0; i < count; i++) + { + ICONINFO iinfo; + BITMAP bm; + SIZE size; + BOOL is_color, ret; + + if (!icons[i] || !GetIconInfo(icons[i], &iinfo)) continue; + + is_color = iinfo.hbmColor != NULL; + ret = GetObjectW(is_color ? iinfo.hbmColor : iinfo.hbmMask, sizeof(bm), &bm); + DeleteObject(iinfo.hbmColor); + DeleteObject(iinfo.hbmMask); + if (!ret) continue; + + size.cx = bm.bmWidth; + size.cy = is_color ? abs(bm.bmHeight) : abs(bm.bmHeight) / 2; + + if (!best_icon || (best_size.cx < size.cx && size.cx <= size_limit.cx && + best_size.cy < size.cy && size.cy <= size_limit.cy)) + { + best_icon = icons[i]; + best_size = size; + } + } + + *out_size = best_size; + return best_icon; +} + +static HRESULT gpstatus_to_hresult(GpStatus status) +{ + switch (status) + { + case Ok: + return S_OK; + case InvalidParameter: + return E_INVALIDARG; + case OutOfMemory: + return E_OUTOFMEMORY; + case NotImplemented: + return E_NOTIMPL; + default: + return E_FAIL; + } +} + +static HRESULT ShellItem_get_icon_bitmap(ShellItem *This, SIZE size, SIIGBF flags, GpBitmap **bitmap) +{ + HRESULT hr; + HICON icons[2] = { NULL, NULL }, best_icon; + SIZE best_icon_size; + UINT i; + + *bitmap = NULL; + + hr = ShellItem_get_icons(This, size, &icons[0], &icons[1]); + if (FAILED(hr)) return hr; + + best_icon = choose_best_icon(icons, ARRAY_SIZE(icons), size, &best_icon_size); + for (i = 0; i < ARRAY_SIZE(icons); i++) + if (icons[i] && icons[i] != best_icon) DeleteObject(icons[i]); + + if (!best_icon) return E_FAIL; + + hr = gpstatus_to_hresult(GdipCreateBitmapFromHICON(best_icon, bitmap)); + DeleteObject(best_icon); + return hr; +} + +/************************************************************************* + * create_argb_hbitmap_from_gpbitmap + * + * This is similar to GdipCreateHBITMAPFromBitmap, except that it uses + * PixelFormat32bppARGB instead of PixelFormat32bppPARGB and lacks the + * background parameter. + */ +static HRESULT create_argb_hbitmap_from_gpbitmap(GpBitmap *bitmap, HBITMAP *gdibitmap) +{ + BITMAPINFOHEADER bmi; + HRESULT hr; + UINT width, height; + BitmapData bitmapdata; + HDC dc; + HBITMAP bm; + void *bits; + + *gdibitmap = NULL; + + hr = GdipGetImageWidth((GpImage *)bitmap, &width); + if (FAILED(hr)) return hr; + + hr = GdipGetImageHeight((GpImage *)bitmap, &height); + if (FAILED(hr)) return hr; + + dc = CreateCompatibleDC(NULL); + if (!dc) return E_FAIL; + + memset(&bmi, 0, sizeof(bmi)); + bmi.biSize = sizeof(bmi); + bmi.biWidth = width; + bmi.biHeight = -height; + bmi.biPlanes = 1; + bmi.biBitCount = 32; + bmi.biCompression = BI_RGB; + + bm = CreateDIBSection(dc, (const BITMAPINFO *)&bmi, DIB_RGB_COLORS, &bits, NULL, 0); + DeleteDC(dc); + if (!bm) + { + WARN("Cannot create bitmap.\n"); + return E_FAIL; + } + + memset(&bitmapdata, 0, sizeof(bitmapdata)); + bitmapdata.Width = width; + bitmapdata.Height = height; + bitmapdata.Stride = width * 4; + bitmapdata.PixelFormat = PixelFormat32bppARGB; + bitmapdata.Scan0 = bits; + hr = gpstatus_to_hresult(GdipBitmapLockBits(bitmap, NULL, + ImageLockModeRead | ImageLockModeUserInputBuf, + bitmapdata.PixelFormat, &bitmapdata)); + if (FAILED(hr)) + { + DeleteObject(bm); + return hr; + } + + GdipBitmapUnlockBits(bitmap, &bitmapdata); + + *gdibitmap = bm; + return S_OK; +} + static HRESULT WINAPI ShellItem_IShellItemImageFactory_GetImage(IShellItemImageFactory *iface, SIZE size, SIIGBF flags, HBITMAP *phbm) { ShellItem *This = impl_from_IShellItemImageFactory(iface); + HRESULT hr; + GpBitmap *bitmap = NULL; static int once;
if (!once++) - FIXME("%p ({%lu, %lu} %lu %p): stub\n", This, size.cx, size.cy, flags, phbm); + FIXME("%p ({%lu, %lu} %lu %p): partial stub\n", This, size.cx, size.cy, flags, phbm);
- *phbm = NULL; - return E_NOTIMPL; + if (flags != SIIGBF_BIGGERSIZEOK) return E_NOTIMPL; + + hr = ShellItem_get_icon_bitmap(This, size, flags, &bitmap); + if (SUCCEEDED(hr)) + { + hr = create_argb_hbitmap_from_gpbitmap(bitmap, phbm); + GdipDisposeImage((GpImage *)bitmap); + } + + return hr; }
static const IShellItemImageFactoryVtbl ShellItem_IShellItemImageFactory_Vtbl = { diff --git a/dlls/shell32/tests/shlfolder.c b/dlls/shell32/tests/shlfolder.c index 900c484d0b9..a5f498f8c6d 100644 --- a/dlls/shell32/tests/shlfolder.c +++ b/dlls/shell32/tests/shlfolder.c @@ -4510,7 +4510,7 @@ static void test_IShellItemImageFactory(void) SIZE size = {32, 32};
ret = IShellItemImageFactory_GetImage(siif, size, SIIGBF_BIGGERSIZEOK, &hbm); - todo_wine ok(ret == S_OK || broken(ret == E_PENDING /* win7 */), "GetImage returned %lx\n", ret); + ok(ret == S_OK || broken(ret == E_PENDING /* win7 */), "GetImage returned %lx\n", ret); ok(FAILED(ret) == !hbm, "result = %lx but bitmap = %p\n", ret, hbm); if (SUCCEEDED(ret) && hbm) {