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.surface;
8 @safe:
9 
10 import bindbc.sdl;
11 import dsdl2.sdl;
12 import dsdl2.blend;
13 import dsdl2.pixels;
14 import dsdl2.rect;
15 
16 import core.memory : GC;
17 import std.conv : to;
18 import std.format : format;
19 import std.typecons : Nullable, nullable;
20 
21 /++
22  + D class that wraps `SDL_Surface` storing a 2D image in the RAM
23  +
24  + `dsdl2.Surface` stores a 2D image out of pixels with a `width` and `height`, where each pixel stored in the
25  + RAM according to its defined `dsdl2.PixelFormat`.
26  +/
27 final class Surface {
28     private PixelFormat pixelFormatProxy = null;
29     private bool isOwner = true;
30     private void* userRef = null;
31 
32     @system SDL_Surface* sdlSurface = null; /// Internal `SDL_Surface` pointer
33 
34     /++
35      + Constructs a `dsdl2.Surface` from a vanilla `SDL_Surface*` from bindbc-sdl
36      +
37      + Params:
38      +   sdlSurface = the `SDL_Surface` pointer to manage
39      +   isOwner = whether the instance owns the given `SDL_Surface*` and should destroy it on its own
40      +   userRef = optional pointer to maintain reference link, avoiding GC cleanup
41      +/
42     this(SDL_Surface* sdlSurface, bool isOwner = true, void* userRef = null) @system
43     in {
44         assert(sdlSurface !is null);
45     }
46     do {
47         this.sdlSurface = sdlSurface;
48         this.isOwner = isOwner;
49         this.userRef = userRef;
50     }
51 
52     /++
53      + Constructs a blank RGB(A) `dsdl2.Surface` with a set width, height, and `dsdl2.PixelFormat` that wraps
54      + `SDL_CreateRGBSurface`
55      +
56      + Params:
57      +   size = size (width and height) of the `dsdl2.Surface` in pixels
58      +   rgbPixelFormat = an RGB(A) `dsdl2.PixelFormat`
59      + Throws: `dsdl2.SDLException` if allocation failed
60      +/
61     this(uint[2] size, const PixelFormat rgbPixelFormat) @trusted
62     in {
63         assert(rgbPixelFormat !is null);
64         assert(!rgbPixelFormat.indexed);
65     }
66     do {
67         uint[4] masks = rgbPixelFormat.toMasks();
68 
69         this.sdlSurface = SDL_CreateRGBSurface(0, size[0].to!int, size[1].to!int, rgbPixelFormat.bitsPerPixel,
70             masks[0], masks[1], masks[2], masks[3]);
71         if (this.sdlSurface is null) {
72             throw new SDLException;
73         }
74     }
75 
76     /++
77      + Constructs an RGB(A) `dsdl2.Surface` from an array of `pixels`
78      +
79      + Params:
80      +   pixels = array of pixel data (copied internally)
81      +   size = size (width and height) of the `dsdl2.Surface` in pixels
82      +   pitch = skips in bytes per line/row of the `dsdl2.Surface`
83      +   rgbPixelFormat = an RGB(A) `dsdl2.PixelFormat`
84      + Throws: `dsdl2.SDLException` if allocation failed
85      +/
86     this(const void[] pixels, uint[2] size, size_t pitch, const PixelFormat rgbPixelFormat) @trusted
87     in {
88         assert(pixels !is null);
89         assert(rgbPixelFormat !is null);
90         assert(!rgbPixelFormat.indexed);
91         assert(pitch * 8 >= size[0] * rgbPixelFormat.bitsPerPixel);
92         assert(pixels.length == pitch * size[1]);
93     }
94     do {
95         this(size, rgbPixelFormat);
96 
97         size_t lineBitSize = cast(size_t) size[0] * rgbPixelFormat.bitsPerPixel;
98         size_t lineSize = lineBitSize / 8 + (lineBitSize % 8 != 0);
99 
100         foreach (line; 0 .. size[1]) {
101             ubyte* srcLine = cast(ubyte*) pixels.ptr + line * pitch;
102             ubyte* destLine = cast(ubyte*) this.sdlSurface.pixels + line * this.pitch;
103             destLine[0 .. lineSize] = srcLine[0 .. lineSize];
104         }
105     }
106 
107     /++
108      + Constructs a blank indexed palette-using `dsdl2.Surface` with a set width, height, and index bit depth,
109      + which wraps `SDL_CreateRGBSurface`
110      +
111      + Params:
112      +   size = size (width and height) of the `dsdl2.Surface` in pixels
113      +   bitDepth = bit depth of the palette index (1, 4, or 8)
114      +   palette = `dsdl2.Palette` to use
115      + Throws: `dsdl2.SDLException` if allocation failed or palette-setting failed
116      +/
117     this(uint[2] size, ubyte bitDepth, Palette palette) @trusted
118     in {
119         assert(bitDepth == 1 || bitDepth == 4 || bitDepth == 8);
120         assert(palette !is null);
121     }
122     do {
123         this.sdlSurface = SDL_CreateRGBSurface(0, size[0], size[1], bitDepth, 0, 0, 0, 0);
124         if (this.sdlSurface is null) {
125             throw new SDLException;
126         }
127 
128         this.palette = palette;
129     }
130 
131     /++
132      + Constructs a blank indexed palette-using `dsdl2.Surface` from an array of `pixels`
133      +
134      + Params:
135      +   pixels = array of pixel data (copied internally)
136      +   size = size (width and height) of the `dsdl2.Surface` in pixels
137      +   pitch = skips in bytes per line/row of the `dsdl2.Surface`
138      +   bitDepth = bit depth of the palette index (1, 4, or 8)
139      +   palette = `dsdl2.Palette` to use
140      + Throws: `dsdl2.SDLException` if allocation failed or palette-setting failed
141      +/
142     this(const void[] pixels, uint[2] size, size_t pitch, ubyte bitDepth, Palette palette) @trusted
143     in {
144         assert(pixels !is null);
145         assert(size[0] > 0 && size[1] > 0);
146         assert(pitch * 8 >= size[0] * bitDepth);
147         assert(pixels.length == pitch * size[1]);
148         assert(palette !is null);
149     }
150     do {
151         this(size, bitDepth, palette);
152 
153         size_t lineBitSize = size[0] * bitDepth;
154         size_t lineSize = lineBitSize / 8 + (lineBitSize % 8 != 0);
155 
156         foreach (line; 0 .. size[1]) {
157             ubyte* srcLine = cast(ubyte*) pixels.ptr + line * pitch;
158             ubyte* destLine = cast(ubyte*) this.sdlSurface.pixels + line * this.pitch;
159             destLine[0 .. lineSize] = srcLine[0 .. lineSize];
160         }
161     }
162 
163     ~this() @trusted {
164         if (this.isOwner) {
165             SDL_FreeSurface(this.sdlSurface);
166         }
167     }
168 
169     @trusted invariant { // @suppress(dscanner.trust_too_much)
170         // Instance might be in an invalid state due to holding a non-owned externally-freed object when
171         // destructed in an unpredictable order.
172         if (!this.isOwner && GC.inFinalizer) {
173             return;
174         }
175 
176         assert(this.sdlSurface !is null);
177     }
178 
179     /++
180      + Equality operator overload
181      +/
182     bool opEquals(const Surface rhs) const @trusted {
183         return this.sdlSurface is rhs.sdlSurface;
184     }
185 
186     /++
187      + Gets the hash of the `dsdl2.Surface`
188      +
189      + Returns: unique hash for the instance being the pointer of the internal `SDL_Surface` pointer
190      +/
191     override hash_t toHash() const @trusted {
192         return cast(hash_t) this.sdlSurface;
193     }
194 
195     /++
196      + Formats the `dsdl2.Surface` into its construction representation:
197      + `"dsdl2.PixelFormat([<bytes>], [<width>, <height>], <pitch>, <pixelFormat>)"`
198      +
199      + Returns: the formatted `string`
200      +/
201     override string toString() const @trusted {
202         return "dsdl2.Surface(%s, %s, %d, %s)".format(this.buffer, this.size, this.pitch,
203             this.pixelFormat);
204     }
205 
206     /++
207      + Gets the `dsdl2.PixelFormat` of the `dsdl2.Surface`
208      +
209      + Returns: `const` proxy to the `dsdl2.PixelFormat` of the `dsdl2.Surface`
210      +/
211     const(PixelFormat) pixelFormat() const @property @trusted {
212         if (this.pixelFormatProxy is null) {
213             (cast(Surface) this).pixelFormatProxy =
214                 new PixelFormat(cast(SDL_PixelFormat*) this.sdlSurface.format, false, cast(void*) this);
215         }
216 
217         // If the internal pixel format pointer happens to change, rewire the proxy.
218         if (this.pixelFormatProxy.sdlPixelFormat !is this.sdlSurface.format) {
219             (cast(Surface) this).pixelFormatProxy.sdlPixelFormat =
220                 cast(SDL_PixelFormat*) this.sdlSurface.format;
221         }
222 
223         return this.pixelFormatProxy;
224     }
225 
226     /++
227      + Gets the internal pixel buffer of the `dsdl2.Surface`
228      +
229      + This function is marked as `@system` due to the potential of referencing invalid memory.
230      +
231      + Returns: slice of the buffer
232      +/
233     inout(void[]) buffer() inout @property @trusted {
234         return (cast(inout(void*)) this.sdlSurface.pixels)[0 .. this.pitch * this.height];
235     }
236 
237     /++
238      + Gets the width of the `dsdl2.Surface` in pixels
239      +
240      + Returns: width of the `dsdl2.Surface` in pixels
241      +/
242     uint width() const @property @trusted {
243         return this.sdlSurface.w;
244     }
245 
246     /++
247      + Gets the height of the `dsdl2.Surface` in pixels
248      +
249      + Returns: height of the `dsdl2.Surface` in pixels
250      +/
251     uint height() const @property @trusted {
252         return this.sdlSurface.h;
253     }
254 
255     /++
256      + Gets the size of the `dsdl2.Surface` in pixels
257      +
258      + Returns: array of width and height of the `dsdl2.Surface` in pixels
259      +/
260     uint[2] size() const @property @trusted {
261         return [this.sdlSurface.w, this.sdlSurface.h];
262     }
263 
264     /++
265      + Gets the pitch of the `dsdl2.Surface` in bytes (multiple of bytes for each line/row)
266      +
267      + Returns: pitch of the `dsdl2.Surface` in bytes
268      +/
269     size_t pitch() const @property @trusted {
270         return this.sdlSurface.pitch;
271     }
272 
273     /++
274      + Gets the used color palette of the `dsdl2.Surface`
275      +
276      + Returns: `dsdl2.Palette` instance of the `dsdl2.Surface`
277      +/
278     inout(Palette) palette() inout @property @trusted
279     in {
280         assert(this.pixelFormat.indexed);
281     }
282     do {
283         return (cast(inout PixelFormat) this.pixelFormat).palette;
284     }
285 
286     /++
287      + Sets the color palette of the `dsdl2.Surface`
288      +
289      + Params:
290      +   newPalette = new `dsdl2.Palette` instance to use for the `dsdl2.Surface`
291      +/
292     void palette(Palette newPalette) @property @trusted
293     in {
294         assert(this.pixelFormat.indexed);
295     }
296     do {
297         if (SDL_SetSurfacePalette(this.sdlSurface, newPalette.sdlPalette) != 0) {
298             throw new SDLException;
299         }
300 
301         (cast(PixelFormat) this.pixelFormat).palette = newPalette;
302     }
303 
304     /++
305      + Gets the pixel value in the `dsdl2.Surface` at the given coordinate
306      +
307      + Params:
308      +   xy = X and Y values of the coordinate
309      + Returns: the pixel value
310      +/
311     uint getPixelAt(uint[2] xy) const @trusted
312     in {
313         assert(xy[0] < this.width);
314         assert(xy[1] < this.height);
315     }
316     do {
317         const ubyte* row = cast(ubyte*) this.sdlSurface.pixels + xy[1] * this.pitch;
318 
319         if (this.pixelFormat.bitsPerPixel >= 8) {
320             const ubyte* pixelPtr = row + xy[0] * this.pixelFormat.bytesPerPixel;
321             align(4) ubyte[4] pixel;
322             pixel[0 .. this.pixelFormat.bytesPerPixel] =
323                 pixelPtr[0 .. this.pixelFormat.bytesPerPixel];
324 
325             return *cast(uint*) pixel.ptr;
326         }
327         else {
328             ubyte pixelByte = *(row + (xy[0] * this.pixelFormat.bitsPerPixel) / 8);
329             ubyte bitOffset = (xy[0] * this.pixelFormat.bitsPerPixel) % 8;
330 
331             switch (this.pixelFormat.sdlPixelFormatEnum) {
332             case SDL_PIXELFORMAT_INDEX1LSB:
333                 return (pixelByte & (0b00000001 << bitOffset)) != 0;
334 
335             case SDL_PIXELFORMAT_INDEX1MSB:
336                 return (pixelByte & (0b10000000 >> bitOffset)) != 0;
337 
338             case SDL_PIXELFORMAT_INDEX4LSB:
339                 return pixelByte & (0b00001111 << (bitOffset * 4)) >> (bitOffset * 4);
340 
341             case SDL_PIXELFORMAT_INDEX4MSB:
342                 return pixelByte & (0b11110000 >> (bitOffset * 4)) >> (4 - bitOffset * 4);
343 
344             default:
345                 assert(false);
346             }
347         }
348     }
349 
350     /++
351      + Sets the pixel value in the `dsdl2.Surface` at the given coordinate
352      +
353      + Params:
354      +   xy = X and Y values of the coordinate
355      +   value = pixel value to set at the given coordinate
356      +/
357     void setPixelAt(uint[2] xy, uint value) @trusted
358     in {
359         assert(xy[0] < this.width);
360         assert(xy[1] < this.height);
361         if (this.pixelFormat.bitsPerPixel != 32) { // Overflow protection
362             assert(value < 1 << this.pixelFormat.bitsPerPixel);
363         }
364     }
365     do {
366         ubyte* row = cast(ubyte*) this.sdlSurface.pixels + xy[1] * this.pitch;
367 
368         if (this.pixelFormat.bitsPerPixel >= 8) {
369             ubyte* pixelPtr = row + xy[0] * this.pixelFormat.bytesPerPixel;
370             pixelPtr[0 .. this.pixelFormat.bytesPerPixel] =
371                 (*cast(ubyte[4]*)&value)[0 .. this.pixelFormat.bytesPerPixel];
372         }
373         else {
374             ubyte* pixelPtr = row + (xy[0] * this.pixelFormat.bitsPerPixel) / 8;
375             ubyte bitOffset = (xy[0] * this.pixelFormat.bitsPerPixel) % 8;
376 
377             switch (this.pixelFormat.sdlPixelFormatEnum) {
378             case SDL_PIXELFORMAT_INDEX1LSB:
379                 *pixelPtr &= !(0b00000001 << bitOffset);
380                 *pixelPtr |= value << bitOffset;
381                 break;
382 
383             case SDL_PIXELFORMAT_INDEX1MSB:
384                 *pixelPtr &= !(0b10000000 >> bitOffset);
385                 *pixelPtr |= value << (7 - bitOffset);
386                 break;
387 
388             case SDL_PIXELFORMAT_INDEX4LSB:
389                 *pixelPtr &= !(0b00001111 << (bitOffset * 4));
390                 *pixelPtr |= value << (bitOffset * 4);
391                 break;
392 
393             case SDL_PIXELFORMAT_INDEX4MSB:
394                 *pixelPtr &= !(0b11110000 >> (bitOffset * 4));
395                 *pixelPtr |= value << (4 - bitOffset * 4);
396                 break;
397 
398             default:
399                 assert(false);
400             }
401         }
402     }
403 
404     /++
405      + Gets the pixel color in the `dsdl2.Surface` at the given coordinate
406      +
407      + Params:
408      +   xy = X and Y values of the coordinate
409      + Returns: the pixel color
410      +/
411     Color getAt(uint[2] xy) const
412     in {
413         assert(xy[0] < this.width);
414         assert(xy[1] < this.height);
415     }
416     do {
417         if (this.pixelFormat.hasAlpha) {
418             return this.pixelFormat.getRGBA(this.getPixelAt(xy));
419         }
420         else {
421             return this.pixelFormat.getRGB(this.getPixelAt(xy));
422         }
423     }
424 
425     /++
426      + Sets the pixel color in the `dsdl2.Surface` at the given coordinate
427      +
428      + Params:
429      +   xy = X and Y values of the coordinate
430      +   color = pixel color to set at the given coordinate
431      +/
432     void setAt(uint[2] xy, Color color)
433     in {
434         assert(xy[0] < this.width);
435         assert(xy[1] < this.height);
436     }
437     do {
438         if (this.pixelFormat.hasAlpha) {
439             this.setPixelAt(xy, this.pixelFormat.mapRGBA(color));
440         }
441         else {
442             this.setPixelAt(xy, this.pixelFormat.mapRGB(color));
443         }
444     }
445 
446     /++
447      + Gets the color and alpha multipliers of the `dsdl2.Surface` that wraps `SDL_GetSurfaceColorMod` and
448      + `SDL_GetSurfaceAlphaMod`
449      +
450      + Returns: color and alpha multipliers of the `dsdl2.Surface`
451      +/
452     Color mod() const @property @trusted {
453         Color multipliers = void;
454         SDL_GetSurfaceColorMod(cast(SDL_Surface*) this.sdlSurface, &multipliers.sdlColor.r,
455             &multipliers.sdlColor.g, &multipliers.sdlColor.b);
456         SDL_GetSurfaceAlphaMod(cast(SDL_Surface*) this.sdlSurface, &multipliers.sdlColor.a);
457         return multipliers;
458     }
459 
460     /++
461      + Sets the color and alpha multipliers of the `dsdl2.Surface` that wraps `SDL_SetSurfaceColorMod` and
462      + `SDL_SetSurfaceAlphaMod`
463      +
464      + Params:
465      +   newMod = `dsdl2.Color` with `.r`, `.g`, `.b` as the color multipliers, and `.a` as the alpha multiplier
466      +/
467     void mod(Color newMod) @property @trusted {
468         SDL_SetSurfaceColorMod(this.sdlSurface, newMod.r, newMod.g, newMod.b);
469         SDL_SetSurfaceAlphaMod(this.sdlSurface, newMod.a);
470     }
471 
472     /++
473      + Wraps `SDL_GetSurfaceColorMod` which gets the color multipliers of the `dsdl2.Surface`
474      +
475      + Returns: color multipliers of the `dsdl2.Surface`
476      +/
477     ubyte[3] colorMod() const @property @trusted {
478         ubyte[3] rgbMod = void;
479         SDL_GetSurfaceColorMod(cast(SDL_Surface*) this.sdlSurface, &rgbMod[0], &rgbMod[1], &rgbMod[2]);
480         return rgbMod;
481     }
482 
483     /++
484      + Wraps `SDL_SetSurfaceColorMod` which sets the color multipliers of the `dsdl2.Surface`
485      +
486      + Params:
487      +   newColorMod = an array of `ubyte`s representing red, green, and blue multipliers (each 0-255)
488      +/
489     void colorMod(ubyte[3] newColorMod) @property @trusted {
490         SDL_SetSurfaceColorMod(this.sdlSurface, newColorMod[0], newColorMod[1], newColorMod[2]);
491     }
492 
493     /++
494      + Wraps `SDL_GetSurfaceAlphaMod` which gets the alpha multiplier of the `dsdl2.Surface`
495      +
496      + Returns: alpha multiplier of the `dsdl2.Surface`
497      +/
498     ubyte alphaMod() const @property @trusted {
499         ubyte aMod = void;
500         SDL_GetSurfaceAlphaMod(cast(SDL_Surface*) this.sdlSurface, &aMod);
501         return aMod;
502     }
503 
504     /++
505      + Wraps `SDL_SetSurfaceAlphaMod` which sets the alpha multiplier of the `dsdl2.Surface`
506      +
507      + Params:
508      +   newAlphaMod = alpha multiplier (0-255)
509      +/
510     void alphaMod(ubyte newAlphaMod) @property @trusted {
511         SDL_SetSurfaceAlphaMod(this.sdlSurface, newAlphaMod);
512     }
513 
514     /++
515      + Wraps `SDL_GetClipRect` which gets the clipping `dsdl2.Rect` of the `dsdl2.Surface`
516      +
517      + Returns: clipping `dsdl2.Rect` of the `dsdl2.Surface`
518      +/
519     Rect clipRect() const @property @trusted {
520         Rect rect = void;
521         SDL_GetClipRect(cast(SDL_Surface*) this.sdlSurface, &rect.sdlRect);
522         return rect;
523     }
524 
525     /++
526      + Wraps `SDL_SetClipRect` which sets the clipping `dsdl2.Rect` of the `dsdl2.Surface`
527      +
528      + Params:
529      +   newRect = `dsdl2.Rect` to set as the clipping rectangle
530      +/
531     void clipRect(Rect newRect) @property @trusted {
532         SDL_SetClipRect(this.sdlSurface, &newRect.sdlRect);
533     }
534 
535     /++
536      + Acts as `SDL_SetClipRect(surface, NULL)` which removes the clipping `dsdl2.Rect` of the
537      + `dsdl2.Surface`
538      +/
539     void clipRect(typeof(null) _) @property @trusted {
540         SDL_SetClipRect(this.sdlSurface, null);
541     }
542 
543     /++
544      + Wraps `SDL_SetClipRect` which sets or removes the clipping `dsdl2.Rect` of the `dsdl2.Surface`
545      +
546      + Params:
547      +   newRect = `dsdl2.Rect` to set as the clipping rectangle; `null` to remove the clipping rectangle
548      +/
549     void clipRect(Nullable!Rect newRect) @property @trusted {
550         if (newRect.isNull) {
551             this.clipRect = null;
552         }
553         else {
554             this.clipRect = newRect.get;
555         }
556     }
557 
558     /++
559      + Wraps `SDL_GetColorKey` which gets the color key used by the `dsdl2.Surface` for transparency
560      +
561      + Returns: transparency color key in `dsdl2.Color`
562      + Throws: `dsdl2.SDLException` if color key unable to be fetched
563      +/
564     Color colorKey() const @property @trusted {
565         uint key = void;
566         if (SDL_GetColorKey(cast(SDL_Surface*) this.sdlSurface, &key) != 0) {
567             throw new SDLException;
568         }
569 
570         if (this.pixelFormat.hasAlpha) {
571             return this.pixelFormat.getRGBA(key);
572         }
573         else {
574             return this.pixelFormat.getRGB(key);
575         }
576     }
577 
578     /++
579      + Wraps `SDL_SetColorKey` which sets the color key used for the `dsdl2.Surface` making pixels of the same
580      + color transparent
581      +
582      + Params:
583      +   newPixelKey = pixel value of the color key that get transparency
584      + Throws: `dsdl2.SDLException` if color key unable to set
585      +/
586     void colorKey(uint newPixelKey) @property @trusted {
587         if (SDL_SetColorKey(this.sdlSurface, SDL_TRUE, newPixelKey) != 0) {
588             throw new SDLException;
589         }
590     }
591 
592     /++
593      + Wraps `SDL_SetColorKey` which sets the color key used for the `dsdl2.Surface` making pixels of the same
594      + color transparent
595      +
596      + Params:
597      +   newColorKey = `dsdl2.Color` specifying pixels that get transparency
598      + Throws: `dsdl2.SDLException` if color key unable to set
599      +/
600     void colorKey(Color newColorKey) @property @trusted {
601         if (this.pixelFormat.hasAlpha) {
602             this.colorKey = this.pixelFormat.mapRGBA(newColorKey);
603         }
604         else {
605             this.colorKey = this.pixelFormat.mapRGB(newColorKey);
606         }
607     }
608 
609     /++
610      + Acts as `SDL_SetColorKey(surface, NULL)` which disables color-keying
611      +/
612     void colorKey(typeof(null) _) @property @trusted {
613         if (SDL_SetColorKey(this.sdlSurface, SDL_FALSE, 0) != 0) {
614             throw new SDLException;
615         }
616     }
617 
618     /++
619      + Wraps `SDL_SetColorKey` which sets or removes the color key used for the `dsdl2.Surface` making pixels
620      + of the same color transparent
621      +
622      + Params:
623      +   newColorKey = `dsdl2.Color` specifying pixels that get transparency; `null` to remove the color key
624      + Throws: `dsdl2.SDLException` if color key unable to set or removed
625      +/
626     void colorKey(Nullable!Color newColorKey) @property @trusted {
627         if (newColorKey.isNull) {
628             this.colorKey = null;
629         }
630         else {
631             this.colorKey = newColorKey.get;
632         }
633     }
634 
635     static if (sdlSupport >= SDLSupport.v2_0_9) {
636         /++
637          + Wraps `SDL_HasColorKey` (from SDL 2.0.9) which checks whether the `dsdl2.Surface` has a color key
638          + for transparency
639          +
640          + Returns: `true` if it does, otherwise `false`
641          +/
642         bool hasColorKey() const @property @trusted
643         in {
644             assert(getVersion() >= Version(2, 0, 9));
645         }
646         do {
647             return SDL_HasColorKey(cast(SDL_Surface*) this.sdlSurface) == SDL_TRUE;
648         }
649     }
650 
651     /++
652      + Wraps `SDL_GetSurfaceBlendMode` which gets the `dsdl2.Surface`'s `dsdl2.BlendMode` defining blitting
653      +
654      + Returns: `dsdl2.BlendMode` of the `dsdl2.Surface`
655      + Throws: `dsdl2.SDLException` if `dsdl2.BlendMode` unable to get
656      +/
657     BlendMode blendMode() const @property @trusted {
658         BlendMode mode = void;
659         if (SDL_GetSurfaceBlendMode(cast(SDL_Surface*) this.sdlSurface, &mode.sdlBlendMode) != 0) {
660             throw new SDLException;
661         }
662 
663         return mode;
664     }
665 
666     /++
667      + Wraps `SDL_SetSurfaceBlendMode` which sets the `dsdl2.Surface`'s `dsdl2.BlendMode` defining blitting
668      +
669      + Params:
670      +   newMode = `dsdl2.BlendMode` to set
671      + Throws: `dsdl2.SDLException` if `dsdl2.BlendMode` unable to set
672      +/
673     void blendMode(BlendMode newMode) @property @trusted {
674         if (SDL_SetSurfaceBlendMode(this.sdlSurface, newMode.sdlBlendMode) != 0) {
675             throw new SDLException;
676         }
677     }
678 
679     /++
680      + Wraps `SDL_ConvertPixels` which converts the `dsdl2.Surface` from its RGB(A) `dsdl2.PixelFormat` to another
681      + `dsdl2.Surface` with a different RGB(A) `dsdl2.PixelFormat`
682      +
683      + Params:
684      +   rgbPixelFormat = the RGB(A) `dsdl2.PixelFormat` to target the conversion
685      + Returns: result `dsdl2.Surface` with the `targetPixelFormat`
686      + Throws: `dsdl2.SDLException` if pixels failed to convert
687      +/
688     Surface convert(const PixelFormat rgbPixelFormat) const @trusted
689     in {
690         assert(!this.pixelFormat.indexed);
691         assert(rgbPixelFormat !is null);
692     }
693     do {
694         auto target = new Surface([this.width, this.height], rgbPixelFormat);
695         if (SDL_ConvertPixels(this.width, this.height, this.pixelFormat.sdlPixelFormatEnum,
696                 this.sdlSurface.pixels, this.pitch.to!int, target.pixelFormat.sdlPixelFormatEnum,
697                 target.sdlSurface.pixels, target.pitch.to!int) != 0) {
698             throw new SDLException;
699         }
700 
701         return target;
702     }
703 
704     /++
705      + Acts as `SDL_FillRect(surface, NULL)` which fills the entire `dsdl2.Surface` with a pixel value
706      +
707      + Params:
708      +   pixel = pixel value of the color to fill the entire `dsdl2.Surface`
709      +/
710     void fill(uint pixel) @trusted {
711         if (SDL_FillRect(this.sdlSurface, null, pixel) != 0) {
712             throw new SDLException;
713         }
714     }
715 
716     /++
717      + Acts as `SDL_FillRect(surface, NULL)` which fills the entire `dsdl2.Surface` with a `dsdl2.Color` value
718      +
719      + Params:
720      +   color = `dsdl2.Color` of the color to fill the entire `dsdl2.Surface`
721      +/
722     void fill(Color color) @trusted {
723         if (this.pixelFormat.hasAlpha) {
724             this.fill(this.pixelFormat.mapRGBA(color));
725         }
726         else {
727             this.fill(this.pixelFormat.mapRGB(color));
728         }
729     }
730 
731     /++
732      + Wraps `SDL_FillRect` which draws a filled rectangle in the `dsdl2.Surface` with specifying a pixel color value
733      +
734      + Params:
735      +   rect = `dsdl2.Rect` specifying the position and size
736      +   pixel = pixel value of the color to fill the rectangle
737      + Throws: `dsdl2.SDLException` if rectangle failed to draw
738      +/
739     void fillRect(Rect rect, uint pixel) @trusted {
740         if (SDL_FillRect(this.sdlSurface, &rect.sdlRect, pixel) != 0) {
741             throw new SDLException;
742         }
743     }
744 
745     /++
746      + Wraps `SDL_FillRect` which draws a filled rectangle in the `dsdl2.Surface` with specifying a `dsdl2.Color` value
747      +
748      + Params:
749      +   rect = `dsdl2.Rect` specifying the position and size
750      +   color = `dsdl2.Color` of the color to fill the rectangle
751      + Throws: `dsdl2.SDLException` if rectangle failed to draw
752      +/
753     void fillRect(Rect rect, Color color) @trusted {
754         if (this.pixelFormat.hasAlpha) {
755             this.fillRect(rect, this.pixelFormat.mapRGBA(color));
756         }
757         else {
758             this.fillRect(rect, this.pixelFormat.mapRGB(color));
759         }
760     }
761 
762     /++
763      + Wraps `SDL_FillRects` which draws multiple filled rectangles in the `dsdl2.Surface` with specifying a pixel
764      + color value
765      +
766      + Params:
767      +   rects = an array of `dsdl2.Rect`s specifying the drawn rectangles' positions and sizes
768      +   pixel = pixel value of the color to fill the rectangles
769      + Throws: `dsdl2.SDLException` if the rectangles failed to draw
770      +/
771     void fillRects(const Rect[] rects, uint pixel) @trusted {
772         if (SDL_FillRects(this.sdlSurface, cast(SDL_Rect*) rects.ptr, rects.length.to!int, pixel) != 0) {
773             throw new SDLException;
774         }
775     }
776 
777     /++
778      + Wraps `SDL_FillRects` which draws multiple filled rectangles in the `dsdl2.Surface` with specifying a
779      + `dsdl2.Color` value
780      +
781      + Params:
782      +   rects = an array of `dsdl2.Rect`s specifying the drawn rectangles' positions and sizes
783      +   color = `dsdl2.Color` of the color to fill the rectangles
784      + Throws: `dsdl2.SDLException` if the rectangles failed to draw
785      +/
786     void fillRects(const Rect[] rects, Color color) @trusted {
787         if (this.pixelFormat.hasAlpha) {
788             this.fillRects(rects, this.pixelFormat.mapRGBA(color));
789         }
790         else {
791             this.fillRects(rects, this.pixelFormat.mapRGB(color));
792         }
793     }
794 
795     /++
796      + Wraps `SDL_BlitSurface` which blits/draws a `dsdl2.Surface` on top of the `dsdl2.Surface` at a specific
797      + point as the top-left point of the drawn `dsdl2.Surface` without any scaling done
798      +
799      + Params:
800      +   source = `dsdl2.Surface` to blit/draw
801      +   destPoint = top-left `dsdl2.Point` of where `source` is drawn
802      +/
803     void blit(const Surface source, Point destPoint) @trusted {
804         SDL_Rect dstrect = SDL_Rect(destPoint.x, destPoint.y, 0, 0);
805         if (SDL_BlitSurface(cast(SDL_Surface*) source.sdlSurface, null, this.sdlSurface, &dstrect) != 0) {
806             throw new SDLException;
807         }
808     }
809 
810     /++
811      + Wraps `SDL_BlitSurface` which blits/draws a `dsdl2.Surface` on top of the `dsdl2.Surface` at a specific
812      + point as the top-left point of the drawn `dsdl2.Surface` without any scaling done
813      +
814      + Params:
815      +   source = `dsdl2.Surface` to blit/draw
816      +   destPoint = top-left `dsdl2.Point` of where `source` is drawn
817      +   srcRect = the clipping rect of `source` specifying which part is drawn
818      +/
819     void blit(const Surface source, Point destPoint, Rect srcRect) @trusted {
820         SDL_Rect dstrect = SDL_Rect(destPoint.x, destPoint.y, 0, 0);
821         if (SDL_BlitSurface(cast(SDL_Surface*) source.sdlSurface, &srcRect.sdlRect, this.sdlSurface,
822                 &dstrect) != 0) {
823             throw new SDLException;
824         }
825     }
826 
827     /++
828      + Wraps `SDL_BlitScaled` which blits/draws a `dsdl2.Surface` on top of the `dsdl2.Surface` at a specific
829      + point as the top-left point of the drawn `dsdl2.Surface` with scaling
830      +
831      + Params:
832      +   source = `dsdl2.Surface` to blit/draw
833      +   destRect = `dsdl2.Rect` of where `source` should be drawn (squeezes/stretches to the dimensions as well)
834      +/
835     void blitScaled(const Surface source, Rect destRect) @trusted {
836         if (SDL_BlitScaled(cast(SDL_Surface*) source.sdlSurface, null, this.sdlSurface,
837                 &destRect.sdlRect) != 0) {
838             throw new SDLException;
839         }
840     }
841 
842     /++
843      + Wraps `SDL_BlitScaled` which blits/draws a `dsdl2.Surface` on top of the `dsdl2.Surface` at a specific
844      + point as the top-left point of the drawn `dsdl2.Surface` with scaling
845      +
846      + Params:
847      +   source = `dsdl2.Surface` to blit/draw
848      +   destRect = `dsdl2.Rect` of where `source` should be drawn (squeezes/stretches to the dimensions as well)
849      +   srcRect = the clipping rect of `source` specifying which part is drawn
850      +/
851     void blitScaled(const Surface source, Rect destRect, Rect srcRect) @trusted {
852         if (SDL_BlitSurface(cast(SDL_Surface*) source.sdlSurface, &srcRect.sdlRect, this.sdlSurface,
853                 &destRect.sdlRect) != 0) {
854             throw new SDLException;
855         }
856     }
857 }
858 ///
859 unittest {
860     auto surface = new dsdl2.Surface([100, 100], dsdl2.PixelFormat.rgba8888);
861     surface.fill(dsdl2.Color(24, 24, 24));
862     surface.fillRect(dsdl2.Rect(25, 25, 50, 50), dsdl2.Color(42, 42, 42));
863 
864     assert(surface.getAt([0, 0]) == dsdl2.Color(24, 24, 24));
865     assert(surface.getAt([50, 50]) == dsdl2.Color(42, 42, 42));
866 }