1 /** 
2  * Copyright: Enalye
3  * License: Zlib
4  * Authors: Enalye
5  */
6 module atelier.render.texture;
7 
8 import std..string;
9 import std.exception;
10 import std.algorithm.comparison : clamp;
11 
12 import bindbc.sdl, bindbc.sdl.image;
13 
14 import atelier.core;
15 import atelier.render.window, atelier.render.drawable;
16 
17 /// Returns the SDL blend flag.
18 package SDL_BlendMode getSDLBlend(Blend blend) {
19     final switch (blend) with (Blend) {
20     case alpha:
21         return SDL_BLENDMODE_BLEND;
22     case additive:
23         return SDL_BLENDMODE_ADD;
24     case modular:
25         return SDL_BLENDMODE_MOD;
26     case none:
27         return SDL_BLENDMODE_NONE;
28     }
29 }
30 
31 /// Base rendering class.
32 final class Texture : Drawable {
33     private {
34         bool _isLoaded = false, _ownData, _isSmooth;
35         SDL_Texture* _texture = null;
36         SDL_Surface* _surface = null;
37         uint _width, _height;
38         Color _color = Color.white;
39         float _alpha = 1f;
40         Blend _blend = Blend.alpha;
41     }
42 
43     @property {
44         /// loaded ?
45         bool isLoaded() const {
46             return _isLoaded;
47         }
48         /// Width in texels.
49         uint width() const {
50             return _width;
51         }
52         /// Height in texels.
53         uint height() const {
54             return _height;
55         }
56 
57         /// Color added to the canvas.
58         Color color() const {
59             return _color;
60         }
61         /// Ditto
62         Color color(Color color_) {
63             _color = color_;
64             auto sdlColor = _color.toSDL();
65             SDL_SetTextureColorMod(_texture, sdlColor.r, sdlColor.g, sdlColor.b);
66             return _color;
67         }
68 
69         /// Alpha
70         float alpha() const {
71             return _alpha;
72         }
73         /// Ditto
74         float alpha(float alpha_) {
75             _alpha = alpha_;
76             SDL_SetTextureAlphaMod(_texture, cast(ubyte)(clamp(_alpha, 0f, 1f) * 255f));
77             return _alpha;
78         }
79 
80         /// Blending algorithm.
81         Blend blend() const {
82             return _blend;
83         }
84         /// Ditto
85         Blend blend(Blend blend_) {
86             _blend = blend_;
87             SDL_SetTextureBlendMode(_texture, getSDLBlend(_blend));
88             return _blend;
89         }
90     }
91 
92     /// Ctor
93     this(const Texture texture) {
94         _isLoaded = texture._isLoaded;
95         _texture = cast(SDL_Texture*) texture._texture;
96         _width = texture._width;
97         _height = texture._height;
98         _isSmooth = texture._isSmooth;
99         _blend = texture._blend;
100         _color = texture._color;
101         _alpha = texture._alpha;
102         _ownData = false;
103     }
104 
105     /// Ctor
106     this(SDL_Surface* surface, bool preload_ = false, bool isSmooth_ = false) {
107         _isSmooth = isSmooth_;
108         if (preload_) {
109             _surface = surface;
110             _width = _surface.w;
111             _height = _surface.h;
112         }
113         else
114             load(surface);
115     }
116 
117     /// Ctor
118     this(string path, bool preload_ = false, bool isSmooth_ = false) {
119         _isSmooth = isSmooth_;
120         if (preload_) {
121             _surface = IMG_Load(toStringz(path));
122             _width = _surface.w;
123             _height = _surface.h;
124             _ownData = true;
125         }
126         else
127             load(path);
128     }
129 
130     ~this() {
131         unload();
132     }
133 
134     /// Call it if you set the preload flag on ctor.
135     void postload() {
136         enforce(null != _surface, "Invalid surface.");
137         enforce(null != _sdlRenderer, "The renderer does not exist.");
138 
139         if (null != _texture)
140             SDL_DestroyTexture(_texture);
141         if (_isSmooth)
142             SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
143         _texture = SDL_CreateTextureFromSurface(_sdlRenderer, _surface);
144         enforce(null != _texture, "Error occurred while converting a surface to a texture format.");
145         if (_isSmooth)
146             SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
147         updateSettings();
148 
149         if (_ownData)
150             SDL_FreeSurface(_surface);
151         _surface = null;
152         _isLoaded = true;
153     }
154 
155     package void load(SDL_Surface* surface) {
156         enforce(null != surface, "Invalid surface.");
157         enforce(null != _sdlRenderer, "The renderer does not exist.");
158 
159         if (null != _texture)
160             SDL_DestroyTexture(_texture);
161 
162         if (_isSmooth)
163             SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
164         _texture = SDL_CreateTextureFromSurface(_sdlRenderer, surface);
165         if (_isSmooth)
166             SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
167 
168         enforce(null != _texture, "Error occurred while converting a surface to a texture format.");
169         updateSettings();
170 
171         _width = surface.w;
172         _height = surface.h;
173 
174         _isLoaded = true;
175         _ownData = true;
176     }
177 
178     /// Load from file
179     void load(string path) {
180         SDL_Surface* surface = IMG_Load(toStringz(path));
181 
182         enforce(null != surface, "Cannot load image file \'" ~ path ~ "\'.");
183         enforce(null != _sdlRenderer, "The renderer does not exist.");
184 
185         if (_isSmooth)
186             SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
187         _texture = SDL_CreateTextureFromSurface(_sdlRenderer, surface);
188         if (_isSmooth)
189             SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
190 
191         if (null == _texture)
192             throw new Exception(
193                     "Error occurred while converting \'" ~ path ~ "\' to a texture format.");
194         updateSettings();
195 
196         _width = surface.w;
197         _height = surface.h;
198         SDL_FreeSurface(surface);
199 
200         _isLoaded = true;
201         _ownData = true;
202     }
203 
204     /// Free image data
205     void unload() {
206         if (!_ownData)
207             return;
208         if (null != _texture)
209             SDL_DestroyTexture(_texture);
210         _isLoaded = false;
211     }
212 
213     private void updateSettings() {
214         auto sdlColor = _color.toSDL();
215         SDL_SetTextureBlendMode(_texture, getSDLBlend(_blend));
216         SDL_SetTextureColorMod(_texture, sdlColor.r, sdlColor.g, sdlColor.b);
217         SDL_SetTextureAlphaMod(_texture, cast(ubyte)(clamp(_alpha, 0f, 1f) * 255f));
218     }
219 
220     /// Render the whole texture here
221     void draw(Vec2f pos, Vec2f anchor = Vec2f.half) const {
222         assert(_isLoaded, "Cannot render the texture: Asset not loaded.");
223         pos -= anchor * Vec2f(_width, _height);
224 
225         SDL_Rect destRect = {cast(uint) pos.x, cast(uint) pos.y, _width, _height};
226 
227         SDL_RenderCopy(cast(SDL_Renderer*) _sdlRenderer,
228                 cast(SDL_Texture*) _texture, null, &destRect);
229     }
230 
231     /// Render a section of the texture here
232     void draw(Vec2f pos, Vec4i srcRect, Vec2f anchor = Vec2f.half) const {
233         assert(_isLoaded, "Cannot render the texture: Asset not loaded.");
234         pos -= anchor * cast(Vec2f) srcRect.zw;
235 
236         SDL_Rect srcSdlRect = srcRect.toSdlRect();
237         SDL_Rect destSdlRect = {
238             cast(uint) pos.x, cast(uint) pos.y, srcSdlRect.w, srcSdlRect.h
239         };
240 
241         SDL_RenderCopy(cast(SDL_Renderer*) _sdlRenderer,
242                 cast(SDL_Texture*) _texture, &srcSdlRect, &destSdlRect);
243     }
244 
245     /// Render the whole texture here
246     void draw(Vec2f pos, Vec2f size, Vec2f anchor = Vec2f.half) const {
247         assert(_isLoaded, "Cannot render the texture: Asset not loaded.");
248         pos -= anchor * size;
249 
250         SDL_Rect destSdlRect = {
251             cast(uint) pos.x, cast(uint) pos.y, cast(uint) size.x, cast(uint) size.y
252         };
253 
254         SDL_RenderCopy(cast(SDL_Renderer*) _sdlRenderer,
255                 cast(SDL_Texture*) _texture, null, &destSdlRect);
256     }
257 
258     /// Render a section of the texture here
259     void draw(Vec2f pos, Vec2f size, Vec4i srcRect, Vec2f anchor = Vec2f.half) const {
260         enforce(_isLoaded, "Cannot render the texture: Asset not loaded.");
261         pos -= anchor * size;
262 
263         SDL_Rect srcSdlRect = srcRect.toSdlRect();
264         SDL_Rect destSdlRect = {
265             cast(uint) pos.x, cast(uint) pos.y, cast(uint) size.x, cast(uint) size.y
266         };
267 
268         SDL_RenderCopy(cast(SDL_Renderer*) _sdlRenderer,
269                 cast(SDL_Texture*) _texture, &srcSdlRect, &destSdlRect);
270     }
271 
272     /// Render a section of the texture here
273     void draw(Vec2f pos, Vec2f size, Vec4i srcRect, float angle,
274             Flip flip = Flip.none, Vec2f anchor = Vec2f.half) const {
275         assert(_isLoaded, "Cannot render the texture: Asset not loaded.");
276         pos -= anchor * size;
277 
278         SDL_Rect srcSdlRect = srcRect.toSdlRect();
279         SDL_Rect destSdlRect = {
280             cast(uint) pos.x, cast(uint) pos.y, cast(uint) size.x, cast(uint) size.y
281         };
282 
283         const SDL_RendererFlip rendererFlip = getSDLFlip(flip);
284         SDL_RenderCopyEx(cast(SDL_Renderer*) _sdlRenderer, cast(SDL_Texture*) _texture,
285                 &srcSdlRect, &destSdlRect, angle, null, rendererFlip);
286     }
287 }