@@ -34,6 +34,15 @@ namespace Imaging
3434 /// </summary>
3535 public static class ImageGifHandler
3636 {
37+ /// <summary>
38+ /// Deletes the object.
39+ /// </summary>
40+ /// <param name="hObject">The h object.</param>
41+ /// <returns>Status of cleanup</returns>
42+ [ System . Runtime . InteropServices . DllImport ( "gdi32.dll" ) ]
43+ [ return : System . Runtime . InteropServices . MarshalAs ( System . Runtime . InteropServices . UnmanagedType . Bool ) ]
44+ internal static extern bool DeleteObject ( IntPtr hObject ) ;
45+
3746 /// <summary>
3847 /// Gets the image information.
3948 /// </summary>
@@ -109,14 +118,15 @@ internal static async Task<List<ImageSource>> LoadGif(string path)
109118 }
110119
111120 /// <summary>
112- /// Creates the gif.
113- /// The gif is slightly bigger for now
114- /// Sources:
115- /// https://stackoverflow.com/questions/18719302/net-creating-a-looping-gif-using-gifbitmapencoder
121+ /// Creates the gif.
122+ /// The gif is slightly bigger for now
123+ /// Sources:
124+ /// https://stackoverflow.com/questions/18719302/net-creating-a-looping-gif-using-gifbitmapencoder
116125 /// </summary>
117126 /// <param name="path">The path to the folder.</param>
118127 /// <param name="target">The target path.</param>
119- internal static void CreateGif ( string path , string target )
128+ /// <param name="delayMs">Optional delay between frames in milliseconds.</param>
129+ internal static void CreateGif ( string path , string target , int delayMs = 100 )
120130 {
121131 //get all allowed files from target folder
122132 var lst = FileHelper . GetFilesByExtensionFullPath ( path , ImagingResources . Appendix ) ;
@@ -131,15 +141,16 @@ internal static void CreateGif(string path, string target)
131141 return ;
132142 }
133143
134- GifCreator ( btm , target ) ;
144+ GifCreator ( btm , target , delayMs ) ;
135145 }
136146
137147 /// <summary>
138- /// Creates the GIF.
148+ /// Creates the GIF.
139149 /// </summary>
140150 /// <param name="path">The path.</param>
141151 /// <param name="target">The target.</param>
142- internal static void CreateGif ( List < string > path , string target )
152+ /// <param name="delayMs">Optional delay between frames in milliseconds.</param>
153+ internal static void CreateGif ( List < string > path , string target , int delayMs = 100 )
143154 {
144155 //collect and convert all images
145156 var btm = path . ConvertAll ( ImageStream . GetOriginalBitmap ) ;
@@ -149,7 +160,18 @@ internal static void CreateGif(List<string> path, string target)
149160 return ;
150161 }
151162
152- GifCreator ( btm , target ) ;
163+ GifCreator ( btm , target , delayMs ) ;
164+ }
165+
166+ /// <summary>
167+ /// Creates the GIF with variable delays per frame using Tuples.
168+ /// </summary>
169+ /// <param name="frames">Collection of Bitmap and specific DelayMs tuples.</param>
170+ /// <param name="target">The target.</param>
171+ internal static void CreateGif ( IEnumerable < ( Bitmap Image , int DelayMs ) > frames , string target )
172+ {
173+ if ( frames == null ) return ;
174+ GifCreator ( frames , target ) ;
153175 }
154176
155177 /// <summary>
@@ -168,40 +190,81 @@ internal static void CreateGif(IEnumerable<FrameInfo>? frames, string target)
168190 }
169191
170192 /// <summary>
171- /// Create the gif.
193+ /// Create the gif with a fixed delay .
172194 /// </summary>
173195 /// <param name="btm">A list of Bitmaps.</param>
174196 /// <param name="target">The target.</param>
175- private static void GifCreator ( IEnumerable < Bitmap > btm , string target )
197+ /// <summary>
198+ /// Create the gif from a list of Bitmaps.
199+ /// </summary>
200+ /// <param name="btm">A list of Bitmaps.</param>
201+ /// <param name="target">The target.</param>
202+ private static void GifCreator ( IEnumerable < Bitmap > btm , string target , int delayMs )
176203 {
177204 var gEnc = new GifBitmapEncoder ( ) ;
205+ ushort gifDelay = ( ushort ) ( delayMs / 10 ) ; // Convert to hundredths of a second
178206
179- foreach ( var src in btm . Select ( bmpImage => bmpImage . GetHbitmap ( ) ) . Select ( bmp =>
180- System . Windows . Interop . Imaging . CreateBitmapSourceFromHBitmap (
181- bmp ,
182- IntPtr . Zero ,
183- Int32Rect . Empty ,
184- BitmapSizeOptions . FromEmptyOptions ( ) ) ) )
207+ foreach ( var bmpImage in btm )
185208 {
186- gEnc . Frames . Add ( BitmapFrame . Create ( src ) ) ;
209+ IntPtr hBitmap = bmpImage . GetHbitmap ( ) ;
210+ try
211+ {
212+ var src = System . Windows . Interop . Imaging . CreateBitmapSourceFromHBitmap (
213+ hBitmap ,
214+ IntPtr . Zero ,
215+ Int32Rect . Empty ,
216+ BitmapSizeOptions . FromEmptyOptions ( ) ) ;
217+
218+ var metadata = new BitmapMetadata ( "gif" ) ;
219+ metadata . SetQuery ( "/grctlext/Delay" , gifDelay ) ;
220+
221+ gEnc . Frames . Add ( BitmapFrame . Create ( src , null , metadata , null ) ) ;
222+ }
223+ finally
224+ {
225+ DeleteObject ( hBitmap ) ;
226+ }
187227 }
188228
189- using var ms = new MemoryStream ( ) ;
190- gEnc . Save ( ms ) ;
191- var fileBytes = ms . ToArray ( ) ;
192- // write custom header
193- // This is the NETSCAPE2.0 Application Extension.
194- var applicationExtension =
195- new byte [ ] { 33 , 255 , 11 , 78 , 69 , 84 , 83 , 67 , 65 , 80 , 69 , 50 , 46 , 48 , 3 , 1 , 0 , 0 , 0 } ;
196- var newBytes = new List < byte > ( ) ;
197- newBytes . AddRange ( fileBytes . Take ( 13 ) ) ;
198- newBytes . AddRange ( applicationExtension ) ;
199- newBytes . AddRange ( fileBytes . Skip ( 13 ) ) ;
200- File . WriteAllBytes ( target , newBytes . ToArray ( ) ) ;
229+ SaveWithLoopingExtension ( gEnc , target ) ;
201230 }
202231
203232 /// <summary>
204- /// Creates the GIF.
233+ /// Create the gif with variable per-frame delay using Tuples.
234+ /// </summary>
235+ /// <param name="frames">The frames.</param>
236+ /// <param name="target">The target.</param>
237+ private static void GifCreator ( IEnumerable < ( Bitmap Image , int DelayMs ) > frames , string target )
238+ {
239+ var gEnc = new GifBitmapEncoder ( ) ;
240+
241+ foreach ( var frame in frames )
242+ {
243+ IntPtr hBitmap = frame . Image . GetHbitmap ( ) ;
244+ try
245+ {
246+ var src = System . Windows . Interop . Imaging . CreateBitmapSourceFromHBitmap (
247+ hBitmap ,
248+ IntPtr . Zero ,
249+ Int32Rect . Empty ,
250+ BitmapSizeOptions . FromEmptyOptions ( ) ) ;
251+
252+ var metadata = new BitmapMetadata ( "gif" ) ;
253+ metadata . SetQuery ( "/grctlext/Delay" , ( ushort ) ( frame . DelayMs / 10 ) ) ;
254+
255+ gEnc . Frames . Add ( BitmapFrame . Create ( src , null , metadata , null ) ) ;
256+ }
257+ finally
258+ {
259+ DeleteObject ( hBitmap ) ;
260+ }
261+ }
262+
263+ SaveWithLoopingExtension ( gEnc , target ) ;
264+ }
265+
266+ /// <summary>
267+ /// Creates the GIF from FrameInfo.
205268 /// </summary>
206269 /// <param name="frames">The frames.</param>
207270 /// <param name="target">The target.</param>
@@ -213,28 +276,61 @@ private static void GifCreator(IEnumerable<FrameInfo>? frames, string target)
213276
214277 foreach ( var frameInfo in frames )
215278 {
216- var bitmapSource = BitmapFrame . Create (
217- BitmapSource . Create (
218- frameInfo . Image . Width ,
219- frameInfo . Image . Height ,
220- 96 , 96 , // DPI
221- PixelFormats . Bgra32 , // Or appropriate format
222- null , // No palette for Bgra32
223- frameInfo . Image . LockBits ( new Rectangle ( 0 , 0 , frameInfo . Image . Width , frameInfo . Image . Height ) ,
224- ImageLockMode . ReadOnly ,
225- PixelFormat . Format32bppArgb ) . Scan0 ,
226- frameInfo . Image . Height * frameInfo . Image . Width * 4 , // Image byte size
227- frameInfo . Image . Width * 4 ) ) ; // Bytes per row
228-
229- var metadata = new BitmapMetadata ( ImagingResources . GifMetadata ) ;
230- metadata . SetQuery ( ImagingResources . GifMetadataQueryDelay ,
231- ( ushort ) ( frameInfo . DelayTime * 100 ) ) ; // Delay in hundredths of seconds
232-
233- gEnc . Frames . Add ( BitmapFrame . Create ( bitmapSource , null , metadata , null ) ) ;
279+ var img = frameInfo . Image ;
280+
281+ var bmpData = img . LockBits (
282+ new Rectangle ( 0 , 0 , img . Width , img . Height ) ,
283+ ImageLockMode . ReadOnly ,
284+ PixelFormat . Format32bppArgb ) ;
285+
286+ try
287+ {
288+ var bitmapSource = BitmapSource . Create (
289+ img . Width ,
290+ img . Height ,
291+ img . HorizontalResolution ,
292+ img . VerticalResolution ,
293+ PixelFormats . Bgra32 ,
294+ null ,
295+ bmpData . Scan0 ,
296+ img . Height * bmpData . Stride ,
297+ bmpData . Stride ) ;
298+
299+ var metadata = new BitmapMetadata ( ImagingResources . GifMetadata ) ;
300+ metadata . SetQuery ( ImagingResources . GifMetadataQueryDelay , ( ushort ) ( frameInfo . DelayTime * 100 ) ) ;
301+
302+ gEnc . Frames . Add ( BitmapFrame . Create ( bitmapSource , null , metadata , null ) ) ;
303+ }
304+ finally
305+ {
306+ img . UnlockBits ( bmpData ) ;
307+ }
234308 }
235309
236310 using var fs = new FileStream ( target , FileMode . Create , FileAccess . Write ) ;
237311 gEnc . Save ( fs ) ;
238312 }
313+
314+ /// <summary>
315+ /// Helper method to inject the Netscape looping extension.
316+ /// </summary>
317+ /// <param name="gEnc">The g enc.</param>
318+ /// <param name="target">The target.</param>
319+ private static void SaveWithLoopingExtension ( GifBitmapEncoder gEnc , string target )
320+ {
321+ using var ms = new MemoryStream ( ) ;
322+ gEnc . Save ( ms ) ;
323+ var fileBytes = ms . ToArray ( ) ;
324+
325+ // This is the NETSCAPE2.0 Application Extension.
326+ var applicationExtension = new byte [ ] { 33 , 255 , 11 , 78 , 69 , 84 , 83 , 67 , 65 , 80 , 69 , 50 , 46 , 48 , 3 , 1 , 0 , 0 , 0 } ;
327+
328+ var newBytes = new List < byte > ( fileBytes . Length + applicationExtension . Length ) ;
329+ newBytes . AddRange ( fileBytes . Take ( 13 ) ) ;
330+ newBytes . AddRange ( applicationExtension ) ;
331+ newBytes . AddRange ( fileBytes . Skip ( 13 ) ) ;
332+
333+ File . WriteAllBytes ( target , newBytes . ToArray ( ) ) ;
334+ }
239335 }
240336}
0 commit comments