Files
PowerToys/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Helpers/IconExtractor.cs
Jiří Polášek 18c6d6b0f3 CmdPal: Improve loading of application icons (uwp and jumbo icons) - part 2 (#44973)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

This PR improves icons for app items:
- Refactors icon detection and selection from the AppX manifest out of
`UWPApplication`
- Prefer *unplated* UWP app logos so icons no longer appear smaller than
expected
- Adds an icon loader based on `IShellItemImageFactory` to correctly
load large icons
- Jumbo icons loaded from shortcuts are now crisp
- Jumbo icons loaded from shortcuts are no longer scaled down
- Refactors detail loading in `AppListItem` to prevent potential
deadlocks
- Makes PWA icons more crisp
- Fixes fallback item (now it gets used not only when the icon is null,
but also when it's empty).

<table>

<thead>
<tr>
<th></th>
<th>Old</th>
<th>New</th>
</tr>
</thead>

<tr>
<td>1</td>
<td>
<img width="830" height="495" alt="image"
src="https://github.com/user-attachments/assets/bc9875bd-6a8b-4a3d-88e1-07a655a5a5cd"
/>
</td>
<td>
<img width="750" height="533" alt="image"
src="https://github.com/user-attachments/assets/a82ed464-b925-4d0c-95c4-6c04859e886e"
/>
</td>
</tr>

<tr>
<td>2</td>
<td>
<img width="814" height="233" alt="image"
src="https://github.com/user-attachments/assets/d560d3c0-ffc5-4178-a610-4e3b3c7107c8"
/>
</td>
<td>
<img width="760" height="299" alt="image"
src="https://github.com/user-attachments/assets/f29c825e-324f-46f1-b6bb-6edcf286fc9a"
/>

</td>
</tr>


<tr>
<td>3</td>
<td>
<img width="813" height="262" alt="image"
src="https://github.com/user-attachments/assets/d94f724d-ec26-48c8-bb8a-1b10f6a0f7eb"
/>
</td>
<td>
<img width="762" height="260" alt="image"
src="https://github.com/user-attachments/assets/76c5debb-baac-417e-8aba-9cec198e742c"
/>
</td>
</tr>

<tr>
<td>4</td>
<td>
<img width="819" height="250" alt="image"
src="https://github.com/user-attachments/assets/5f16d714-56d8-42f2-ad8b-1c2be6570e5c"
/>
</td>
<td>
<img width="747" height="244" alt="image"
src="https://github.com/user-attachments/assets/485c72cf-ef39-4c05-afdd-877f0a47f51a"
/>
</td>
</tr>


<tr>
<td>5</td>
<td>
<img width="815" height="327" alt="image"
src="https://github.com/user-attachments/assets/4108e36a-5950-43c9-bdff-6a9f58dadcf6"
/>
</td>
<td>
<img width="762" height="272" alt="image"
src="https://github.com/user-attachments/assets/804a3159-a165-4a48-87f6-15849f5f4516"
/>
</td>
</tr>

<tr>
<td>6</td>
<td>
<img width="809" height="257" alt="image"
src="https://github.com/user-attachments/assets/93ad8241-1d75-415f-b08c-4161c0905e41"
/>
</td>
<td>
<img width="756" height="231" alt="image"
src="https://github.com/user-attachments/assets/a0c9bb44-7151-438d-a811-82d5e2080f44"
/>
</td>
</tr>

<tr>
<td></td>
<td>
</td>
<td>
</td>
</tr>

</table>

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #44970
- [x] Closes: #43320
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-02-02 11:53:40 -06:00

133 lines
3.6 KiB
C#

// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using ManagedCommon;
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.Shell;
namespace Microsoft.CmdPal.Ext.Apps.Helpers;
internal static class IconExtractor
{
public static async Task<IRandomAccessStream?> GetIconStreamAsync(string path, int size)
{
var bitmap = GetIcon(path, size);
if (bitmap == null)
{
return null;
}
var stream = new InMemoryRandomAccessStream();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetSoftwareBitmap(bitmap);
await encoder.FlushAsync();
stream.Seek(0);
return stream;
}
public static unsafe SoftwareBitmap? GetIcon(string path, int size)
{
IShellItemImageFactory* factory = null;
HBITMAP hBitmap = default;
try
{
fixed (char* pPath = path)
{
var iid = IShellItemImageFactory.IID_Guid;
var hr = PInvoke.SHCreateItemFromParsingName(
pPath,
null,
&iid,
(void**)&factory);
if (hr.Failed || factory == null)
{
return null;
}
}
var requestedSize = new SIZE { cx = size, cy = size };
var hr2 = factory->GetImage(
requestedSize,
SIIGBF.SIIGBF_ICONONLY | SIIGBF.SIIGBF_BIGGERSIZEOK | SIIGBF.SIIGBF_CROPTOSQUARE,
&hBitmap);
if (hr2.Failed || hBitmap.IsNull)
{
return null;
}
return CreateSoftwareBitmap(hBitmap, size);
}
catch (Exception ex)
{
Logger.LogError($"Failed to load icon from path='{path}',size={size}", ex);
return null;
}
finally
{
if (!hBitmap.IsNull)
{
PInvoke.DeleteObject(hBitmap);
}
if (factory != null)
{
factory->Release();
}
}
}
private static unsafe SoftwareBitmap CreateSoftwareBitmap(HBITMAP hBitmap, int size)
{
var pixels = new byte[size * size * 4];
var bmi = new BITMAPINFO
{
bmiHeader = new BITMAPINFOHEADER
{
biSize = (uint)sizeof(BITMAPINFOHEADER),
biWidth = size,
biHeight = -size,
biPlanes = 1,
biBitCount = 32,
biCompression = 0,
},
};
var hdc = PInvoke.GetDC(default);
try
{
fixed (byte* pPixels = pixels)
{
_ = PInvoke.GetDIBits(
hdc,
hBitmap,
0,
(uint)size,
pPixels,
&bmi,
DIB_USAGE.DIB_RGB_COLORS);
}
}
finally
{
_ = PInvoke.ReleaseDC(default, hdc);
}
var bitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8, size, size, BitmapAlphaMode.Premultiplied);
bitmap.CopyFromBuffer(pixels.AsBuffer());
return bitmap;
}
}