Extract thumbnail for any file in Windows

What's the most efficient way of extracting thumbnails from any file, not just just images at varying sizes?

I've looked all over, most promising of which was Windows API ShellFile yet this didn't appear to install properly. I'm using windows 7.


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.


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;
        }
    }