1 /++
2  + Authors: Avaxar <avaxar@nekkl.org>
3  + Copyright: Copyright © 2023, Avaxar
4  + License: $(LINK2 https://mit-license.org, MIT License)
5  +/
6 
7 module dsdl2.image;
8 @safe:
9 
10 // dfmt off
11 import bindbc.sdl;
12 static if (bindSDLImage):
13 // dfmt on
14 
15 import dsdl2.sdl;
16 import dsdl2.renderer;
17 import dsdl2.surface;
18 import dsdl2.texture;
19 
20 import core.memory : GC;
21 import std.conv : to;
22 import std.format : format;
23 import std.string : toStringz;
24 
25 version (BindSDL_Static) {
26 }
27 else {
28     /++
29      + Loads the SDL2_image shared dynamic library, which wraps bindbc-sdl's `loadSDLImage` function
30      +
31      + Unless if bindbc-sdl is on static mode (by adding a `BindSDL_Static` version), this function will exist and must
32      + be called before any calls are made to the library. Otherwise, a segfault will happen upon any function calls.
33      +
34      + Params:
35      +   libName = name or path to look the SDL2_image SO/DLL for, otherwise `null` for default searching path
36      + Throws: `dsdl2.SDLException` if failed to find the library
37      +/
38     void loadSO(string libName = null) @trusted {
39         SDLImageSupport current = libName is null ? loadSDLImage() : loadSDLImage(libName.toStringz());
40         if (current == sdlImageSupport) {
41             return;
42         }
43 
44         Version wanted = Version(sdlImageSupport);
45         if (current == SDLImageSupport.badLibrary) {
46             import std.stdio : writeln;
47 
48             writeln("WARNING: dsdl2 expects SDL_image ", wanted.format(), ", but got ", getVersion().format(), ".");
49         }
50         else if (current == SDLImageSupport.noLibrary) {
51             throw new SDLException("No SDL2_image library found, especially of version " ~ wanted.format(),
52                 __FILE__, __LINE__);
53         }
54     }
55 }
56 
57 /++
58  + Wraps `IMG_Init` which initializes selected SDL2_image image format subsystems
59  +
60  + Params:
61  +   jpg = selects the `IMG_INIT_JPG` subsystem
62  +   png = selects the `IMG_INIT_PNG` subsystem
63  +   tif = selects the `IMG_INIT_TIF` subsystem
64  +   webp = selects the `IMG_INIT_WEBP` subsystem
65  +   jxl = selects the `IMG_INIT_JXL` subsystem (from SDL_image 2.6)
66  +   avif = selects the `IMG_INIT_AVIF` subsystem (from SDL_image 2.6)
67  +   everything = selects every available subsystem
68  + Throws: `dsdl2.SDLException` if any selected subsystem failed to initialize
69  + Example:
70  + ---
71  + dsdl2.image.init(everything : true);
72  + ---
73  +/
74 void init(bool jpg = false, bool png = false, bool tif = false, bool webp = false, bool jxl = false, bool avif = false,
75     bool everything = false) @trusted
76 in {
77     static if (sdlImageSupport < SDLImageSupport.v2_6) {
78         assert(jxl == false);
79         assert(avif == false);
80     }
81     else {
82         if (jxl || avif) {
83             assert(dsdl2.image.getVersion() >= Version(2, 6));
84         }
85     }
86 }
87 do {
88     int flags = 0;
89 
90     flags |= jpg ? IMG_INIT_JPG : 0;
91     flags |= png ? IMG_INIT_PNG : 0;
92     flags |= tif ? IMG_INIT_TIF : 0;
93     flags |= webp ? IMG_INIT_WEBP : 0;
94     flags |= everything ? IMG_INIT_JPG | IMG_INIT_PNG | IMG_INIT_TIF | IMG_INIT_WEBP : 0;
95 
96     static if (sdlImageSupport >= SDLImageSupport.v2_6) {
97         flags |= jxl ? IMG_INIT_JXL : 0;
98         flags |= avif ? IMG_INIT_AVIF : 0;
99         flags |= everything ? IMG_INIT_JXL | IMG_INIT_AVIF : 0;
100     }
101 
102     if ((IMG_Init(flags) & flags) != flags) {
103         throw new SDLException;
104     }
105 }
106 
107 version (unittest) {
108     static this() {
109         version (BindSDL_Static) {
110         }
111         else {
112             dsdl2.image.loadSO();
113         }
114 
115         dsdl2.image.init(everything : true);
116     }
117 }
118 
119 /++
120  + Wraps `IMG_Quit` which entirely deinitializes SDL2_image
121  +/
122 void quit() @trusted {
123     IMG_Quit();
124 }
125 
126 version (unittest) {
127     static ~this() {
128         dsdl2.image.quit();
129     }
130 }
131 
132 /++
133  + Wraps `IMG_Linked_version` which gets the version of the linked SDL2_image library
134  +
135  + Returns: `dsdl2.Version` of the linked SDL2_image library
136  +/
137 Version getVersion() @trusted {
138     return Version(*IMG_Linked_Version());
139 }
140 
141 /++
142  + Wraps `IMG_Load` which loads an image from a filesystem path into a software `dsdl2.Surface`
143  +
144  + Params:
145  +   file = path to the image file
146  + Returns: `dsdl2.Surface` of the loaded image
147  + Throws: `dsdl2.SDLException` if failed to load the image
148  +/
149 Surface load(string file) @trusted {
150     if (SDL_Surface* sdlSurface = IMG_Load(file.toStringz())) {
151         return new Surface(sdlSurface);
152     }
153     else {
154         throw new SDLException;
155     }
156 }
157 
158 /++
159  + Wraps `IMG_Load_RW` which loads an image from a data buffer into a software `dsdl2.Surface`
160  +
161  + Params:
162  +   data = data buffer of the image
163  + Returns: `dsdl2.Surface` of the loaded image
164  + Throws: `dsdl2.SDLException` if failed to load the image
165  +/
166 Surface loadRaw(const void[] data) @trusted {
167     SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
168     if (sdlRWops is null) {
169         throw new SDLException;
170     }
171 
172     if (SDL_Surface* sdlSurface = IMG_Load_RW(sdlRWops, 1)) {
173         return new Surface(sdlSurface);
174     }
175     else {
176         throw new SDLException;
177     }
178 }
179 
180 /++
181  + Wraps `IMG_LoadTyped_RW` which loads a typed image from a data buffer into a software `dsdl2.Surface`
182  +
183  + Params:
184  +   data = data buffer of the image
185  +   type = specified type of the image
186  + Returns: `dsdl2.Surface` of the loaded image
187  + Throws: `dsdl2.SDLException` if failed to load the image
188  +/
189 Surface loadRaw(const void[] data, string type) @trusted {
190     SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
191     if (sdlRWops is null) {
192         throw new SDLException;
193     }
194 
195     if (SDL_Surface* sdlSurface = IMG_LoadTyped_RW(sdlRWops, 1, type.toStringz())) {
196         return new Surface(sdlSurface);
197     }
198     else {
199         throw new SDLException;
200     }
201 }
202 
203 /++
204  + Wraps `IMG_LoadTexture` which loads an image from a filesystem path into a hardware `dsdl2.Texture`
205  +
206  + Params:
207  +   renderer = given `dsdl2.Renderer` to initialize the texture
208  +   file = path to the image file
209  + Returns: `dsdl2.Texture` of the loaded image
210  + Throws: `dsdl2.SDLException` if failed to load the image
211  +/
212 Texture loadTexture(Renderer renderer, string file) @trusted
213 in {
214     assert(renderer !is null);
215 }
216 do {
217     if (SDL_Texture* sdlTexture = IMG_LoadTexture(renderer.sdlRenderer, file.toStringz())) {
218         return new Texture(sdlTexture);
219     }
220     else {
221         throw new SDLException;
222     }
223 }
224 
225 /++
226  + Wraps `IMG_LoadTexture_RW` which loads an image from a data buffer into a hardware `dsdl2.Texture`
227  +
228  + Params:
229  +   renderer = given `dsdl2.Renderer` to initialize the texture
230  +   data = data buffer of the image
231  + Returns: `dsdl2.Texture` of the loaded image
232  + Throws: `dsdl2.SDLException` if failed to load the image
233  +/
234 Texture loadTextureRaw(Renderer renderer, const void[] data) @trusted
235 in {
236     assert(renderer !is null);
237 }
238 do {
239     SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
240     if (sdlRWops is null) {
241         throw new SDLException;
242     }
243 
244     if (SDL_Texture* sdlTexture = IMG_LoadTexture_RW(renderer.sdlRenderer, sdlRWops, 1)) {
245         return new Texture(sdlTexture);
246     }
247     else {
248         throw new SDLException;
249     }
250 }
251 
252 /++
253  + Wraps `IMG_LoadTextureTyped_RW` which loads a typed image from a data buffer into a hardware `dsdl2.Texture`
254  +
255  + Params:
256  +   renderer = given `dsdl2.Renderer` to initialize the texture
257  +   data = data buffer of the image
258  +   type = specified type of the image
259  + Returns: `dsdl2.Texture` of the loaded image
260  + Throws: `dsdl2.SDLException` if failed to load the image
261  +/
262 Texture loadTextureRaw(Renderer renderer, const void[] data, string type) @trusted
263 in {
264     assert(renderer !is null);
265 }
266 do {
267     SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
268     if (sdlRWops is null) {
269         throw new SDLException;
270     }
271 
272     if (SDL_Texture* sdlTexture = IMG_LoadTextureTyped_RW(renderer.sdlRenderer, sdlRWops, 1, type.toStringz())) {
273         return new Texture(sdlTexture);
274     }
275     else {
276         throw new SDLException;
277     }
278 }
279 
280 bool _isType(alias func, ubyte minMinorVer = 0, ubyte minPatchVer = 0)(const void[] data) @trusted
281 in {
282     assert(dsdl2.image.getVersion() >= Version(2, minMinorVer, minPatchVer));
283 }
284 do {
285     SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
286     if (sdlRWops is null) {
287         throw new SDLException;
288     }
289     scope (exit)
290         if (SDL_RWclose(sdlRWops) != 0) {
291             throw new SDLException;
292         }
293 
294     return func(sdlRWops) != 0;
295 }
296 
297 alias isICO = _isType!IMG_isICO; /// Wraps `IMG_isICO` which checks whether `data` is an ICO file
298 alias isCUR = _isType!IMG_isCUR; /// Wraps `IMG_isCUR` which checks whether `data` is a CUR file
299 alias isBMP = _isType!IMG_isBMP; /// Wraps `IMG_isBMP` which checks whether `data` is a BMP file
300 alias isGIF = _isType!IMG_isGIF; /// Wraps `IMG_isGIF` which checks whether `data` is a GIF file
301 alias isJPG = _isType!IMG_isJPG; /// Wraps `IMG_isJPG` which checks whether `data` is a JPG file
302 alias isLBM = _isType!IMG_isLBM; /// Wraps `IMG_isLBM` which checks whether `data` is a LBM file
303 alias isPCX = _isType!IMG_isPCX; /// Wraps `IMG_isPCX` which checks whether `data` is a PCX file
304 alias isPNG = _isType!IMG_isPNG; /// Wraps `IMG_isPNG` which checks whether `data` is a PNG file
305 alias isPNM = _isType!IMG_isPNM; /// Wraps `IMG_isPNM` which checks whether `data` is a PNM file
306 alias isTIF = _isType!IMG_isTIF; /// Wraps `IMG_isTIF` which checks whether `data` is a TIF file
307 alias isXCF = _isType!IMG_isXCF; /// Wraps `IMG_isXCF` which checks whether `data` is an XCF file
308 alias isXPM = _isType!IMG_isXPM; /// Wraps `IMG_isXPM` which checks whether `data` is an XPM file
309 alias isXV = _isType!IMG_isXV; /// Wraps `IMG_isXV` which checks whether `data` is an XV file
310 alias isWEBP = _isType!IMG_isWEBP; /// Wraps `IMG_isWEBP` which checks whether `data` is a WEBP file
311 
312 static if (sdlImageSupport >= SDLImageSupport.v2_0_2) {
313     alias isSVG = _isType!(IMG_isSVG, 0, 2); /// Wraps `IMG_isSVG` which checks whether `data` is a SVG file (from SDL_image 2.0.2)
314 }
315 
316 static if (sdlImageSupport >= SDLImageSupport.v2_6) {
317     alias isAVIF = _isType!(IMG_isAVIF, 6); /// Wraps `IMG_isAVIF` which checks whether `data` is an AVIF file (from SDL_image 2.6)
318     alias isJXL = _isType!(IMG_isJXL, 6); /// Wraps `IMG_isJXL` which checks whether `data` is a JXL file (from SDL_image 2.6)
319     alias isQOI = _isType!(IMG_isQOI, 6); /// Wraps `IMG_isQOI` which checks whether `data` is a QOI file (from SDL_image 2.6)
320 }
321 
322 Surface _loadTypeRaw(alias func, ubyte minMinorVer = 0, ubyte minPatchVer = 0)(const void[] data) @trusted
323 in {
324     assert(dsdl2.image.getVersion() >= Version(2, minMinorVer, minPatchVer));
325 }
326 do {
327     SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
328     if (sdlRWops is null) {
329         throw new SDLException;
330     }
331     scope (exit)
332         if (SDL_RWclose(sdlRWops) != 0) {
333             throw new SDLException;
334         }
335 
336     if (SDL_Surface* sdlSurface = func(sdlRWops)) {
337         return new Surface(sdlSurface);
338     }
339     else {
340         throw new SDLException;
341     }
342 }
343 
344 alias loadICORaw = _loadTypeRaw!IMG_LoadICO_RW; /// Wraps `IMG_LoadICO_RW` which loads `data` as an ICO image to surface
345 alias loadCURRaw = _loadTypeRaw!IMG_LoadCUR_RW; /// Wraps `IMG_LoadCUR_RW` which loads `data` as a CUR image to surface
346 alias loadBMPRaw = _loadTypeRaw!IMG_LoadBMP_RW; /// Wraps `IMG_LoadBMP_RW` which loads `data` as a BMP image to surface
347 alias loadGIFRaw = _loadTypeRaw!IMG_LoadGIF_RW; /// Wraps `IMG_LoadGIF_RW` which loads `data` as a GIF image to surface
348 alias loadJPGRaw = _loadTypeRaw!IMG_LoadJPG_RW; /// Wraps `IMG_LoadJPG_RW` which loads `data` as a JPG image to surface
349 alias loadLBRaw = _loadTypeRaw!IMG_LoadLBM_RW; /// Wraps `IMG_LoadLBM_RW` which loads `data` as a LBM image to surface
350 alias loadPCXRaw = _loadTypeRaw!IMG_LoadPCX_RW; /// Wraps `IMG_LoadPCX_RW` which loads `data` as a PCX image to surface
351 alias loadPNGRaw = _loadTypeRaw!IMG_LoadPNG_RW; /// Wraps `IMG_LoadPNG_RW` which loads `data` as a PNG image to surface
352 alias loadPNMRaw = _loadTypeRaw!IMG_LoadPNM_RW; /// Wraps `IMG_LoadPNM_RW` which loads `data` as a PNM image to surface
353 alias loadTIFRaw = _loadTypeRaw!IMG_LoadTIF_RW; /// Wraps `IMG_LoadTIF_RW` which loads `data` as a TIF image to surface
354 alias loadXCFRaw = _loadTypeRaw!IMG_LoadXCF_RW; /// Wraps `IMG_LoadXCF_RW` which loads `data` as an XCF image to surface
355 alias loadXPMRaw = _loadTypeRaw!IMG_LoadXPM_RW; /// Wraps `IMG_LoadXPM_RW` which loads `data` as an XPM image to surface
356 alias loadXVRaw = _loadTypeRaw!IMG_LoadXV_RW; /// Wraps `IMG_LoadXV_RW` which loads `data` as an XV image as surface
357 alias loadWEBPRaw = _loadTypeRaw!IMG_LoadWEBP_RW; /// Wraps `IMG_LoadWEBP_RW` which loads `data` as a WEBP image to surface
358 
359 static if (sdlImageSupport >= SDLImageSupport.v2_0_2) {
360     alias loadSVGRaw = _loadTypeRaw!(IMG_LoadSVG_RW, 0, 2); /// Wraps `IMG_LoadSVG_RW` which loads `data` as a SVG image (from SDL_image 2.0.2)
361 }
362 
363 static if (sdlImageSupport >= SDLImageSupport.v2_6) {
364     alias loadAVIFRaw = _loadTypeRaw!(IMG_LoadAVIF_RW, 6); /// Wraps `IMG_LoadAVIF_RW` which loads `data` as an AVIF image (from SDL_image 2.6)
365     alias loadJXLRaw = _loadTypeRaw!(IMG_LoadJXL_RW, 6); /// Wraps `IMG_LoadJXL_RW` which loads `data` as a JXL image (from SDL_image 2.6)
366     alias loadQOIRaw = _loadTypeRaw!(IMG_LoadQOI_RW, 6); /// Wraps `IMG_LoadQOI_RW` which loads `data` as a QOI image (from SDL_image 2.6)
367 
368     /++
369      + Wraps `IMG_LoadSizedSVG_RW` (from SDL_image 2.6) which loads a `dsdl2.Surface` image from a buffer of SVG file
370      + format, while providing the desired flattened size of the vector image
371      +
372      + Params:
373      +   data = data buffer of the image
374      +   size = desized size in pixels (width and height) of the flattened SVG image; `0` to either one of the
375      +          dimensions to be adjusted for aspect ratio
376      + Returns: `dsdl2.Surface` of the loaded image
377      + Throws: `dsdl2.SDLException` if failed to load
378      +/
379     Surface loadSizedSVGRaw(const void[] data, uint[2] size) @trusted
380     in {
381         assert(dsdl2.image.getVersion() >= Version(2, 6));
382     }
383     do {
384         SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
385         if (sdlRWops is null) {
386             throw new SDLException;
387         }
388         scope (exit)
389             if (SDL_RWclose(sdlRWops) != 0) {
390                 throw new SDLException;
391             }
392 
393         if (SDL_Surface* sdlSurface = IMG_LoadSizedSVG_RW(sdlRWops, size[0].to!int, size[1].to!int)) {
394             return new Surface(sdlSurface);
395         }
396         else {
397             throw new SDLException;
398         }
399     }
400 }
401 
402 /++
403  + Wraps `IMG_SavePNG` which saves a `dsdl2.Surface` image into a PNG file in the filesystem
404  +
405  + Params:
406  +   surface = given `dsdl2.Surface` of the image to save
407  +   file = target file path to save
408  + Throws: `dsdl2.SDLException` if failed to save
409  +/
410 void savePNG(Surface surface, string file) @trusted
411 in {
412     assert(surface !is null);
413 }
414 do {
415     if (IMG_SavePNG(surface.sdlSurface, file.toStringz()) != 0) {
416         throw new SDLException;
417     }
418 }
419 
420 /++
421  + Wraps `IMG_SavePNG_RW` which saves a `dsdl2.Surface` image into a buffer of PNG file format
422  +
423  + Params:
424  +   surface = given `dsdl2.Surface` of the image to save
425  + Returns: `dsdl2.SDLException` if failed to save
426  +/
427 void[] savePNGRaw(Surface surface) @trusted
428 in {
429     assert(surface !is null);
430 }
431 do {
432     // TODO: make a writable `SDL_RWops` to dynamic memory
433     assert(false, "Not implemented");
434 }
435 
436 static if (sdlImageSupport >= SDLImageSupport.v2_0_2) {
437     /++
438      + Wraps `IMG_SaveJPG` (from SDL_image 2.0.2) which saves a `dsdl2.Surface` image into a JPG file in the filesystem
439      +
440      + Params:
441      +   surface = given `dsdl2.Surface` of the image to save
442      +   file = target file path to save
443      +   quality = value ranging from `0` to `100` specifying the image quality compensating for compression
444      + Throws: `dsdl2.SDLException` if failed to save
445      +/
446     void saveJPG(Surface surface, string file, ubyte quality) @trusted
447     in {
448         assert(dsdl2.image.getVersion() >= Version(2, 0, 2));
449         assert(surface !is null);
450     }
451     do {
452         if (IMG_SaveJPG(surface.sdlSurface, file.toStringz(), quality) != 0) {
453             throw new SDLException;
454         }
455     }
456 
457     /++
458      + Wraps `IMG_SaveJPG_RW` (from SDL_image 2.0.2) which saves a `dsdl2.Surface` image into a buffer of JPG file format
459      +
460      + Params:
461      +   surface = given `dsdl2.Surface` of the image to save
462      +   quality = value ranging from `0` to `100` specifying the image quality compensating for compression
463      + Returns: `dsdl2.SDLException` if failed to save
464      +/
465     void[] saveJPGRaw(Surface surface, ubyte quality) @trusted
466     in {
467         assert(dsdl2.image.getVersion() >= Version(2, 0, 2));
468         assert(surface !is null);
469     }
470     do {
471         // TODO: make a writable `SDL_RWops` to dynamic memory
472         assert(false, "Not implemented");
473     }
474 }
475 
476 static if (sdlImageSupport >= SDLImageSupport.v2_6) {
477     /++
478      + D class that wraps `IMG_Animation` (from SDL_image 2.6) storing multiple `dsdl2.Surface`s of an animation
479      +/
480     class Animation {
481         private Surface[] framesProxy = null;
482         private bool isOwner = true;
483         private void* userRef = null;
484 
485         @system IMG_Animation* imgAnimation = null; /// Internal `IMG_Animation` pointer
486 
487         /++
488          + Constructs a `dsdl2.image.Animation` from a vanilla `IMG_Animation*` from bindbc-sdl
489          +
490          + Params:
491          +   imgAnimation = the `IMG_Animation` pointer to manage
492          +   isOwner = whether the instance owns the given `IMG_Animation*` and should destroy it on its own
493          +   userRef = optional pointer to maintain reference link, avoiding GC cleanup
494          +/
495         this(IMG_Animation* imgAnimation, bool isOwner = true, void* userRef = null) @system
496         in {
497             assert(imgAnimation !is null);
498         }
499         do {
500             this.imgAnimation = imgAnimation;
501             this.isOwner = isOwner;
502             this.userRef = userRef;
503         }
504 
505         ~this() @trusted {
506             if (this.isOwner) {
507                 IMG_FreeAnimation(this.imgAnimation);
508             }
509         }
510 
511         @trusted invariant { // @suppress(dscanner.trust_too_much)
512             // Instance might be in an invalid state due to holding a non-owned externally-freed object when
513             // destructed in an unpredictable order.
514             if (!this.isOwner && GC.inFinalizer) {
515                 return;
516             }
517 
518             assert(this.imgAnimation !is null);
519         }
520 
521         /++
522          + Equality operator overload
523          +/
524         bool opEquals(const Animation rhs) const @trusted {
525             return this.imgAnimation is rhs.imgAnimation;
526         }
527 
528         /++
529          + Gets the hash of the `dsdl2.image.Animation`
530          +
531          + Returns: unique hash for the instance being the pointer of the internal `IMG_Animation` pointer
532          +/
533         override hash_t toHash() const @trusted {
534             return cast(hash_t) this.imgAnimation;
535         }
536 
537         /++
538          + Formats the `dsdl2.image.Animation` into its construction representation:
539          + `"dsdl2.image.Animation(<imgAnimation>)"`
540          +
541          + Returns: the formatted `string`
542          +/
543         override string toString() const @trusted {
544             return "dsdl2.image.Animation(0x%x)".format(this.imgAnimation);
545         }
546 
547         /++
548          + Gets the width of the `dsdl2.image.Animation` in pixels
549          +
550          + Returns: width of the `dsdl2.image.Animation` in pixels
551          +/
552         uint width() const @property @trusted {
553             return this.imgAnimation.w.to!uint;
554         }
555 
556         /++
557          + Gets the height of the `dsdl2.image.Animation` in pixels
558          +
559          + Returns: height of the `dsdl2.image.Animation` in pixels
560          +/
561         uint height() const @property @trusted {
562             return this.imgAnimation.h.to!uint;
563         }
564 
565         /++
566          + Gets the size of the `dsdl2.image.Animation` in pixels
567          +
568          + Returns: size of the `dsdl2.image.Animation` in pixels
569          +/
570         uint[2] size() const @property @trusted {
571             return [this.imgAnimation.w.to!uint, this.imgAnimation.h.to!uint];
572         }
573 
574         /++
575          + Gets the frame count of the `dsdl2.image.Animation`
576          +
577          + Returns: frame count of the `dsdl2.image.Animation`
578          +/
579         size_t count() const @property @trusted {
580             return cast(size_t) this.imgAnimation.count;
581         }
582 
583         /++
584          + Gets an array of `dsdl2.Surface` frames of the `dsdl2.image.Animation`
585          +
586          + Returns: array of `dsdl2.Surface` frames of the `dsdl2.image.Animation`
587          +/
588         const(Surface[]) frames() const @property @trusted {
589             if (this.framesProxy is null) {
590                 (cast(Animation) this).framesProxy = new Surface[this.count];
591                 foreach (i; 0 .. this.count) {
592                     (cast(Animation) this).framesProxy[i] = new Surface(
593                         cast(SDL_Surface*) this.imgAnimation.frames[i], false, cast(void*) this);
594                 }
595             }
596 
597             return this.framesProxy;
598         }
599 
600         /++
601          + Gets an array of delay per frame of the `dsdl2.image.Animation`
602          +
603          + Returns: array of delay per frame of the `dsdl2.image.Animation`
604          +/
605         const(uint[]) delays() const @property @trusted {
606             return (cast(uint*)&this.imgAnimation.delays)[0 .. this.count];
607         }
608     }
609 
610     /++
611      + Wraps `IMG_LoadAnimation` (from SDL_image 2.6) which loads an animation from a filesystem path into an
612      + `dsdl2.image.Animation`
613      +
614      + Params:
615      +   file = path to the animation file
616      + Returns: `dsdl2.image.Animation` of the loaded animated image
617      + Throws: `dsdl2.SDLException` if failed to load the animation
618      +/
619     Animation loadAnimation(string file) @trusted
620     in {
621         assert(dsdl2.image.getVersion() >= Version(2, 6));
622     }
623     do {
624         if (IMG_Animation* imgAnimation = IMG_LoadAnimation(file.toStringz())) {
625             return new Animation(imgAnimation);
626         }
627         else {
628             throw new SDLException;
629         }
630     }
631 
632     /++
633      + Wraps `IMG_LoadAnimation_RW` (from SDL_image 2.6) which loads an animation from a data buffer into a
634      + `dsdl2.image.Animation`
635      +
636      + Params:
637      +   data = data buffer of the animation
638      + Returns: `dsdl2.image.Animation` of the loaded animated image
639      + Throws: `dsdl2.SDLException` if failed to load the animation
640      +/
641     Animation loadAnimationRaw(const void[] data) @trusted
642     in {
643         assert(dsdl2.image.getVersion() >= Version(2, 6));
644     }
645     do {
646         SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
647         if (sdlRWops is null) {
648             throw new SDLException;
649         }
650 
651         if (IMG_Animation* imgAnimation = IMG_LoadAnimation_RW(sdlRWops, 1)) {
652             return new Animation(imgAnimation);
653         }
654         else {
655             throw new SDLException;
656         }
657     }
658 
659     /++
660      + Wraps `IMG_LoadAnimationTyped_RW` (from SDL_image 2.6) which loads a typed image from a data buffer into a
661      + `dsdl2.image.Animation`
662      +
663      + Params:
664      +   data = data buffer of the animation
665      +   type = specified type of the animation
666      + Returns: `dsdl2.image.Animation` of the loaded animated image
667      + Throws: `dsdl2.SDLException` if failed to load the animation
668      +/
669     Animation loadAnimationTypedRaw(const void[] data, string type) @trusted
670     in {
671         assert(dsdl2.image.getVersion() >= Version(2, 6));
672     }
673     do {
674         SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
675         if (sdlRWops is null) {
676             throw new SDLException;
677         }
678 
679         if (IMG_Animation* imgAnimation = IMG_LoadAnimationTyped_RW(sdlRWops, 1, type.toStringz())) {
680             return new Animation(imgAnimation);
681         }
682         else {
683             throw new SDLException;
684         }
685     }
686 
687     /++
688      + Wraps `IMG_LoadGIFAnimation_RW` (from SDL_image 2.6) which loads a `dsdl2.image.Animation` from a buffer of
689      + animated GIF file format
690      +
691      + Params:
692      +   data = data buffer of the animation
693      + Returns: `dsdl2.image.Animation` of the animated GIF
694      + Throws: `dsdl2.SDLException` if failed to load
695      +/
696     Animation loadGIFAnimationRaw(const void[] data) @trusted
697     in {
698         assert(dsdl2.image.getVersion() >= Version(2, 6));
699     }
700     do {
701         SDL_RWops* sdlRWops = SDL_RWFromConstMem(data.ptr, data.length.to!int);
702         if (sdlRWops is null) {
703             throw new SDLException;
704         }
705         scope (exit)
706             if (SDL_RWclose(sdlRWops) != 0) {
707                 throw new SDLException;
708             }
709 
710         if (IMG_Animation* imgAnimation = IMG_LoadGIFAnimation_RW(sdlRWops)) {
711             return new Animation(imgAnimation);
712         }
713         else {
714             throw new SDLException;
715         }
716     }
717 }