[prev in list] [next in list] [prev in thread] [next in thread] 

List:       wine-devel
Subject:    Re: [PATCH v7 3/3] shell32: Partially implement IShellItemImageFactory (icon only, no thumbnail).
From:       Jinoh Kang <jinoh.kang.kr () gmail ! com>
Date:       2022-04-29 17:38:55
Message-ID: ea040d2c-7821-17e5-1d68-9ddaf5023530 () gmail ! com
[Download RAW message or body]

On 4/29/22 23:44, Nikolay Sivov wrote:
> I think this needs a lot of cleanup.
> 
> On 4/26/22 21:17, Jinoh Kang wrote:
> > 
> > 
> > +
> > typedef struct _ShellItem {
> > IShellItem2             IShellItem2_iface;
> > LONG                    ref;
> > @@ -565,17 +568,205 @@ 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)
> 
> You don't really need two icons, for SIIGBF_BIGGERSIZEOK.

SIIGBF_BIGGERSIZEOK does not appear to signify that the size parameter can be \
entirely disregarded.  Windows still takes the size argument as a hint. Maybe it \
needs more testing, but the impression I got is that it would choose the image that \
will experience the *least* distortion should it be eventually resized to the given \
size. It would then actually perform the resize if SIIGBF_RESIZETOFIT is specified; \
otherwise, the application is supposed to do the resize as needed. Although returning \
an image of arbitrary size isn't *contractually wrong* per se, it may choose a worse \
candidate for shrinking: for example, icons tend to have the same text size in both \
versions; thus, resizing a bigger icon would render the text unreadable. Also, it \
doesn't match Windows behavior anyway.

> > +{
> > +    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;
> 
> You probably can get rid of gil_in_flags and pbc.

For gil_in_flags: ACK. I just wanted to clarify the purpose of the parameter; maybe \
it wasn't a good idea to unnecessarily make a constant-as-of-now a variable after \
all.

For pbc: While the IBindCtx is currently ignored, wouldn't it make sense to pass \
something valid as required by the BindToHandler's interface definition anyway?

> 
> > +
> > +    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;
> > +        }
> 
> Instead of this you can probably do SHGetFileInfo(SHGFI_SYSICONINDEX), and then \
> pick appropriate imagelist.

That would preclude GIL_NOTFILENAME in Wine's current implementation; maybe it \
doesn't matter that much anyway...

> > +    }
> > +    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;
> > +}
> 
> This looks too complicated. System imaglists come in few sizes.

Not all icons belong to the system imagelist, are they?

> Desired size is know on GetImage(), I think it make sense to look for exact match \
> first, and load that.

I don't see how that would simplify the algorithm. An exact match may not always \
exist, but only subpar candidates.

> For non-file based case Extract() has size argument on its own, should that do \
> something to pick "the best" size?

My intention was not to rely on Extract() to actually do what we want, even as we \
provide the hint.

> 
> > +
> > +static HRESULT ShellItem_get_icon_bitmap(ShellItem *This, IWICImagingFactory \
> > *imgfactory, +                                         SIZE size, SIIGBF flags, \
> > IWICBitmap **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 = IWICImagingFactory_CreateBitmapFromHICON(imgfactory, best_icon, \
> > bitmap); +    DeleteObject(best_icon);
> > +    return hr;
> > +}
> > +
> > +static HRESULT convert_wicbitmapsource_to_gdi(IWICImagingFactory *imgfactory,
> > +                                              IWICBitmapSource *bitmapsource, \
> > HBITMAP *gdibitmap) +{
> > +    BITMAPINFOHEADER bmi;
> > +    HRESULT hr;
> > +    UINT width, height;
> > +    IWICBitmapSource *newsrc;
> > +    HDC dc;
> > +    HBITMAP bm;
> > +    void *bits;
> > +
> > +    *gdibitmap = NULL;
> > +
> > +    hr = WICConvertBitmapSource(&GUID_WICPixelFormat32bppBGRA, bitmapsource, \
> > &newsrc); +    if (FAILED(hr)) goto done;
> > +
> > +    hr = IWICBitmapSource_GetSize(newsrc, &width, &height);
> > +    if (FAILED(hr)) goto free_newsrc;
> > +
> > +    dc = CreateCompatibleDC(NULL);
> > +    if (!dc)
> > +    {
> > +        hr = E_FAIL;
> > +        goto free_newsrc;
> > +    }
> > +
> > +    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);
> I don't think you need a device context for this.

ACK.  TIL that CreateDIBSection(NULL, ..., DIB_RGB_COLORS, ...) is legal.

> 
> > +    if (!bm)
> > +    {
> > +        WARN("Cannot create bitmap.\n");
> > +        hr = E_FAIL;
> > +        goto free_newsrc;
> > +    }
> > +
> > +    hr = IWICBitmapSource_CopyPixels(newsrc, NULL, width * 4, width * height * \
> > 4, bits); +    if (FAILED(hr))
> > +    {
> > +        DeleteObject(bm);
> > +        goto free_newsrc;
> > +    }
> > +
> > +    hr = S_OK;
> > +    *gdibitmap = bm;
> > +
> > +free_newsrc:
> > +    IWICBitmapSource_Release(newsrc);
> > +done:
> > +    return hr;
> > +}
> > +
> > static HRESULT WINAPI \
> > ShellItem_IShellItemImageFactory_GetImage(IShellItemImageFactory *iface, SIZE \
> > size, SIIGBF flags, HBITMAP *phbm) {
> > ShellItem *This = impl_from_IShellItemImageFactory(iface);
> > +    HRESULT hr;
> > +    IWICImagingFactory *imgfactory;
> > +    IWICBitmap *bitmap = NULL;
> > static int once;
> > if (!once++)
> > -        FIXME("%p ({%lu, %lu} %d %p): stub\n", This, size.cx, size.cy, flags, \
> > phbm); +        FIXME("%p ({%lu, %lu} %d %p): partial stub\n", This, size.cx, \
> >                 size.cy, flags, phbm);
> > *phbm = NULL;
> > -    return E_NOTIMPL;
> > +
> > +    if (flags != SIIGBF_BIGGERSIZEOK) return E_NOTIMPL;
> > +
> > +    hr = WICCreateImagingFactory_Proxy(WINCODEC_SDK_VERSION, &imgfactory);
> > +    if (SUCCEEDED(hr))
> > +    {
> > +        hr = ShellItem_get_icon_bitmap(This, imgfactory, size, flags, &bitmap);
> > +        if (SUCCEEDED(hr))
> > +        {
> > +            hr = convert_wicbitmapsource_to_gdi(imgfactory, (IWICBitmapSource \
> > *)bitmap, phbm); +            IWICBitmap_Release(bitmap);
> > +        }
> > +        IWICImagingFactory_Release(imgfactory);
> > +    }
> > +
> > +    return hr;
> > }
> 


-- 
Sincerely,
Jinoh Kang


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic