using System; using System.Runtime.InteropServices; using Avalonia; using Avalonia.Media.Imaging; using Avalonia.Platform; namespace Notify.Interop; /// /// 从 exe 提取图标并转成 Avalonia 位图 /// /// ExtractIconEx 拿 HICON,再用 GDI 读出 BGRA 像素构造 Bitmap;不依赖 /// System.Drawing(其 AOT 不友好) /// internal static partial class AppIcon { public static Bitmap? Extract(string exePath) { if (string.IsNullOrEmpty(exePath)) { return null; } var hIcon = IntPtr.Zero; try { if (ExtractIconExW(exePath, 0, out hIcon, out _, 1) == 0 || hIcon == IntPtr.Zero) { return null; } return IconToBitmap(hIcon); } catch { return null; } finally { if (hIcon != IntPtr.Zero) { DestroyIcon(hIcon); } } } private static Bitmap? IconToBitmap(IntPtr hIcon) { if (!GetIconInfo(hIcon, out var ii)) { return null; } try { var bm = default(BITMAP); if (GetObjectW(ii.hbmColor, Marshal.SizeOf(), ref bm) == 0 || bm.bmWidth <= 0 || bm.bmHeight <= 0) { return null; } var w = bm.bmWidth; var h = bm.bmHeight; var buffer = new byte[w * h * 4]; var bmi = new BITMAPINFOHEADER { biSize = (uint)Marshal.SizeOf(), biWidth = w, biHeight = -h, // 负数 = 自上而下,行序正常 biPlanes = 1, biBitCount = 32, biCompression = 0, }; var hdc = GetDC(IntPtr.Zero); try { if (GetDIBits(hdc, ii.hbmColor, 0, (uint)h, buffer, ref bmi, 0) == 0) { return null; } } finally { ReleaseDC(IntPtr.Zero, hdc); } // 某些老图标无 alpha 通道(全 0),那样会整块透明,补成不透明 var anyAlpha = false; for (var i = 3; i < buffer.Length; i += 4) { if (buffer[i] != 0) { anyAlpha = true; break; } } if (!anyAlpha) { for (var i = 3; i < buffer.Length; i += 4) { buffer[i] = 255; } } var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); try { return new Bitmap( PixelFormat.Bgra8888, AlphaFormat.Unpremul, handle.AddrOfPinnedObject(), new PixelSize(w, h), new Vector(96, 96), w * 4); } finally { handle.Free(); } } finally { if (ii.hbmColor != IntPtr.Zero) { DeleteObject(ii.hbmColor); } if (ii.hbmMask != IntPtr.Zero) { DeleteObject(ii.hbmMask); } } } [LibraryImport("shell32.dll", EntryPoint = "ExtractIconExW", StringMarshalling = StringMarshalling.Utf16)] private static partial uint ExtractIconExW(string lpszFile, int nIconIndex, out IntPtr phiconLarge, out IntPtr phiconSmall, uint nIcons); [LibraryImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo); [LibraryImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool DestroyIcon(IntPtr hIcon); [LibraryImport("gdi32.dll", EntryPoint = "GetObjectW")] private static partial int GetObjectW(IntPtr hgdiobj, int cbBuffer, ref BITMAP lpvObject); [LibraryImport("gdi32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool DeleteObject(IntPtr hObject); [LibraryImport("gdi32.dll")] private static partial int GetDIBits(IntPtr hdc, IntPtr hbmp, uint uStartScan, uint cScanLines, [Out] byte[] lpvBits, ref BITMAPINFOHEADER lpbi, uint uUsage); [LibraryImport("user32.dll")] private static partial IntPtr GetDC(IntPtr hWnd); [LibraryImport("user32.dll")] private static partial int ReleaseDC(IntPtr hWnd, IntPtr hDC); } [StructLayout(LayoutKind.Sequential)] internal struct ICONINFO { public int fIcon; public uint xHotspot; public uint yHotspot; public IntPtr hbmMask; public IntPtr hbmColor; } [StructLayout(LayoutKind.Sequential)] internal struct BITMAP { public int bmType; public int bmWidth; public int bmHeight; public int bmWidthBytes; public ushort bmPlanes; public ushort bmBitsPixel; public IntPtr bmBits; } [StructLayout(LayoutKind.Sequential)] internal struct BITMAPINFOHEADER { public uint biSize; public int biWidth; public int biHeight; public ushort biPlanes; public ushort biBitCount; public uint biCompression; public uint biSizeImage; public int biXPelsPerMeter; public int biYPelsPerMeter; public uint biClrUsed; public uint biClrImportant; }