11// Copyright (c) Files Community
22// Licensed under the MIT License.
33
4+ using System . Collections . Concurrent ;
45using System . Runtime . InteropServices ;
56using Windows . Win32 ;
67using Windows . Win32 . Foundation ;
78using Windows . Win32 . Graphics . Gdi ;
89using Windows . Win32 . Graphics . GdiPlus ;
910using Windows . Win32 . System . Com ;
1011using Windows . Win32 . UI . Shell ;
12+ using Windows . Win32 . UI . WindowsAndMessaging ;
1113
1214namespace Files . App . Storage . Storables
1315{
1416 public static partial class WindowsStorableHelpers
1517 {
18+ // Fields
19+
1620 private static ( Guid Format , Guid Encorder ) [ ] ? GdiEncoders ;
21+ private static ConcurrentDictionary < ( string , int , int ) , byte [ ] > ? DllIconCache ;
22+
23+ // Methods
1724
18- /// <inheritdoc cref="GetThumbnail "/>
19- public static async Task < byte [ ] > GetThumbnailAsync ( this IWindowsStorable storable , int size , SIIGBF options )
25+ /// <inheritdoc cref="TryGetThumbnail "/>
26+ public static async Task < byte [ ] ? > GetThumbnailAsync ( this IWindowsStorable storable , int size , SIIGBF options )
2027 {
21- return await STATask . Run ( ( ) => storable . GetThumbnail ( size , options ) ) ;
28+ return await STATask . Run ( ( ) =>
29+ {
30+ storable . TryGetThumbnail ( size , options , out var thumbnailData ) ;
31+ return thumbnailData ;
32+ } ) ;
2233 }
2334
2435 /// <summary>
@@ -29,32 +40,99 @@ public static async Task<byte[]> GetThumbnailAsync(this IWindowsStorable storabl
2940 /// <param name="options">A combination of <see cref="SIIGBF"/> flags that specify how the thumbnail should be retrieved.</param>
3041 /// <returns>A byte array containing the thumbnail image in its native format (e.g., PNG, JPEG).</returns>
3142 /// <remarks>If the thumbnail is JPEG, this tries to decoded as a PNG instead because JPEG loses data.</remarks>
32- public unsafe static byte [ ] GetThumbnail ( this IWindowsStorable storable , int size , SIIGBF options )
43+ public unsafe static bool TryGetThumbnail ( this IWindowsStorable storable , int size , SIIGBF options , out byte [ ] ? thumbnailData )
3344 {
45+ thumbnailData = null ;
46+
3447 using ComPtr < IShellItemImageFactory > pShellItemImageFactory = storable . ThisPtr . As < IShellItemImageFactory > ( ) ;
3548 if ( pShellItemImageFactory . IsNull )
36- return [ ] ;
49+ return false ;
3750
3851 // Get HBITMAP
3952 HBITMAP hBitmap = default ;
4053 HRESULT hr = pShellItemImageFactory . Get ( ) ->GetImage ( new ( size , size ) , options , & hBitmap ) ;
4154 if ( hr . ThrowIfFailedOnDebug ( ) . Failed )
4255 {
4356 if ( ! hBitmap . IsNull ) PInvoke . DeleteObject ( hBitmap ) ;
44- return [ ] ;
57+ return false ;
4558 }
4659
4760 // Convert to GpBitmap of GDI+
4861 GpBitmap * gpBitmap = default ;
49- PInvoke . GdipCreateBitmapFromHBITMAP ( hBitmap , HPALETTE . Null , & gpBitmap ) ;
62+ if ( PInvoke . GdipCreateBitmapFromHBITMAP ( hBitmap , HPALETTE . Null , & gpBitmap ) is not Status . Ok )
63+ {
64+ if ( gpBitmap is not null ) PInvoke . GdipDisposeImage ( ( GpImage * ) gpBitmap ) ;
65+ if ( ! hBitmap . IsNull ) PInvoke . DeleteObject ( hBitmap ) ;
66+ return false ;
67+ }
68+
69+ if ( TryConvertGpBitmapToByteArray ( gpBitmap , out thumbnailData ) )
70+ {
71+ if ( ! hBitmap . IsNull ) PInvoke . DeleteObject ( hBitmap ) ;
72+ return false ;
73+ }
74+
75+ return true ;
76+ }
77+
78+ public unsafe static bool TryExtractImageFromDll ( this IWindowsStorable storable , int size , int index , out byte [ ] ? imageData )
79+ {
80+ DllIconCache ??= [ ] ;
81+ imageData = null ;
82+
83+ if ( storable . ToString ( ) is not { } path )
84+ return false ;
85+
86+ if ( DllIconCache . TryGetValue ( ( path , index , size ) , out var cachedImageData ) )
87+ {
88+ imageData = cachedImageData ;
89+ return true ;
90+ }
91+ else
92+ {
93+ HICON hIcon = default ;
94+ HRESULT hr = default ;
95+
96+ fixed ( char * pszPath = path )
97+ hr = PInvoke . SHDefExtractIcon ( pszPath , - 1 * index , 0 , & hIcon , null , ( uint ) size ) ;
98+
99+ if ( hr . ThrowIfFailedOnDebug ( ) . Failed )
100+ {
101+ if ( ! hIcon . IsNull ) PInvoke . DestroyIcon ( hIcon ) ;
102+ return false ;
103+ }
104+
105+ // Convert to GpBitmap of GDI+
106+ GpBitmap * gpBitmap = default ;
107+ if ( PInvoke . GdipCreateBitmapFromHICON ( hIcon , & gpBitmap ) is not Status . Ok )
108+ {
109+ if ( ! hIcon . IsNull ) PInvoke . DestroyIcon ( hIcon ) ;
110+ return false ;
111+ }
112+
113+ if ( ! TryConvertGpBitmapToByteArray ( gpBitmap , out imageData ) )
114+ {
115+ if ( ! hIcon . IsNull ) PInvoke . DestroyIcon ( hIcon ) ;
116+ return false ;
117+ }
118+
119+ DllIconCache [ ( path , index , size ) ] = imageData ;
120+ PInvoke . DestroyIcon ( hIcon ) ;
121+
122+ return true ;
123+ }
124+ }
125+
126+ public unsafe static bool TryConvertGpBitmapToByteArray ( GpBitmap * gpBitmap , out byte [ ] ? imageData )
127+ {
128+ imageData = null ;
50129
51130 // Get an encoder for PNG
52131 Guid format = Guid . Empty ;
53132 if ( PInvoke . GdipGetImageRawFormat ( ( GpImage * ) gpBitmap , & format ) is not Status . Ok )
54133 {
55134 if ( gpBitmap is not null ) PInvoke . GdipDisposeImage ( ( GpImage * ) gpBitmap ) ;
56- if ( ! hBitmap . IsNull ) PInvoke . DeleteObject ( hBitmap ) ;
57- return [ ] ;
135+ return false ;
58136 }
59137
60138 Guid encoder = GetEncoderClsid ( format ) ;
@@ -65,28 +143,25 @@ public unsafe static byte[] GetThumbnail(this IWindowsStorable storable, int siz
65143 }
66144
67145 using ComPtr < IStream > pStream = default ;
68- hr = PInvoke . CreateStreamOnHGlobal ( HGLOBAL . Null , true , pStream . GetAddressOf ( ) ) ;
146+ HRESULT hr = PInvoke . CreateStreamOnHGlobal ( HGLOBAL . Null , true , pStream . GetAddressOf ( ) ) ;
69147 if ( hr . ThrowIfFailedOnDebug ( ) . Failed )
70148 {
71149 if ( gpBitmap is not null ) PInvoke . GdipDisposeImage ( ( GpImage * ) gpBitmap ) ;
72- if ( ! hBitmap . IsNull ) PInvoke . DeleteObject ( hBitmap ) ;
73- return [ ] ;
150+ return false ;
74151 }
75152
76153 if ( PInvoke . GdipSaveImageToStream ( ( GpImage * ) gpBitmap , pStream . Get ( ) , & encoder , ( EncoderParameters * ) null ) is not Status . Ok )
77154 {
78155 if ( gpBitmap is not null ) PInvoke . GdipDisposeImage ( ( GpImage * ) gpBitmap ) ;
79- if ( ! hBitmap . IsNull ) PInvoke . DeleteObject ( hBitmap ) ;
80- return [ ] ;
156+ return false ;
81157 }
82158
83159 STATSTG stat = default ;
84160 hr = pStream . Get ( ) ->Stat ( & stat , ( uint ) STATFLAG . STATFLAG_NONAME ) ;
85161 if ( hr . ThrowIfFailedOnDebug ( ) . Failed )
86162 {
87163 if ( gpBitmap is not null ) PInvoke . GdipDisposeImage ( ( GpImage * ) gpBitmap ) ;
88- if ( ! hBitmap . IsNull ) PInvoke . DeleteObject ( hBitmap ) ;
89- return [ ] ;
164+ return false ;
90165 }
91166
92167 ulong statSize = stat . cbSize & 0xFFFFFFFF ;
@@ -97,15 +172,14 @@ public unsafe static byte[] GetThumbnail(this IWindowsStorable storable, int siz
97172 if ( hr . ThrowIfFailedOnDebug ( ) . Failed )
98173 {
99174 if ( gpBitmap is not null ) PInvoke . GdipDisposeImage ( ( GpImage * ) gpBitmap ) ;
100- if ( ! hBitmap . IsNull ) PInvoke . DeleteObject ( hBitmap ) ;
101175 if ( RawThumbnailData is not null ) NativeMemory . Free ( RawThumbnailData ) ;
102- return [ ] ;
176+ return false ;
103177 }
104178
105- byte [ ] thumbnailData = new ReadOnlySpan < byte > ( RawThumbnailData , ( int ) statSize / sizeof ( byte ) ) . ToArray ( ) ;
179+ imageData = new ReadOnlySpan < byte > ( RawThumbnailData , ( int ) statSize / sizeof ( byte ) ) . ToArray ( ) ;
106180 NativeMemory . Free ( RawThumbnailData ) ;
107181
108- return thumbnailData ;
182+ return true ;
109183
110184 Guid GetEncoderClsid ( Guid format )
111185 {
0 commit comments