2017-11-27 13:11:20 -05:00
/ *
* 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 ;
2018-09-22 00:35:34 -04:00
using Vanara.PInvoke ;
using static Vanara . PInvoke . ComCtl32 ;
2017-11-27 13:11:20 -05:00
using static Vanara . PInvoke . Shell32 ;
2019-08-27 18:03:21 -04:00
using static Vanara . PInvoke . User32 ;
2017-11-27 13:11:20 -05:00
namespace Vanara.Extensions
{
2018-11-28 14:33:55 -05:00
/// <summary>Used to determine the size of the icon returned by various shell methods.</summary>
2018-09-22 00:35:34 -04:00
public enum IconSize
{
/// <summary>
/// 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.
/// </summary>
Large = SHIL . SHIL_LARGE ,
/// <summary>The image is the Shell standard small icon size of 16x16, but the size can be customized by the user.</summary>
Small = SHIL . SHIL_SMALL ,
/// <summary>The image is the Shell standard extra-large icon size. This is typically 48x48, but the size can be customized by the user.</summary>
ExtraLarge = SHIL . SHIL_EXTRALARGE ,
/// <summary>Windows Vista and later. The image is normally 256x256 pixels.</summary>
Jumbo = SHIL . SHIL_JUMBO
}
2020-04-02 23:59:18 -04:00
/// <summary>Extension methods for <see cref="Icon"/>.</summary>
2017-11-27 13:11:20 -05:00
public static class IconExtension
{
private delegate byte [ ] GetIconDataDelegate ( Icon icon ) ;
private static readonly GetIconDataDelegate getIconData ;
2018-09-22 00:35:34 -04:00
private static HIMAGELIST hSystemImageList ;
2017-11-27 13:11:20 -05:00
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 ) ) ;
}
/// <summary>Gets the bit depth of an Icon.</summary>
/// <param name="icon">An System.Drawing.Icon object.</param>
/// <returns>Bit depth of the icon.</returns>
/// <remarks>This method takes into account the PNG header. If the icon has multiple variations, this method returns the bit depth of the first variation.</remarks>
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 ;
}
/// <summary>Load a full icon from a file. The file must be in the Windows ICO format. It may contain multiple instances of an icon.</summary>
/// <param name="filename">The filename of the icon file.</param>
/// <returns>An <see cref="Icon"/> instance.</returns>
public static Icon IconFromFile ( string filename )
{
using ( var fs = File . OpenRead ( filename ) )
return new Icon ( fs ) ;
}
/// <summary>Gets the icon associated with this ITEMIDLIST, if one does.</summary>
/// <param name="pidl">The ITEMIDLIST pointer from which to retrieve the icon.</param>
2018-09-22 00:35:34 -04:00
/// <param name="iconSize">Size of the icon.</param>
2017-11-27 13:11:20 -05:00
/// <returns>Icon of the specified size, or <c>null</c> if no icon is associated with this ITEMIDLIST.</returns>
2018-11-28 14:33:55 -05:00
public static Icon GetIcon ( this PIDL pidl , IconSize iconSize = IconSize . Large )
2017-11-27 13:11:20 -05:00
{
if ( pidl . IsInvalid ) return null ;
var shfi = new SHFILEINFO ( ) ;
2018-11-28 14:33:55 -05:00
var ret = SHGetFileInfo ( pidl , 0 , ref shfi , SHFILEINFO . Size , SHGFI . SHGFI_ICON | SHGFI . SHGFI_PIDL | ( SHGFI ) iconSize ) ;
2018-11-19 23:18:50 -05:00
return ret = = IntPtr . Zero ? null : new SafeHICON ( ( IntPtr ) shfi . hIcon ) . ToIcon ( ) ;
2018-09-22 00:35:34 -04:00
}
/// <summary>Gets the system icon for the given file name or extension.</summary>
/// <param name="fileNameOrExtension">The file name or extension.</param>
/// <param name="iconSize">Size of the icon.</param>
/// <returns>An <see cref="Icon"/> instance if found; otherwise <see langword="null"/>.</returns>
public static Icon GetSystemIcon ( string fileNameOrExtension , IconSize iconSize = IconSize . Large )
{
var shfi = new SHFILEINFO ( ) ;
2020-11-02 09:53:41 -05:00
if ( hSystemImageList . IsNull )
2018-09-22 00:35:34 -04:00
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 ( ) ;
2022-01-14 09:32:51 -05:00
return ( ( IImageList ) il ) . GetIcon ( shfi . iIcon , IMAGELISTDRAWFLAGS . ILD_TRANSPARENT ) . ToIcon ( ) ;
2018-09-22 00:35:34 -04:00
}
/// <summary>Gets the Shell icon for the given file name or extension.</summary>
/// <param name="fileNameOrExtension">The file name or extension .</param>
2018-11-28 14:33:55 -05:00
/// <param name="iconType">Flags to specify the type of the icon to retrieve. This uses the <see cref="SHGetFileInfo(string, FileAttributes, ref SHFILEINFO, int, SHGFI)"/> method and can only retrieve small or large icons.</param>
2018-09-22 00:35:34 -04:00
/// <returns>An <see cref="Icon"/> instance if found; otherwise <see langword="null"/>.</returns>
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 ) ;
2018-11-19 23:18:50 -05:00
return ret = = IntPtr . Zero ? null : new SafeHICON ( ( IntPtr ) shfi . hIcon ) . ToIcon ( ) ;
2017-11-27 13:11:20 -05:00
}
/// <summary>Split an Icon consists of multiple icons into an array of Icon each consists of single icons.</summary>
/// <param name="icon">A System.Drawing.Icon to be split.</param>
/// <returns>An array of System.Drawing.Icon.</returns>
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 ;
}
/// <summary>Converts this icon to a GDI+ <see cref="Bitmap"/> and preserves any transparency from the source icon.</summary>
/// <param name="icon">The icon to convert.</param>
/// <returns>A <see cref="Bitmap"/> that represents the converted <paramref name="icon"/>.</returns>
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 ( ) ; * /
}
/// <summary>Converts an <see cref="Icon"/> to a byte array.</summary>
/// <param name="icon">The <see cref="Icon"/> instance.</param>
/// <returns>The icon expressed as an array of bytes.</returns>
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
} * /
}
}