end2endzone / ShellAnything

ShellAnything is a C++ open-source software which allow one to easily customize and add new options to *Windows Explorer* context menu. Define specific actions when a user right-click on a file or a directory.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Menu entries with icons that uses an icon file (.ico) does not work when scale is greater than 100%.

hboyd2003 opened this issue · comments

Describe the bug
When Windows scale is greater than 100%, entry icons that use an icon file (.ico) does not work.

To Reproduce
Steps to reproduce the behavior:

  1. Add a menu entry that uses an icon file as its icon.
  2. View the menu entry that uses an icon file as its icon.
  3. See the icon.
  4. Set Windows scale >100%.
  5. Restart Windows Explorer.
  6. View the menu entry that uses an icon file as its icon.
  7. See no icon.

Expected behavior
An icon should show next to the menu entry.

Screenshots
175% Scale
Screenshot_20230126_040126
100% Scale
Screenshot_20230126_040309

Environment

  • OS: Windows 11 22H2 (But I am using the old context menu)
  • Version 1.7.0 & 1.8.0

Additional Context
I have tried multiple different icon files.

Thank you for reporting this. It is actually something that I have never observed myself.
If I understand correctly, based on your "Using shell32.dll", if you pick an icon from a dll, it seems to be working fine. I don't know if this is related to this specific icon file or format. Have you tried embedding the icon in a dll and load it from there ? I realize this might be hard to do but it is possible.

Have you looked at the logs ? They are available in C:\Users\%USERNAME%\ShellAnything\Logs. You might find a clue why this is occurring.

I will try to quickly reproduce this myself with another icon. Do you know if this is specific to your icon file ? Have you tried with a "plain 16x16, single resolution, 8 bit per pixel" ?

It's been a while since I worked on the project. I still need to reintegrate other stuff so it might take a while before I am able to resolve the issue. Hopefully, I will quickly find a solution.

I did some more testing and the issue lies solely with .ico files that are 32-bit Multiple sizes with 256x256 icon.
If you want to test it yourself here is a zip file with the XML config and .ico files.

Looking at the code it looks like it might have to do with how it grabs the highest resolution .ico. To my understanding 256x256 was added later so not everything supports it. I might see if I can fix it my self but only just started my intro to C++ class.

Feel free to look at the code. I would gladly welcome any contributions and fixes. I have never tested the feature with icons that were larger than 32x32 since the intended display resolution (when at 100% scale) is 16x16.

On a side note, you may want to remove the log file from your previous comment as they can contain sensitive information about your system. I don't recall an exact example but the way I designed the logs was for users to to do self-investigations. In other words, I did not put much care about verifying if something would contains sensitive information. I also downloaded a local copy.

Hi, just want to add that I'm on 200% scale (4K tv) and no .ico shows in the app, no matter the size of the icon or if has other sizes inside. (tried with 16x16 too)
If I use a DLL (like the default example) it works.

2023-01-28 16-06-28

I might be wrong by this seems to be related to scaling and not specifically about the icon format. My system is configured at 1080p 100% scaling for the moment and I am busy completing other features and fixes.

If anyone is willing to help, the code that loads the icons is located in file shellext.cpp#L312. As a quick guess, I would assume that ExtractIconW() is working properly but the next calls might not handle scaling properly. More specifycally Win32Utils::GetBestIconForMenu() or Win32Utils::CopyAsBitmap() :

UINT numIconInFile = ExtractIconExW(icon_filename_wide.c_str(), -1, NULL, NULL, 1);
UINT numIconLoaded = ExtractIconExW(icon_filename_wide.c_str(), icon_index, &hIconLarge, &hIconSmall, 1);
if (numIconLoaded >= 1)
{
  //Find the best icon
  HICON hIcon = Win32Utils::GetBestIconForMenu(hIconLarge, hIconSmall);

  //Convert the icon to a bitmap (with invisible background)
  hBitmap = Win32Utils::CopyAsBitmap(hIcon);

I understand this might be annoying and I consider this high priority. As soon as I am done with other priorities, I will take a look at this.

Update:
I tried loading the icons provided by @hboyd2003. I am on Windows 10, 1080p resolution, scaled at 175%. The issue does not seems to be reproducible:
image
However, I see that some icons are blurry and some have more contrast. I think the function Win32Utils::GetBestIconForMenu() is prioritizing icons that matches the systems "small icon size". On Windows 7 and older, it was 16x16. That might have changed now that scaling exists.

I will try again later on another computer (Windows 11, 4K TV, scaled at 150%). Will see if I can reproduce. Once I am able to reproduce, I will be able to add traces (logs) in the code to see why this is occurring.

Note to self:

  • Add logging after the call to UINT numIconInFile = ExtractIconExW() to log warnings when "no icons found in file xyz".
  • Add logs for system metric values such as GetSystemMetrics(SM_CXSMICON).

I made more testing. I've tested with more icon formats, resolutions and scaling. I also compared v0.7 with the latest unofficial build on 2023-06-09.

I made screenshots for each tests:

Here is what I have found:

  • On 100% scale, all icons provided by @hboyd2003 are working. This includes v0.7 and latest build.
  • On 125% scale or more, version 0.7 struggles with icon file that contains a 256x256 and other resolutions icons.
  • On 100% scale with the latest build, the issue is not reproducible. All icons no matter their resolution are displayed properly. This is also what I described in my latest post.
  • On 125% scale or more, with the latest build, icon file that contains a 256x256 and other resolutions icons are displayed but with the incorrect size. The icons are bigger than they should be. They partially hide the menu title.

Regarding the observations made by @hijoput4, you may double check the path to your icons. Version 0.7 should support all resolutions as long as they are single-icon. Also, make sure your file is encoded in utf-8. This is mandatory if you have non English characters in your file path. If all above fails, you should have a look at the latest build. I have added more verbose logging regarding icons manipulation. You may find a clue on what is happening on your side.

The function ExtractIconEx() used for reading an icon from a *.ico file may not be appropriate for jumbo icons (256x256). I will try to change the implementation and rely on another function for reading icons. This will also simplify the algorithm that select the "best resolution icon" for a particular scaling.

For example:

I think I have nailed down the issue. Man. This one was way more complicated that it looks. The Windows function ExtractIconEx() does not behave the same if your monitor is scaled or not. I have created 2 new tests that shows this behavior: TestWin32Utils.testExtractLargeAndSmallIconsIssue117 and TestWin32Utils.testExtractSmallIconIssue117.

Here are the test execution logs:
For 1080p scaled at 100%:

[ RUN      ] TestWin32Utils.testExtractLargeAndSmallIconsIssue117
icon_path=test-res-16-20-24-32-40-48-60-64-72-80-96-120-128-144-160-192-256.ico  -->   large:32x32 small:16x16
icon_path=test-res-16-32-48-64-96-128-256.ico  -->   large:32x32 small:16x16
icon_path=alarm_clock-256-32bpp-8ba-compressed.ico  -->   large:32x32 small:16x16
icon_path=alarm_clock-16-32bpp-8ba-uncompressed.ico  -->   large:32x32 small:16x16
icon_path=alarm_clock-20-32bpp-8ba-uncompressed.ico  -->   large:32x32 small:16x16
icon_path=alarm_clock-24-32bpp-8ba-uncompressed.ico  -->   large:32x32 small:16x16
icon_path=alarm_clock-32-32bpp-8ba-uncompressed.ico  -->   large:32x32 small:16x16
icon_path=alarm_clock-40-32bpp-8ba-uncompressed.ico  -->   large:32x32 small:16x16
icon_path=alarm_clock-48-32bpp-8ba-uncompressed.ico  -->   large:32x32 small:16x16
icon_path=alarm_clock-64-32bpp-8ba-uncompressed.ico  -->   large:32x32 small:16x16
icon_path=alarm_clock-96-32bpp-8ba-uncompressed.ico  -->   large:32x32 small:16x16
icon_path=alarm_clock-128-32bpp-8ba-uncompressed.ico  -->   large:32x32 small:16x16
icon_path=alarm_clock-all-compressed.ico  -->   large:32x32 small:16x16
[       OK ] TestWin32Utils.testExtractLargeAndSmallIconsIssue117 (720 ms)

For a 4K monitor scaled at 300%:

[ RUN      ] TestWin32Utils.testExtractLargeAndSmallIconsIssue117
icon_path=test-res-16-20-24-32-40-48-60-64-72-80-96-120-128-144-160-192-256.ico  -->   large:96x96
icon_path=test-res-16-32-48-64-96-128-256.ico  -->   large:96x96
icon_path=alarm_clock-256-32bpp-8ba-compressed.ico  -->   large:96x96 small:48x48
icon_path=alarm_clock-16-32bpp-8ba-uncompressed.ico  -->   large:96x96 small:48x48
icon_path=alarm_clock-20-32bpp-8ba-uncompressed.ico  -->   large:96x96 small:48x48
icon_path=alarm_clock-24-32bpp-8ba-uncompressed.ico  -->   large:96x96 small:48x48
icon_path=alarm_clock-32-32bpp-8ba-uncompressed.ico  -->   large:96x96 small:48x48
icon_path=alarm_clock-40-32bpp-8ba-uncompressed.ico  -->   large:96x96 small:48x48
icon_path=alarm_clock-48-32bpp-8ba-uncompressed.ico  -->   large:96x96 small:48x48
icon_path=alarm_clock-64-32bpp-8ba-uncompressed.ico  -->   large:96x96 small:48x48
icon_path=alarm_clock-96-32bpp-8ba-uncompressed.ico  -->   large:96x96 small:48x48
icon_path=alarm_clock-128-32bpp-8ba-uncompressed.ico  -->   large:96x96 small:48x48
icon_path=alarm_clock-all-compressed.ico  -->   large:96x96
[       OK ] TestWin32Utils.testExtractLargeAndSmallIconsIssue117 (418 ms)

Basically, the code in ShellExtension wants to extract a "small" and a "large" resolution icons from the same file. It then "picks the best one" out of the two. If the file contains a single resolution icon, then the first is extracted and a second is generated. Notice how the behavior for ExtractIconEx() is different for icon test-res-16-20-24-32-40-48-60-64-72-80-96-120-128-144-160-192-256.ico. At 1080p scaled at 100% both large (32x32) and small (16x16) icons are found. However, when scaled at more than 100%, only a large icon is extracted. On a 4K monitor scaled at 300%, only a large (96x96) is found. This weird behavior is not documented anywhere.

The reasons it is working "better" in the latest builds than v0.7 is because I added these lines a month ago. They are not required if you are scaled at 100% but I added them recently for paranoia. Turns out they are useful when scaled >100% !

The solution was to only query for a single "small" icon. If the file only have a 256x256 (jumbo) icon, it will be automatically resized down to 16x16 (or a multiple of that if your monitor is scaled). It has been tested in TestWin32Utils.testExtractSmallIconIssue117. I should have a working solution tomorrow or in a few days.