/* * IconExtractor/IconUtil for .NET * Copyright (C) 2014 Tsuda Kageyu. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Drawing; using System.IO; using System.Reflection; using System.Reflection.Emit; using System.Windows.Forms; using Vanara.PInvoke; using static Vanara.PInvoke.ComCtl32; using static Vanara.PInvoke.Shell32; using static Vanara.PInvoke.User32; namespace Vanara.Extensions { /// Used to determine the size of the icon returned by various shell methods. public enum IconSize { /// /// The image size is normally 32x32 pixels. However, if the Use large icons option is selected from the Effects section of the Appearance tab in /// Display Properties, the image is 48x48 pixels. /// Large = SHIL.SHIL_LARGE, /// The image is the Shell standard small icon size of 16x16, but the size can be customized by the user. Small = SHIL.SHIL_SMALL, /// The image is the Shell standard extra-large icon size. This is typically 48x48, but the size can be customized by the user. ExtraLarge = SHIL.SHIL_EXTRALARGE, /// Windows Vista and later. The image is normally 256x256 pixels. Jumbo = SHIL.SHIL_JUMBO } /// Extension methods for . public static class IconExtension { private delegate byte[] GetIconDataDelegate(Icon icon); private static readonly GetIconDataDelegate getIconData; private static HIMAGELIST hSystemImageList; static IconExtension() { // Create a dynamic method to access Icon.iconData private field. var dm = new DynamicMethod("GetIconData", typeof(byte[]), new[] { typeof(Icon) }, typeof(Icon)); var fi = typeof(Icon).GetField("iconData", BindingFlags.Instance | BindingFlags.NonPublic); var gen = dm.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, fi); gen.Emit(OpCodes.Ret); getIconData = (GetIconDataDelegate)dm.CreateDelegate(typeof(GetIconDataDelegate)); } /// Gets the bit depth of an Icon. /// An System.Drawing.Icon object. /// Bit depth of the icon. /// This method takes into account the PNG header. If the icon has multiple variations, this method returns the bit depth of the first variation. public static int GetBitCount(this Icon icon) { if (icon == null) throw new ArgumentNullException(nameof(icon)); // Get an .ico file in memory, then read the header. var data = ToByteArray(icon); int idCount = BitConverter.ToInt16(data, 4); // ICONDIR.idCount if (idCount < 1 || data.Length < 14) throw new ArgumentException(@"The icon is corrupt. Couldn't read the header.", nameof(icon)); var bColorCount = data[8]; // ICONDIRENTRY.bColorCount var wPlanes = BitConverter.ToInt16(data, 10); // ICONDIRENTRY.wPlanes var wBitCount = BitConverter.ToInt16(data, 12); // ICONDIRENTRY.wBitCount var bits = wPlanes * wBitCount; if (bits >= 8 || bColorCount == 0) return bits; for (bits = 4; bits > 0 && 1 << bits != bColorCount; bits >>= 1) { } return bits; } /// Load a full icon from a file. The file must be in the Windows ICO format. It may contain multiple instances of an icon. /// The filename of the icon file. /// An instance. public static Icon IconFromFile(string filename) { using (var fs = File.OpenRead(filename)) return new Icon(fs); } /// Gets the icon associated with this ITEMIDLIST, if one does. /// The ITEMIDLIST pointer from which to retrieve the icon. /// Size of the icon. /// Icon of the specified size, or null if no icon is associated with this ITEMIDLIST. public static Icon GetIcon(this PIDL pidl, IconSize iconSize = IconSize.Large) { if (pidl.IsInvalid) return null; var shfi = new SHFILEINFO(); var ret = SHGetFileInfo(pidl, 0, ref shfi, SHFILEINFO.Size, SHGFI.SHGFI_ICON | SHGFI.SHGFI_PIDL | (SHGFI)iconSize); return ret == IntPtr.Zero ? null : new SafeHICON((IntPtr)shfi.hIcon).ToIcon(); } /// Gets the system icon for the given file name or extension. /// The file name or extension. /// Size of the icon. /// An instance if found; otherwise . public static Icon GetSystemIcon(string fileNameOrExtension, IconSize iconSize = IconSize.Large) { var shfi = new SHFILEINFO(); if (hSystemImageList.IsNull) hSystemImageList = SHGetFileInfo(fileNameOrExtension, 0, ref shfi, SHFILEINFO.Size, SHGFI.SHGFI_SYSICONINDEX | (iconSize == IconSize.Small ? SHGFI.SHGFI_SMALLICON : 0)); if (hSystemImageList.IsNull) return null; if (iconSize <= IconSize.Small) return ImageList_GetIcon(hSystemImageList, shfi.iIcon, IMAGELISTDRAWFLAGS.ILD_TRANSPARENT).ToIcon(); SHGetImageList((SHIL)iconSize, typeof(IImageList).GUID, out var il).ThrowIfFailed(); return il.GetIcon(shfi.iIcon, IMAGELISTDRAWFLAGS.ILD_TRANSPARENT).ToIcon(); } /// Gets the Shell icon for the given file name or extension. /// The file name or extension . /// Flags to specify the type of the icon to retrieve. This uses the method and can only retrieve small or large icons. /// An instance if found; otherwise . public static Icon GetFileIcon(string fileNameOrExtension, IconSize iconType = IconSize.Large) { var shfi = new SHFILEINFO(); var ret = SHGetFileInfo(fileNameOrExtension, 0, ref shfi, SHFILEINFO.Size, SHGFI.SHGFI_USEFILEATTRIBUTES | SHGFI.SHGFI_ICON | (SHGFI)iconType); if (ret == IntPtr.Zero) ret = SHGetFileInfo(fileNameOrExtension, 0, ref shfi, SHFILEINFO.Size, SHGFI.SHGFI_ICON | (SHGFI)iconType); return ret == IntPtr.Zero ? null : new SafeHICON((IntPtr)shfi.hIcon).ToIcon(); } /// Split an Icon consists of multiple icons into an array of Icon each consists of single icons. /// A System.Drawing.Icon to be split. /// An array of System.Drawing.Icon. public static Icon[] Split(this Icon icon) { const int sIconDir = 6; // sizeof(ICONDIR) const int sIconDirEntry = 16; // sizeof(ICONDIRENTRY) if (icon == null) throw new ArgumentNullException(nameof(icon)); // Get multiple .ico file image. var srcBuf = ToByteArray(icon); int count = BitConverter.ToInt16(srcBuf, 4); // ICONDIR.idCount var splitIcons = new Icon[count]; for (var i = 0; i < count; i++) { using (var destStream = new MemoryStream()) { using (var writer = new BinaryWriter(destStream)) { // Copy ICONDIR and ICONDIRENTRY. writer.Write(srcBuf, 0, sIconDir - 2); writer.Write((short)1); // ICONDIR.idCount == 1; writer.Write(srcBuf, sIconDir + sIconDirEntry * i, sIconDirEntry - 4); writer.Write(sIconDir + sIconDirEntry); // ICONDIRENTRY.dwImageOffset = sizeof(ICONDIR) + sizeof(ICONDIRENTRY) // Copy picture and mask data. var imgSize = BitConverter.ToInt32(srcBuf, sIconDir + sIconDirEntry * i + 8); // ICONDIRENTRY.dwBytesInRes var imgOffset = BitConverter.ToInt32(srcBuf, sIconDir + sIconDirEntry * i + 12); // ICONDIRENTRY.dwImageOffset writer.Write(srcBuf, imgOffset, imgSize); // Create new icon. destStream.Seek(0, SeekOrigin.Begin); splitIcons[i] = new Icon(destStream); } } } return splitIcons; } /// Converts this icon to a GDI+ and preserves any transparency from the source icon. /// The icon to convert. /// A that represents the converted . public static Bitmap ToAlphaBitmap(this Icon icon) { if (icon.GetBitCount() < 32) return icon.ToBitmap(); var il = new ImageList {ColorDepth = ColorDepth.Depth32Bit, ImageSize = icon.Size}; il.Images.Add(icon); return il.Images[0] as Bitmap; /*var ii = new ICONINFO(); if (!GetIconInfo(icon.Handle, ref ii)) throw new Win32Exception(); var bmp = ii.Bitmap; if (Image.GetPixelFormatSize(bmp.PixelFormat) == 32) { using (var lb = new ExtensionMethods.SmartBitmapLock(bmp)) { if (lb.Any(c => c.A > 0 && c.A < 255)) return lb.ToBitmap(); } } return icon.ToBitmap();*/ } /// Converts an to a byte array. /// The instance. /// The icon expressed as an array of bytes. public static byte[] ToByteArray(this Icon icon) { var data = getIconData(icon); if (data != null) return data; using (var ms = new MemoryStream()) { icon.Save(ms); return ms.ToArray(); } } /*[StructLayout(LayoutKind.Sequential)] public struct GrpIconDir { private ushort idReserved; public ushort idType; public ushort idCount; [MarshalAs(UnmanagedType.ByValArray)] public GrpIconDirEntry[] idEntries; } [StructLayout(LayoutKind.Sequential)] public struct GrpIconDirEntry { public byte bWidth; // Width, in pixels, of the image public byte bHeight; // Height, in pixels, of the image public byte bColorCount; // Number of colors in image (0 if >=8bpp) public byte bReserved; // Reserved ( must be 0) public ushort wPlanes; // Color Planes public ushort wBitCount; // Bits per pixel public uint dwBytesInRes; // How many bytes in this resource? public ushort nID; // the ID }*/ } }