SystemTrayMenu/Utilities/File/IconReader.cs
Peter Kirmeier aa87100095 Reduce code complexity and refined member variables
Improve null pointer assumptions by relying on nullable value types
2023-04-29 18:57:39 +02:00

298 lines
10 KiB
C#

// <copyright file="IconReader.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
// see also: https://www.codeproject.com/Articles/2532/Obtaining-and-managing-file-and-folder-icons-using.
namespace SystemTrayMenu.Utilities
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using SystemTrayMenu.DllImports;
/// <summary>
/// Provides static methods to read system icons for folders and files.
/// </summary>
public static class IconReader
{
private static readonly ConcurrentDictionary<string, Icon?> IconDictPersistent = new();
private static readonly ConcurrentDictionary<string, Icon?> IconDictCache = new();
public enum IconSize
{
Large = 0, // 32x32 pixels
Small = 1, // 16x16 pixels
}
// see https://github.com/Hofknecht/SystemTrayMenu/issues/209.
public static bool IsPreloading { get; set; } = true;
public static void ClearCacheWhenLimitReached()
{
if (IconDictCache.Count > Properties.Settings.Default.ClearCacheIfMoreThanThisNumberOfItems)
{
foreach (Icon? icon in IconDictCache.Values)
{
icon?.Dispose();
}
IconDictCache.Clear();
GC.Collect();
}
}
public static void RemoveIconFromCache(string path)
{
if (IconDictPersistent.Remove(path, out Icon? iconToRemove))
{
iconToRemove?.Dispose();
}
}
public static Icon? GetFileIconWithCache(
string path,
string resolvedPath,
bool linkOverlay,
bool updateIconInBackground,
bool checkPersistentFirst,
out bool loading)
{
loading = false;
Icon? icon = null;
string key;
string extension = Path.GetExtension(path);
if (IsExtensionWithSameIcon(extension))
{
key = extension + linkOverlay;
}
else
{
key = path;
}
if (!DictIconCache(checkPersistentFirst).TryGetValue(key, out icon) &&
!DictIconCache(!checkPersistentFirst).TryGetValue(key, out icon))
{
icon = Resources.StaticResources.LoadingIcon;
loading = true;
if (updateIconInBackground)
{
IconSize size = IconSize.Small;
if (Scaling.Factor >= 1.25f ||
Scaling.FactorByDpi >= 1.25f ||
Properties.Settings.Default.IconSizeInPercent / 100f >= 1.25f)
{
size = IconSize.Large;
}
new Thread(UpdateIconInBackground).Start();
void UpdateIconInBackground()
{
DictIconCache(checkPersistentFirst).GetOrAdd(key, GetIconSTA(path, resolvedPath, linkOverlay, size, false));
}
}
}
return icon;
}
public static Icon? GetFolderIconWithCache(
string path,
bool linkOverlay,
bool updateIconInBackground,
bool checkPersistentFirst,
out bool loading)
{
loading = false;
Icon? icon = null;
string key = path;
if (!DictIconCache(checkPersistentFirst).TryGetValue(key, out icon) &&
!DictIconCache(!checkPersistentFirst).TryGetValue(key, out icon))
{
icon = Resources.StaticResources.LoadingIcon;
loading = true;
if (updateIconInBackground)
{
IconSize size = IconSize.Small;
if (Scaling.Factor >= 1.25f ||
Scaling.FactorByDpi >= 1.25f ||
Properties.Settings.Default.IconSizeInPercent / 100f >= 1.25f)
{
// IconSize.Large returns another folder icon than windows explorer
size = IconSize.Large;
}
if (IsPreloading)
{
DictIconCache(checkPersistentFirst).GetOrAdd(key, GetFolder);
}
else
{
new Thread(UpdateIconInBackground).Start();
void UpdateIconInBackground()
{
DictIconCache(checkPersistentFirst).GetOrAdd(key, GetFolder);
}
}
Icon? GetFolder(string keyExtension)
{
return GetIconSTA(path, path, linkOverlay, size, true);
}
}
}
return icon;
}
public static Icon? GetIconSTA(string path, string resolvedPath, bool linkOverlay, IconSize size, bool isFolder)
{
Icon? icon = null;
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
{
icon = GetIcon(path, resolvedPath, linkOverlay, size, isFolder);
}
else
{
Thread staThread = new(new ParameterizedThreadStart(StaThreadMethod));
void StaThreadMethod(object? obj)
{
icon = GetIcon(path, resolvedPath, linkOverlay, size, isFolder);
}
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start(icon);
staThread.Join();
}
return icon;
}
public static Icon? AddIconOverlay(Icon? originalIcon, Icon overlay)
{
Icon? icon = originalIcon;
if (originalIcon != null)
{
using Bitmap target = new(originalIcon.Width, originalIcon.Height, PixelFormat.Format32bppArgb);
using Graphics graphics = Graphics.FromImage(target);
graphics.DrawIcon(originalIcon, 0, 0);
graphics.DrawIcon(overlay, new(0, 0, originalIcon.Width + 2, originalIcon.Height + 2));
target.MakeTransparent(target.GetPixel(1, 1));
IntPtr hIcon = target.GetHicon();
icon = (Icon)Icon.FromHandle(hIcon).Clone();
NativeMethods.User32DestroyIcon(hIcon);
}
return icon;
}
private static ConcurrentDictionary<string, Icon?> DictIconCache(bool checkPersistentFirst)
=> checkPersistentFirst ? IconDictPersistent : IconDictCache;
private static bool IsExtensionWithSameIcon(string fileExtension)
{
bool isExtensionWithSameIcon = true;
List<string> extensionsWithDiffIcons = new() { string.Empty, ".EXE", ".LNK", ".ICO", ".URL" };
if (extensionsWithDiffIcons.Contains(fileExtension.ToUpperInvariant()))
{
isExtensionWithSameIcon = false;
}
return isExtensionWithSameIcon;
}
private static Icon? GetIcon(string path, string resolvedPath, bool linkOverlay, IconSize size, bool isFolder)
{
Icon? icon;
if (Path.GetExtension(path).Equals(".ico", StringComparison.InvariantCultureIgnoreCase))
{
icon = Icon.ExtractAssociatedIcon(path);
}
else if (File.Exists(resolvedPath) &&
Path.GetExtension(resolvedPath).Equals(".ico", StringComparison.InvariantCultureIgnoreCase))
{
icon = Icon.ExtractAssociatedIcon(resolvedPath);
if (linkOverlay)
{
icon = AddIconOverlay(icon, Properties.Resources.LinkArrow);
}
}
else
{
NativeMethods.SHFILEINFO shFileInfo = default;
uint flags = GetFlags(linkOverlay, size);
uint attribute = isFolder ? NativeMethods.FileAttributeDirectory : NativeMethods.FileAttributeNormal;
IntPtr imageList = NativeMethods.Shell32SHGetFileInfo(
path, attribute, ref shFileInfo, (uint)Marshal.SizeOf(shFileInfo), flags);
icon = GetIcon(path, linkOverlay, shFileInfo, imageList);
}
return icon;
}
private static uint GetFlags(bool linkOverlay, IconSize size)
{
uint flags = NativeMethods.ShgfiIcon | NativeMethods.ShgfiSYSICONINDEX;
if (linkOverlay)
{
flags += NativeMethods.ShgfiLINKOVERLAY;
}
if (size == IconSize.Small)
{
flags += NativeMethods.ShgfiSMALLICON;
}
else
{
flags += NativeMethods.ShgfiLARGEICON;
}
return flags;
}
private static Icon? GetIcon(
string path, bool linkOverlay, NativeMethods.SHFILEINFO shFileInfo, IntPtr imageList)
{
Icon? icon = null;
if (imageList != IntPtr.Zero)
{
IntPtr hIcon;
if (linkOverlay)
{
hIcon = shFileInfo.hIcon;
}
else
{
hIcon = NativeMethods.ImageList_GetIcon(
imageList, shFileInfo.iIcon, NativeMethods.IldTransparent);
}
try
{
icon = (Icon)Icon.FromHandle(hIcon).Clone();
}
catch (Exception ex)
{
Log.Warn($"path:'{path}'", ex);
}
if (!linkOverlay)
{
NativeMethods.User32DestroyIcon(hIcon);
}
NativeMethods.User32DestroyIcon(shFileInfo.hIcon);
}
return icon;
}
}
}