From e42e76b1a48a1a523f8405c85a2baf9df9e4dabb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gabriel=20Iv=C4=83ncescu?= <gabrielopcode@gmail.com>
Date: Fri, 30 Nov 2018 12:20:40 +0200
Subject: [PATCH] shell32/iconcache: Generate icons from available icons if
 some icon sizes failed to load
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

For icon sizes that fail to load, create them from another icon that
succeeded by resizing it, favoring icons that are the closest and larger
(to reduce pixelation artefacts) and with the closest aspect ratio as the
source of this operation (to be as generic as possible). For example, if
the icon that needs to be created must be 16x16, an 18x18 icon would get
picked over either a 32x32 (it's further from 16x16) or a 15x15 (icons
larger than 16x16 are favored since they're larger than the result, so
smaller icons are only picked if no other available icon is larger).

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=45696
Signed-off-by: Gabriel Ivăncescu <gabrielopcode@gmail.com>
---
 dlls/shell32/iconcache.c | 67 +++++++++++++++++++++++++++++++++-------
 1 file changed, 56 insertions(+), 11 deletions(-)

diff --git a/dlls/shell32/iconcache.c b/dlls/shell32/iconcache.c
index 44422ab..5ffb293 100644
--- a/dlls/shell32/iconcache.c
+++ b/dlls/shell32/iconcache.c
@@ -345,13 +345,6 @@ static INT SIC_IconAppend (const WCHAR *sourcefile, INT src_index, HICON *hicons
     return ret;
 }
 
-static BOOL get_imagelist_icon_size(int list, SIZE *size)
-{
-    if (list < 0 || list >= ARRAY_SIZE(shell_imagelists)) return FALSE;
-
-    return ImageList_GetIconSize( shell_imagelists[list], &size->cx, &size->cy );
-}
-
 /****************************************************************************
  * SIC_LoadIcon				[internal]
  *
@@ -362,15 +355,67 @@ static INT SIC_LoadIcon (const WCHAR *sourcefile, INT index, DWORD flags)
 {
     HICON hicons[ARRAY_SIZE(shell_imagelists)] = { 0 };
     HICON hshortcuts[ARRAY_SIZE(hicons)] = { 0 };
+    SIZE size[ARRAY_SIZE(shell_imagelists)];
     unsigned int i;
-    SIZE size;
     INT ret = -1;
 
+    /* Keep track of the sizes in case any icon fails to get extracted */
+    for (i = 0; i < ARRAY_SIZE(hicons); i++)
+    {
+        ImageList_GetIconSize(shell_imagelists[i], &size[i].cx, &size[i].cy);
+        PrivateExtractIconsW(sourcefile, index, size[i].cx, size[i].cy, &hicons[i], 0, 1, 0);
+    }
+
+    /* Fill any icon handles that failed to get extracted, by resizing
+       another icon handle that succeeded and creating the icon from it.
+       Use a dumb O(n^2) algorithm since ARRAY_SIZE(hicons) is small */
     for (i = 0; i < ARRAY_SIZE(hicons); i++)
     {
-        get_imagelist_icon_size( i, &size );
-        if (!PrivateExtractIconsW( sourcefile, index, size.cx, size.cy, &hicons[i], 0, 1, 0 ))
-            WARN("Failed to load icon %d from %s.\n", index, debugstr_w(sourcefile));
+        unsigned int k, ix, iy;
+        BOOL failed = TRUE;
+        if (hicons[i]) continue;
+
+        for (k = 0; k < ARRAY_SIZE(hicons); k++)
+        {
+            if (hicons[k])
+            {
+                ix = iy = k;
+                failed = FALSE;
+                break;
+            }
+        }
+        if (failed) goto fail;
+
+        for (k++; k < ARRAY_SIZE(hicons); k++)
+        {
+            if (!hicons[k]) continue;
+
+            /* Find closest-sized icon, but favor larger icons to resize from */
+            if (size[k].cx >= size[i].cx)
+                ix = (size[ix].cx < size[i].cx || size[ix].cx > size[k].cx) ? k : ix;
+            else
+                ix = (size[ix].cx < size[i].cx && size[ix].cx < size[k].cx) ? k : ix;
+
+            if (size[k].cy >= size[i].cy)
+                iy = (size[iy].cy < size[i].cy || size[iy].cy > size[k].cy) ? k : iy;
+            else
+                iy = (size[iy].cy < size[i].cy && size[iy].cy < size[k].cy) ? k : iy;
+        }
+
+        /* Use the closest icon in aspect ratio if ix and iy differ */
+        if (ix != iy)
+        {
+            float i_ratio, ix_ratio, iy_ratio;
+            i_ratio  = (float)size[i].cx  / (float)size[i].cy;
+            ix_ratio = (float)size[ix].cx / (float)size[ix].cy;
+            iy_ratio = (float)size[iy].cx / (float)size[iy].cy;
+            if (fabsf(ix_ratio - i_ratio) > fabsf(iy_ratio - i_ratio))
+                ix = iy;
+        }
+
+        /* If this fails, we have to abort to prevent the image lists from
+           becoming out of sync and completely screwing the icons up */
+        hicons[i] = CopyImage(hicons[ix], IMAGE_ICON, size[i].cx, size[i].cy, 0);
         if (!hicons[i]) goto fail;
     }
 
-- 
2.19.1

