1 /*
2  *******************************************************************************************
3  * Dgame (a D game framework) - Copyright (c) Randy Schütt
4  * 
5  * This software is provided 'as-is', without any express or implied warranty.
6  * In no event will the authors be held liable for any damages arising from
7  * the use of this software.
8  * 
9  * Permission is granted to anyone to use this software for any purpose,
10  * including commercial applications, and to alter it and redistribute it
11  * freely, subject to the following restrictions:
12  * 
13  * 1. The origin of this software must not be misrepresented; you must not claim
14  *    that you wrote the original software. If you use this software in a product,
15  *    an acknowledgment in the product documentation would be appreciated but is
16  *    not required.
17  * 
18  * 2. Altered source versions must be plainly marked as such, and must not be
19  *    misrepresented as being the original software.
20  * 
21  * 3. This notice may not be removed or altered from any source distribution.
22  *******************************************************************************************
23  */
24 module Dgame.Graphic.Surface;
25 
26 private:
27 
28 import derelict.sdl2.sdl;
29 import derelict.sdl2.image;
30 
31 import Dgame.Math.Rect;
32 import Dgame.Math.Vector2;
33 
34 import Dgame.Graphic.Color;
35 import Dgame.Graphic.Masks;
36 
37 import Dgame.Internal.Error;
38 import Dgame.Internal.d2c;
39 
40 public:
41 
42 /**
43  * Surface is a wrapper for a SDL_Surface and can load and save images.
44  *
45  * Author: Randy Schuett (rswhite4@googlemail.com)
46  */
47 struct Surface {
48     /**
49      * Supported BlendModes
50      */
51     enum BlendMode : ubyte {
52         None   = SDL_BLENDMODE_NONE,  /// no blending
53         Blend  = SDL_BLENDMODE_BLEND, /// dst = (src * A) + (dst * (1-A))
54         Add    = SDL_BLENDMODE_ADD,   /// dst = (src * A) + dst
55         Mod    = SDL_BLENDMODE_MOD    /// dst = src * dst
56     }
57 
58 private:
59     SDL_Surface* _surface;
60 
61     @nogc
62     static SDL_Surface* create(ref const Masks masks, uint width, uint height, ubyte depth, void* memory) nothrow {
63         SDL_Surface* surface;
64         if (memory) {
65             surface = SDL_CreateRGBSurfaceFrom(
66                 memory,
67                 width, height, depth, 
68                 (depth / 8) * width,
69                 masks.red, masks.green, masks.blue, masks.alpha
70             );
71         } else {
72             surface = SDL_CreateRGBSurface(
73                 0,
74                 width, height, depth,
75                 masks.red, masks.green, masks.blue, masks.alpha
76             );
77         }
78 
79         assert_fmt(surface, "Invalid SDL_Surface. Error: %s\n", SDL_GetError());
80         assert_fmt(surface.pixels, "Invalid pixel data. Error: %s\n", SDL_GetError());
81 
82         return surface;
83     }
84 
85 public:
86     /**
87      * CTor
88      */
89     @nogc
90     this(SDL_Surface* srfc) nothrow {
91         assert_fmt(srfc, "Invalid SDL_Surface. Error: %s\n", SDL_GetError());
92         assert_fmt(srfc.pixels, "Invalid pixel data. Error: %s\n", SDL_GetError());
93 
94         _surface = srfc;
95     }
96 
97     /**
98      * CTor
99      */
100     @nogc
101     this(string filename) nothrow {
102         this.loadFromFile(filename);
103     }
104 
105     /**
106      * Make a new Surface of the given width, height and depth.
107      */
108     @nogc
109     this(uint width, uint height, ubyte depth = 32, const Masks masks = Masks.init) nothrow {
110         _surface = Surface.create(masks, width, height, depth, null);
111     }
112     
113     /**
114      * Make an new Surface of the given memory, width, height and depth.
115      */
116     @nogc
117     this(void* memory, uint width, uint height, ubyte depth = 32, const Masks masks = Masks.init) nothrow {
118         _surface = Surface.create(masks, width, height, depth, memory);
119     }
120     
121     /**
122      * Postblit is allowed and increases the internal ref count
123      */
124     @nogc
125     this(this) nothrow {
126         if (_surface)
127             _surface.refcount++;
128     }
129     
130     /**
131      * DTor
132      */
133     @nogc
134     ~this() nothrow {
135         SDL_FreeSurface(_surface);
136     }
137 
138     /**
139      * Returns the current ref count / usage
140      */
141     @property
142     @nogc
143     int refCount() const pure nothrow {
144         return _surface ? _surface.refcount : 0;
145     }
146     
147     /**
148      * Returns if the Surface is valid. Which means that the Surface has valid data.
149      */
150     @nogc
151     bool isValid() const pure nothrow {
152         return _surface && _surface.pixels;
153     }
154     
155     /**
156      * Load from filename. If any data is already stored, the data will be freed.
157      */
158     @nogc
159     bool loadFromFile(string filename) nothrow {
160         import std.file : exists;
161 
162         immutable bool ex = exists(filename);
163         if (!ex) {
164             print_fmt("File %s does not exists.\n", toStringz(filename));
165             return false;
166         }
167 
168         SDL_FreeSurface(_surface); // free old surface
169 
170         _surface = IMG_Load(toStringz(filename));
171         if (!_surface) {
172             print_fmt("Could not load image %s. Error: %s.\n", toStringz(filename), SDL_GetError());
173             return false;
174         }
175         
176         assert(_surface.pixels, "Invalid pixel data.");
177 
178         return true;
179     }
180     
181     /**
182      * Load from memory.
183      */
184     @nogc
185     bool loadFromMemory(void* memory, ushort width, ushort height, ubyte depth = 32, const Masks masks = Masks.init) nothrow {
186         SDL_FreeSurface(_surface); // free old surface
187         _surface = Surface.create(masks, width, height, depth, memory);
188 
189         return true;
190     }
191     
192     /**
193      * Save the current pixel data to the file.
194      */
195     @nogc
196     bool saveToFile(string filename) nothrow {
197         immutable int result = IMG_SavePNG(_surface, toStringz(filename));
198         if (result != 0) {
199             print_fmt("Could not save image %s. Error: %s.\n", toStringz(filename), SDL_GetError());
200             return false;
201         }
202 
203         return true;
204     }
205     
206     /**
207      * Fills a specific area of the surface with the given color.
208      * The second parameter is a pointer to the area.
209      * If it's null, the whole Surface is filled.
210      */
211     @nogc
212     void fill(const Color4b col, const Rect* rect = null) nothrow {
213         if (!_surface)
214             return;
215 
216         SDL_Rect a = void;
217         const SDL_Rect* ptr = rect ? _transfer(*rect, a) : null;
218 
219         immutable uint key = SDL_MapRGBA(_surface.format, col.red, col.green, col.blue, col.alpha);
220         SDL_FillRect(_surface, ptr, key);
221     }
222     
223     /**
224      * Use this function to set the RLE acceleration hint for a surface.
225      * RLE (Run-Length-Encoding) is a way of compressing data.
226      * If RLE is enabled, color key and alpha blending blits are much faster, 
227      * but the surface must be locked before directly accessing the pixels.
228      *
229      * Returns: whether the call succeeded or not
230      */
231     @nogc
232     bool optimizeRLE(bool enable) nothrow {
233         if (!_surface)
234             return false;
235         return SDL_SetSurfaceRLE(_surface, enable) == 0;
236     }
237     
238     /**
239      * Use this function to set up a surface for directly accessing the pixels.
240      *
241      * Returns: whether the call succeeded or not
242      */
243     @nogc
244     bool lock() nothrow {
245         return _surface && SDL_LockSurface(_surface) == 0;
246     }
247     
248     /**
249      * Use this function to release a surface after directly accessing the pixels.
250      */
251     @nogc
252     void unlock() nothrow {
253         if (_surface)
254             SDL_UnlockSurface(_surface);
255     }
256     
257     /**
258      * Returns whether this Surface is locked or not.
259      */
260     @nogc
261     bool isLocked() const pure nothrow {
262         return _surface ? _surface.locked != 0 : false;
263     }
264     
265     /**
266      * Use this function to determine whether a surface must be locked for access.
267      */
268     @nogc
269     bool mustLock() nothrow {
270         if (!_surface)
271             return false;
272         return SDL_MUSTLOCK(_surface) == SDL_TRUE;
273     }
274     
275     /**
276      * Use this function to adapt the format of another Surface to this surface.
277      */
278     @nogc
279     bool adaptTo(ref Surface srfc) nothrow {
280         return this.adaptTo(srfc.bits);
281     }
282     
283     /**
284      * Use this function to adapt the format of another Surface depth to this surface.
285      */
286     @nogc
287     bool adaptTo(ubyte depth) nothrow {
288         if (!_surface)
289             return false;
290 
291         SDL_PixelFormat fmt;
292         fmt.BitsPerPixel = depth;
293 
294         SDL_Surface* adapted = SDL_ConvertSurface(_surface, &fmt, 0);
295         if (adapted) {
296             SDL_FreeSurface(_surface);
297             _surface = adapted;
298 
299             return true;
300         }
301 
302         print_fmt("Image could not be adapted: %s\n", SDL_GetError());
303 
304         return false;
305     }
306     
307     /**
308      * Set the colorkey.
309      */
310     @nogc
311     void setColorkey(const Color4b col) nothrow {
312         if (!_surface)
313             return;
314 
315         immutable uint key = SDL_MapRGBA(_surface.format, col.red, col.green, col.blue, col.alpha);
316         SDL_SetColorKey(_surface, SDL_TRUE, key);
317     }
318     
319     /**
320      * Returns the current colorkey,
321      * or Color4b.Black, if the Surface is invalid
322      */
323     @nogc
324     Color4b getColorkey() nothrow {
325         if (!_surface)
326             return Color4b.Black;
327 
328         uint key = 0;
329         SDL_GetColorKey(_surface, &key);
330         
331         ubyte r, g, b, a;
332         SDL_GetRGBA(key, _surface.format, &r, &g, &b, &a);
333         
334         return Color4b(r, g, b, a);
335     }
336     
337     /**
338      * Set the Alpha mod.
339      */
340     @nogc
341     void setAlphaMod(ubyte alpha) nothrow {
342         if (_surface)
343             SDL_SetSurfaceAlphaMod(_surface, alpha);
344     }
345     
346     /**
347      * Returns the current Alpha mod.
348      */
349     @nogc
350     ubyte getAlphaMod() nothrow {
351         ubyte alpha;
352         if (_surface)
353             SDL_GetSurfaceAlphaMod(_surface, &alpha);
354         return alpha;
355     }
356     
357     /**
358      * Set the Blendmode.
359      */
360     @nogc
361     void setBlendMode(BlendMode mode) nothrow {
362         if (_surface)
363             SDL_SetSurfaceBlendMode(_surface, mode);
364     }
365     
366     /**
367      * Returns the current Blendmode.
368      */
369     @nogc
370     BlendMode getBlendMode() nothrow {
371         SDL_BlendMode mode;
372         if (_surface)
373             SDL_GetSurfaceBlendMode(_surface, &mode);
374         return cast(BlendMode) mode;
375     }
376     
377     /**
378      * Returns the clip rect of this surface.
379      * The clip rect is the area of the surface which is drawn.
380      */
381     @nogc
382     Rect getClipRect() nothrow {
383         SDL_Rect clip;
384         if (_surface)
385             SDL_GetClipRect(_surface, &clip);
386         return Rect(clip.x, clip.y, clip.w, clip.h);
387     }
388     
389     /**
390      * Set the clip rect.
391      */
392     @nogc
393     void setClipRect(const Rect clip) nothrow {
394         if (_surface) {
395             SDL_Rect a = void;
396             SDL_SetClipRect(_surface, _transfer(clip, a));
397         }
398     }
399     
400     /**
401      * Returns the width.
402      */
403     @property
404     @nogc
405     int width() const pure nothrow {
406         return _surface ? _surface.w : 0;
407     }
408     
409     /**
410      * Returns the height.
411      */
412     @property
413     @nogc
414     int height() const pure nothrow {
415         return _surface ? _surface.h : 0;
416     }
417     
418     /**
419      * Returns the pixel data of this surface.
420      */
421     @property
422     @nogc
423     inout(void*) pixels() inout pure nothrow {
424         return _surface ? _surface.pixels : null;
425     }
426     
427     /**
428      * Count the bits of this surface.
429      * Could be 32, 24, 16, 8, 0.
430      */
431     @property
432     @nogc
433     ubyte bits() const pure nothrow {
434         return _surface ? _surface.format.BitsPerPixel : 0;
435     }
436     
437     /**
438      * Count the bytes of this surface.
439      * Could be 4, 3, 2, 1, 0. (countBits / 8)
440      */
441     @property
442     @nogc
443     ubyte bytes() const pure nothrow {
444         return _surface ? _surface.format.BytesPerPixel : 0;
445     }
446     
447     /**
448      * Returns the Surface pitch or 0.
449      */
450     @property
451     @nogc
452     int pitch() const pure nothrow {
453         return _surface ? _surface.pitch : 0;
454     }
455 
456     /**
457      * Returns the Surface color Masks
458      */
459     @nogc
460     Masks getMasks() const pure nothrow {
461         if (!_surface)
462             return Masks.Zero;
463 
464         return Masks(
465             _surface.format.Rmask,
466             _surface.format.Gmask,
467             _surface.format.Bmask,
468             _surface.format.Amask
469         );
470     }
471 
472     /**
473      * Returns the pixel at the given coordinates.
474      */
475     @nogc
476     int getPixelAt(int x, int y) const nothrow {
477         if (!_surface)
478             return -1;
479 
480         uint* pixels = cast(uint*) this.pixels;
481         assert(pixels, "No pixel at this point.");
482         
483         return pixels[(y * _surface.w) + x];
484     }
485     
486     /**
487      * Returns the pixel at the given coordinates.
488      */
489     @nogc
490     int getPixelAt(const Vector2i pos) const nothrow {
491         return this.getPixelAt(pos.x, pos.y);
492     }
493     
494     /**
495      * Put a new pixel at the given coordinates.
496      */
497     @nogc
498     void putPixelAt(const Vector2i pos, uint pixel) nothrow {
499         if (!_surface)
500             return;
501 
502         uint* pixels = cast(uint*) this.pixels();
503         assert(pixels, "No pixel at this point.");
504         
505         pixels[(pos.y * _surface.w) + pos.x] = pixel;
506     }
507 
508     /**
509      * Returns the color on the given position,
510      * or Color4b.Black if the position is out of range.
511      */
512     @nogc
513     Color4b getColorAt(int x, int y) const nothrow {
514         if (!_surface)
515             return Color4b.Black;
516 
517         immutable uint len = this.width * this.height;
518         if ((x * y) <= len) {
519             immutable uint pixel = this.getPixelAt(x, y);
520             
521             ubyte r, g, b, a;
522             SDL_GetRGBA(pixel, _surface.format, &r, &g, &b, &a);
523             
524             return Color4b(r, g, b, a);
525         }
526 
527         return Color4b.Black;
528     }
529     
530     /**
531      * Returns the color on the given position.
532      */
533     @nogc
534     Color4b getColorAt(const Vector2i pos) const nothrow {
535         return this.getColorAt(pos.x, pos.y);
536     }
537     
538     /**
539      * Use this function to perform a fast, low quality,
540      * stretch blit between two surfaces of the same pixel format.
541      * src is the a pointer to a Rect structure which represents the rectangle to be copied, 
542      * or null to copy the entire surface.
543      * dst is a pointer to a Rect structure which represents the rectangle that is copied into.
544      * null means, that the whole srfc is copied to (0|0).
545      */
546     @nogc
547     bool blitScaled(ref Surface srfc, const Rect* src = null, Rect* dst = null) nothrow {
548         if (!srfc.isValid())
549             return false;
550 
551         SDL_Rect a = void;
552         SDL_Rect b = void;
553 
554         const SDL_Rect* src_ptr = src ? _transfer(*src, a) : null;
555         SDL_Rect* dst_ptr = dst ? _transfer(*dst, b) : null;
556         
557         immutable bool result = SDL_BlitScaled(srfc._surface, src_ptr, _surface, dst_ptr) == 0;
558         if (!result)
559             print_fmt("Could not blit surface: %s\n", SDL_GetError());
560 
561         return result;
562     }
563     
564     /**
565      * Use this function to perform a fast blit from the source surface to the this surface.
566      * src is the a pointer to a Rect structure which represents the rectangle to be copied, 
567      * or null to copy the entire surface.
568      * dst is a pointer to a Rect structure which represents the rectangle that is copied into.
569      * null means, that the whole srfc is copied to (0|0).
570      */
571     @nogc
572     bool blit(ref Surface srfc, const Rect* src = null, Rect* dst = null) nothrow {
573         if (!srfc.isValid())
574             return false;
575 
576         SDL_Rect a = void;
577         SDL_Rect b = void;
578 
579         const SDL_Rect* src_ptr = src ? _transfer(*src, a) : null;
580         SDL_Rect* dst_ptr = dst ? _transfer(*dst, b) : null;
581         
582         immutable bool result = SDL_BlitSurface(srfc._surface, src_ptr, _surface, dst_ptr) == 0;
583         if (!result)
584             print_fmt("Could not blit surface: %s\n", SDL_GetError());
585 
586         return result;
587     }
588     
589     /**
590      * Returns a subsurface from this surface. rect represents the viewport.
591      * The subsurface is a separate Surface object.
592      */
593     @nogc
594     Surface subSurface(const Rect rect) nothrow {
595         assert(!rect.isEmpty(), "Cannot take a empty subsurface.");
596         assert(_surface, "Cannot take a subsurface from null.");
597 
598         SDL_Surface* sub = this.create(Masks.Zero, rect.width, rect.height, 32, null);
599         assert(sub, "Failed to construct a sub surface.");
600 
601         SDL_Rect clip = void;
602 
603         immutable int result = SDL_BlitSurface(_surface, _transfer(rect, clip), sub, null);
604         assert_fmt(result != 0, "Could not blit Surface: %s\n", SDL_GetError());
605         
606         return Surface(sub);
607     }
608     
609     @nogc
610     package(Dgame) void setAsIconOf(SDL_Window* wnd) nothrow {
611         assert(wnd, "Invalid SDL_Window");
612         assert(_surface, "Invalid SDL_Surface");
613 
614         SDL_SetWindowIcon(wnd, _surface);
615     }
616 
617     @nogc
618     package(Dgame) SDL_Cursor* setAsCursorAt(int hx, int hy) nothrow {
619         return SDL_CreateColorCursor(_surface, hx, hy);
620     }
621 }