Extract thumbnail for any file in Windows
This is an improved version of the (awesome) answer that was accepted. The following was changed:
- Use unsafe code instead of SetPixel() -> that improves performance drastically
remove the whole
isAlphaBitmap
check. I have no idea why that was implemented in the first place, but I see no reason to determine between two images which contain the same content (I just return the Bitmap with transparency, regardless if that changed something)[Flags] public enum ThumbnailOptions { None = 0x00, BiggerSizeOk = 0x01, InMemoryOnly = 0x02, IconOnly = 0x04, ThumbnailOnly = 0x08, InCacheOnly = 0x10 } public class WindowsThumbnailProvider { private const string IShellItem2Guid = "7E9FB0D3-919F-4307-AB2E-9B1860310C93"; [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] internal static extern int SHCreateItemFromParsingName( [MarshalAs(UnmanagedType.LPWStr)] string path, // The following parameter is not used - binding context. IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem); [DllImport("gdi32.dll")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool DeleteObject(IntPtr hObject); [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] public static extern unsafe IntPtr memcpy(void* dst, void* src, UIntPtr count); public static Bitmap GetThumbnail(string fileName, int width, int height, ThumbnailOptions options) { var hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options); try { // return a System.Drawing.Bitmap from the hBitmap return GetBitmapFromHBitmap(hBitmap); } finally { // delete HBitmap to avoid memory leaks DeleteObject(hBitmap); } } public static Bitmap GetBitmapFromHBitmap(IntPtr nativeHBitmap) { Bitmap bmp = Image.FromHbitmap(nativeHBitmap); if (Image.GetPixelFormatSize(bmp.PixelFormat) < 32) return bmp; using (bmp) return CreateAlphaBitmap(bmp, PixelFormat.Format32bppArgb); } public static unsafe Bitmap CreateAlphaBitmap(Bitmap srcBitmap, PixelFormat targetPixelFormat) { var result = new Bitmap(srcBitmap.Width, srcBitmap.Height, targetPixelFormat); var bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height); var srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat); var destData = result.LockBits(bmpBounds, ImageLockMode.ReadOnly, targetPixelFormat); var srcDataPtr = (byte*) srcData.Scan0; var destDataPtr = (byte*) destData.Scan0; try { for (int y = 0; y <= srcData.Height - 1; y++) { for (int x = 0; x <= srcData.Width - 1; x++) { //this is really important because one stride may be positive and the other negative var position = srcData.Stride * y + 4 * x; var position2 = destData.Stride * y + 4 * x; memcpy(destDataPtr + position2, srcDataPtr + position, (UIntPtr) 4); } } } finally { srcBitmap.UnlockBits(srcData); result.UnlockBits(destData); } return result; } private static IntPtr GetHBitmap(string fileName, int width, int height, ThumbnailOptions options) { IShellItem nativeShellItem; Guid shellItem2Guid = new Guid(IShellItem2Guid); int retCode = SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref shellItem2Guid, out nativeShellItem); if (retCode != 0) throw Marshal.GetExceptionForHR(retCode); NativeSize nativeSize = new NativeSize(); nativeSize.Width = width; nativeSize.Height = height; IntPtr hBitmap; HResult hr = ((IShellItemImageFactory) nativeShellItem).GetImage(nativeSize, options, out hBitmap); Marshal.ReleaseComObject(nativeShellItem); if (hr == HResult.Ok) return hBitmap; throw Marshal.GetExceptionForHR((int) hr); } [ComImport] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")] internal interface IShellItem { void BindToHandler(IntPtr pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv); void GetParent(out IShellItem ppsi); void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName); void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs); void Compare(IShellItem psi, uint hint, out int piOrder); } internal enum SIGDN : uint { NORMALDISPLAY = 0, PARENTRELATIVEPARSING = 0x80018001, PARENTRELATIVEFORADDRESSBAR = 0x8001c001, DESKTOPABSOLUTEPARSING = 0x80028000, PARENTRELATIVEEDITING = 0x80031001, DESKTOPABSOLUTEEDITING = 0x8004c000, FILESYSPATH = 0x80058000, URL = 0x80068000 } internal enum HResult { Ok = 0x0000, False = 0x0001, InvalidArguments = unchecked((int) 0x80070057), OutOfMemory = unchecked((int) 0x8007000E), NoInterface = unchecked((int) 0x80004002), Fail = unchecked((int) 0x80004005), ElementNotFound = unchecked((int) 0x80070490), TypeElementNotFound = unchecked((int) 0x8002802B), NoObject = unchecked((int) 0x800401E5), Win32ErrorCanceled = 1223, Canceled = unchecked((int) 0x800704C7), ResourceInUse = unchecked((int) 0x800700AA), AccessDenied = unchecked((int) 0x80030005) } [ComImport] [Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IShellItemImageFactory { [PreserveSig] HResult GetImage( [In, MarshalAs(UnmanagedType.Struct)] NativeSize size, [In] ThumbnailOptions flags, [Out] out IntPtr phbm); } [StructLayout(LayoutKind.Sequential)] internal struct NativeSize { private int width; private int height; public int Width { set { width = value; } } public int Height { set { height = value; } } } [StructLayout(LayoutKind.Sequential)] public struct RGBQUAD { public byte rgbBlue; public byte rgbGreen; public byte rgbRed; public byte rgbReserved; } }
Some time ago I wrote a ThumbnailProvider that loads a thumbnail from the Win API. Supports transparent images. This is the implementation:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.IO;
namespace ThumbnailGenerator
{
[Flags]
public enum ThumbnailOptions
{
None = 0x00,
BiggerSizeOk = 0x01,
InMemoryOnly = 0x02,
IconOnly = 0x04,
ThumbnailOnly = 0x08,
InCacheOnly = 0x10,
}
public class WindowsThumbnailProvider
{
private const string IShellItem2Guid = "7E9FB0D3-919F-4307-AB2E-9B1860310C93";
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern int SHCreateItemFromParsingName(
[MarshalAs(UnmanagedType.LPWStr)] string path,
// The following parameter is not used - binding context.
IntPtr pbc,
ref Guid riid,
[MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem);
[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DeleteObject(IntPtr hObject);
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
internal interface IShellItem
{
void BindToHandler(IntPtr pbc,
[MarshalAs(UnmanagedType.LPStruct)]Guid bhid,
[MarshalAs(UnmanagedType.LPStruct)]Guid riid,
out IntPtr ppv);
void GetParent(out IShellItem ppsi);
void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName);
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
void Compare(IShellItem psi, uint hint, out int piOrder);
};
internal enum SIGDN : uint
{
NORMALDISPLAY = 0,
PARENTRELATIVEPARSING = 0x80018001,
PARENTRELATIVEFORADDRESSBAR = 0x8001c001,
DESKTOPABSOLUTEPARSING = 0x80028000,
PARENTRELATIVEEDITING = 0x80031001,
DESKTOPABSOLUTEEDITING = 0x8004c000,
FILESYSPATH = 0x80058000,
URL = 0x80068000
}
internal enum HResult
{
Ok = 0x0000,
False = 0x0001,
InvalidArguments = unchecked((int)0x80070057),
OutOfMemory = unchecked((int)0x8007000E),
NoInterface = unchecked((int)0x80004002),
Fail = unchecked((int)0x80004005),
ElementNotFound = unchecked((int)0x80070490),
TypeElementNotFound = unchecked((int)0x8002802B),
NoObject = unchecked((int)0x800401E5),
Win32ErrorCanceled = 1223,
Canceled = unchecked((int)0x800704C7),
ResourceInUse = unchecked((int)0x800700AA),
AccessDenied = unchecked((int)0x80030005)
}
[ComImportAttribute()]
[GuidAttribute("bcc18b79-ba16-442f-80c4-8a59c30c463b")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IShellItemImageFactory
{
[PreserveSig]
HResult GetImage(
[In, MarshalAs(UnmanagedType.Struct)] NativeSize size,
[In] ThumbnailOptions flags,
[Out] out IntPtr phbm);
}
[StructLayout(LayoutKind.Sequential)]
internal struct NativeSize
{
private int width;
private int height;
public int Width { set { width = value; } }
public int Height { set { height = value; } }
};
[StructLayout(LayoutKind.Sequential)]
public struct RGBQUAD
{
public byte rgbBlue;
public byte rgbGreen;
public byte rgbRed;
public byte rgbReserved;
}
public static Bitmap GetThumbnail(string fileName, int width, int height, ThumbnailOptions options)
{
IntPtr hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
try
{
// return a System.Drawing.Bitmap from the hBitmap
return GetBitmapFromHBitmap(hBitmap);
}
finally
{
// delete HBitmap to avoid memory leaks
DeleteObject(hBitmap);
}
}
public static Bitmap GetBitmapFromHBitmap(IntPtr nativeHBitmap)
{
Bitmap bmp = Bitmap.FromHbitmap(nativeHBitmap);
if (Bitmap.GetPixelFormatSize(bmp.PixelFormat) < 32)
return bmp;
return CreateAlphaBitmap(bmp, PixelFormat.Format32bppArgb);
}
public static Bitmap CreateAlphaBitmap(Bitmap srcBitmap, PixelFormat targetPixelFormat)
{
Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, targetPixelFormat);
Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
bool isAlplaBitmap = false;
try
{
for (int y = 0; y <= srcData.Height - 1; y++)
{
for (int x = 0; x <= srcData.Width - 1; x++)
{
Color pixelColor = Color.FromArgb(
Marshal.ReadInt32(srcData.Scan0, (srcData.Stride * y) + (4 * x)));
if (pixelColor.A > 0 & pixelColor.A < 255)
{
isAlplaBitmap = true;
}
result.SetPixel(x, y, pixelColor);
}
}
}
finally
{
srcBitmap.UnlockBits(srcData);
}
if (isAlplaBitmap)
{
return result;
}
else
{
return srcBitmap;
}
}
private static IntPtr GetHBitmap(string fileName, int width, int height, ThumbnailOptions options)
{
IShellItem nativeShellItem;
Guid shellItem2Guid = new Guid(IShellItem2Guid);
int retCode = SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref shellItem2Guid, out nativeShellItem);
if (retCode != 0)
throw Marshal.GetExceptionForHR(retCode);
NativeSize nativeSize = new NativeSize();
nativeSize.Width = width;
nativeSize.Height = height;
IntPtr hBitmap;
HResult hr = ((IShellItemImageFactory)nativeShellItem).GetImage(nativeSize, options, out hBitmap);
Marshal.ReleaseComObject(nativeShellItem);
if (hr == HResult.Ok) return hBitmap;
throw Marshal.GetExceptionForHR((int)hr);
}
}
}
Then you can use it in the following way:
int THUMB_SIZE = 256;
Bitmap thumbnail = WindowsThumbnailProvider.GetThumbnail(
fileName, THUMB_SIZE, THUMB_SIZE, ThumbnailOptions.None);
Remember that you need to Dispose() the bitmap after using it.