Извличане на миниатюра за всеки файл в Windows

Кой е най-ефективният начин за извличане на миниатюри от всеки файл, а не само изображения с различни размери?

Прегледах всичко, най-обещаващият от които беше Windows API ShellFile, но изглежда, че това не се инсталира правилно. Използвам windows 7.


person matinau    schedule 13.02.2014    source източник
comment
Какво е неефективно по твоя начин, че искаш най-ефикасния?   -  person L.B    schedule 13.02.2014
comment
Разгледайте горния отговор за това... stackoverflow.com/questions/1439719/   -  person Matt Webber    schedule 13.02.2014


Отговори (2)


Преди време написах ThumbnailProvider, който зарежда миниатюра от Win API. Поддържа прозрачни изображения. Това е изпълнението:

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

След това можете да го използвате по следния начин:

int THUMB_SIZE = 256;
Bitmap thumbnail = WindowsThumbnailProvider.GetThumbnail(
   fileName, THUMB_SIZE, THUMB_SIZE, ThumbnailOptions.None);

Не забравяйте, че трябва да Dispose() растерното изображение след използването му.

person Daniel Peñalba    schedule 13.02.2014
comment
Уау, това изглежда умно. Ще се опитам да го пробвам днес някой път. Благодаря Даниел - person matinau; 14.02.2014
comment
Бихте ли искали да пуснете това под лиценз в стил MIT? Благодаря! Това е удивително! - person IronManMark20; 04.11.2015
comment
@IronManMark20: Разбира се, не се колебайте да го използвате както искате. - person Daniel Peñalba; 04.11.2015
comment
Благодаря. Толкова много! Ще се радвам да използвам това много! - person IronManMark20; 05.11.2015
comment
Отличен отговор! - person Lord of Scripts; 29.08.2016
comment
Би било полезно да обясните защо се изисква CreateAlphaBitmap, а Bitmap.FromHbitmap не е достатъчно. - person Alex Vang; 04.12.2018
comment
Добър принос! Би било чудесно да имате пакет NuGet за това :) - person NinjaCross; 22.12.2019
comment
Когато стартирам този код на Windows 10, работи перфектно, когато стартирам същия код на Windows Server 2008R2, всяка снимка, направена в портрет, миниатюрите идват в пейзаж. Някаква идея? - person Albert Torres; 29.06.2020

Това е подобрена версия на (страхотния) отговор, който беше приет. Променено е следното:

  • Използвайте опасен код вместо SetPixel() ->, който драстично подобрява производителността
  • премахнете цялата проверка isAlphaBitmap. Нямам представа защо това е приложено на първо място, но не виждам причина да определям между две изображения, които съдържат едно и също съдържание (просто връщам Bitmap с прозрачност, независимо дали това промени нещо)

    [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;
        }
    }
    
person Snicker    schedule 11.02.2017
comment
Вашият using (srcBitmap)… трябва да бъде направен от повикващия(ите), в този случай GetBitmapFromHBitmap, така че последният ред на тази функция трябва да бъде заменен с using (srcBitmap) return CreateAlphaBitmap(bmp, PixelFormat.Format32bppArgb);. Начинът, по който го имате, вашето използване на srcBitmap не е в неговия собствен използващ блок. Въпреки че специалното познаване на ключовата дума using ни казва, че този нетрадиционен код работи, той нарушава RAII, като по този начин двусмисля отговорността за почистване. Обърнете внимание, че сега работи само при специални познания за вашия (единствен) обаждащ се; друг обаждащ се може да иска да продължи да използва bmp. - person Glenn Slayden; 19.04.2017
comment
p.s. Ако по някаква причина искате да го запазите така, както го имате, тогава IMHO би било по-ясно просто да извикате srcBitmap.Dispose(), вместо да използвате конструкцията C# using. И ако искате да редактирам отговора ви, както предложих по-горе, просто ме уведомете. - person Glenn Slayden; 19.04.2017
comment
@GlennSlayden ти си напълно прав, благодаря ти за обратната връзка. Мислех си за това, че ако създадете прозрачно изображение на вашето изображение, никога не искате да запазите непрозрачното изображение, но сте прав, по-добре е така. Току-що редактирах отговора си, моля, проверете дали това е наред, в противен случай го променете както искате. Приятен ден :) - person Snicker; 19.04.2017
comment
Изглежда добре, но ако погледнем по-нататък, мисля, че всъщност можете напълно да се отървете от повторното копиране на растерното изображение (т.е. да изтриете цялата функция CreateAlphaBitmap). Не е ли растерното изображение, върнато от Image.FromHbitmap(...), напълно достатъчно добро? Освен това определено трябва да извикате Freeze() на окончателното растерно изображение, преди да го върнете. - person Glenn Slayden; 20.04.2017
comment
@GlennSlayden Можете ли да замразите System.Drawing.Bitmap? Това не е ли нещо само за WPF? Доколкото си спомням, CreateAlphaBitmap беше доста важно, не се колебайте да проверите разликата. - person Snicker; 20.04.2017
comment
Моя грешка. Да, това е WPF нещо, за което си мислех. - person Glenn Slayden; 21.04.2017