1 /** 
2  * Copyright: Enalye
3  * License: Zlib
4  * Authors: Enalye
5  */
6 module atelier.render.canvas;
7 
8 import std.conv : to;
9 
10 import bindbc.sdl;
11 
12 import atelier.core;
13 import atelier.render.window;
14 import atelier.render.texture;
15 import atelier.render.drawable;
16 
17 /// Behave like Texture but you can render onto it.
18 /// Use pushCanvas/popCanvas to start the drawing region on it.
19 final class Canvas : Drawable {
20     private {
21         SDL_Texture* _texture;
22         Vec2i _renderSize;
23         bool _isSmooth = false;
24         Color _color = Color.white;
25         float _alpha = 1f;
26         Blend _blend = Blend.alpha;
27     }
28 
29     package(atelier.render) {
30         bool _isTargetOnStack;
31     }
32 
33     @property {
34         package(atelier) const(SDL_Texture*) target() const {
35             return _texture;
36         }
37 
38         /// loaded ?
39         bool isLoaded() const {
40             return true;
41         }
42 
43         /// Width in texels.
44         uint width() const {
45             return _renderSize.x;
46         }
47         /// Height in texels.
48         uint height() const {
49             return _renderSize.y;
50         }
51 
52         /// The size (in texels) of the surface to be rendered on.
53         /// Changing that value allocate a new texture, so don't do it everytime.
54         Vec2i renderSize() const {
55             return _renderSize;
56         }
57         /// Ditto
58         Vec2i renderSize(Vec2i renderSize_) {
59             if (_isTargetOnStack)
60                 throw new Exception("attempt to resize canvas while being rendered");
61             if (renderSize_.x >= 2048u || renderSize_.y >= 2048u
62                     || renderSize_.x <= 0 || renderSize_.y <= 0)
63                 throw new Exception("canvas render size exceeds limits");
64             _renderSize = renderSize_;
65             if (_texture !is null)
66                 SDL_DestroyTexture(_texture);
67             if (_isSmooth)
68                 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
69             _texture = SDL_CreateTexture(_sdlRenderer, SDL_PIXELFORMAT_RGBA8888,
70                     SDL_TEXTUREACCESS_TARGET, _renderSize.x, _renderSize.y);
71             if (_isSmooth)
72                 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
73             updateCanvasSettings();
74             return _renderSize;
75         }
76 
77         /// Color added to the canvas.
78         Color color() const {
79             return _color;
80         }
81         /// Ditto
82         Color color(Color color_) {
83             _color = color_;
84             auto sdlColor = _color.toSDL();
85             SDL_SetTextureColorMod(_texture, sdlColor.r, sdlColor.g, sdlColor.b);
86             return _color;
87         }
88 
89         /// Alpha
90         float alpha() const {
91             return _alpha;
92         }
93         /// Ditto
94         float alpha(float alpha_) {
95             _alpha = alpha_;
96             SDL_SetTextureAlphaMod(_texture, cast(ubyte)(clamp(_alpha, 0f, 1f) * 255f));
97             return _alpha;
98         }
99 
100         /// Blending algorithm.
101         Blend blend() const {
102             return _blend;
103         }
104         /// Ditto
105         Blend blend(Blend blend_) {
106             _blend = blend_;
107             SDL_SetTextureBlendMode(_texture, getSDLBlend(_blend));
108             return _blend;
109         }
110     }
111 
112     /// The view position inside the canvas.
113     Vec2f position = Vec2f.zero, /// The size of the view inside of the canvas.
114         size = Vec2f.zero;
115     /// The base color when nothing is rendered.
116     Color clearColor = Color.black;
117     /// The base opacity when nothing is rendered.
118     float clearAlpha = 0f;
119     /// Mirroring property.
120     Flip flip = Flip.none;
121 
122     /// Ctor
123     this(Vec2f renderSize_, bool isSmooth_ = false) {
124         this(to!Vec2i(renderSize_), isSmooth_);
125     }
126 
127     /// Ctor
128     this(Vec2i renderSize_, bool isSmooth_ = false) {
129         _isSmooth = isSmooth_;
130         if (renderSize_.x >= 2048u || renderSize_.y >= 2048u
131                 || renderSize_.x <= 0 || renderSize_.y <= 0)
132             throw new Exception("Canvas render size exceeds limits.");
133         _renderSize = renderSize_;
134         if (_isSmooth)
135             SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
136         _texture = SDL_CreateTexture(_sdlRenderer, SDL_PIXELFORMAT_RGBA8888,
137                 SDL_TEXTUREACCESS_TARGET, _renderSize.x, _renderSize.y);
138         if (_isSmooth)
139             SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
140         updateCanvasSettings();
141 
142         size = cast(Vec2f) _renderSize;
143     }
144 
145     /// Ctor
146     this(const Canvas canvas) {
147         _renderSize = canvas._renderSize;
148         size = canvas.size;
149         position = canvas.position;
150         _isSmooth = canvas._isSmooth;
151         clearColor = canvas.clearColor;
152         clearAlpha = canvas.clearAlpha;
153         _blend = canvas._blend;
154         _color = canvas._color;
155         _alpha = canvas._alpha;
156 
157         if (_isSmooth)
158             SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
159         _texture = SDL_CreateTexture(_sdlRenderer, SDL_PIXELFORMAT_RGBA8888,
160                 SDL_TEXTUREACCESS_TARGET, _renderSize.x, _renderSize.y);
161         if (_isSmooth)
162             SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
163         updateCanvasSettings();
164     }
165 
166     ~this() {
167         if (_texture !is null)
168             SDL_DestroyTexture(_texture);
169     }
170 
171     /// Copy
172     Canvas copy(const Canvas canvas) {
173         _renderSize = canvas._renderSize;
174         size = canvas.size;
175         position = canvas.position;
176         _isSmooth = canvas._isSmooth;
177         clearColor = canvas.clearColor;
178         clearAlpha = canvas.clearAlpha;
179         _blend = canvas._blend;
180         _color = canvas._color;
181         _alpha = canvas._alpha;
182 
183         if (_texture !is null)
184             SDL_DestroyTexture(_texture);
185         if (_isSmooth)
186             SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
187         _texture = SDL_CreateTexture(_sdlRenderer, SDL_PIXELFORMAT_RGBA8888,
188                 SDL_TEXTUREACCESS_TARGET, _renderSize.x, _renderSize.y);
189         if (_isSmooth)
190             SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
191         updateCanvasSettings();
192         return this;
193     }
194 
195     private void updateCanvasSettings() {
196         auto sdlColor = _color.toSDL();
197         SDL_SetTextureBlendMode(_texture, getSDLBlend(_blend));
198         SDL_SetTextureColorMod(_texture, sdlColor.r, sdlColor.g, sdlColor.b);
199         SDL_SetTextureAlphaMod(_texture, cast(ubyte)(clamp(_alpha, 0f, 1f) * 255f));
200     }
201 
202     /// Toggle the canvas smoothing
203     void setSmooth(bool isSmooth_) {
204         if (isSmooth_ != _isSmooth) {
205             renderSize(_renderSize);
206         }
207         _isSmooth = isSmooth_;
208     }
209 
210     /// Draw the texture at the specified location.
211     /*void draw(const Vec2f renderPosition) const {
212         draw(renderPosition, 0f);
213     }
214 
215     /// Draw the texture at the specified location.
216     void draw(const Vec2f renderPosition, float angle, Vec2f anchor = Vec2f.half) const {
217         Vec2f pos = transformRenderSpace(renderPosition);
218         const Vec2f scale = transformScale();
219         pos -= anchor * (cast(Vec2f) _renderSize) * scale;
220 
221         SDL_Rect destRect = {
222             cast(uint)(pos.x), cast(uint)(pos.y),
223                 cast(uint)(_renderSize.x * scale.x), cast(uint)(_renderSize.y * scale.y)
224         };
225 
226         SDL_RendererFlip rendererFlip = getSDLFlip(flip);
227         SDL_RenderCopyEx(_sdlRenderer, cast(SDL_Texture*) _texture, null,
228                 &destRect, angle, null, rendererFlip);
229     }
230 
231     /// Draw the texture at the specified location while scaling it.
232     void draw(const Vec2f renderPosition, const Vec2f scale) const {
233         const Vec2f pos = transformRenderSpace(renderPosition);
234         const Vec2f rscale = transformScale() * scale;
235 
236         SDL_Rect destRect = {
237             cast(uint)(pos.x - (_renderSize.x / 2) * rscale.x),
238                 cast(uint)(pos.y - (_renderSize.y / 2) * rscale.y),
239                 cast(uint)(_renderSize.x * rscale.x), cast(uint)(_renderSize.y * rscale.y)
240         };
241 
242         SDL_RendererFlip rendererFlip = getSDLFlip(flip);
243         SDL_RenderCopyEx(cast(SDL_Renderer*) _sdlRenderer,
244                 cast(SDL_Texture*) _texture, null, &destRect, 0f, null, rendererFlip);
245     }
246 
247     /// Draw the part of the texture at the specified location.
248     void draw(Vec2f pos, Vec2f rsize, Vec4i srcRect, float angle, Vec2f anchor = Vec2f.half) const {
249         pos -= anchor * rsize;
250 
251         SDL_Rect srcSdlRect = srcRect.toSdlRect();
252         SDL_Rect destSdlRect = {
253             cast(uint) pos.x, cast(uint) pos.y, cast(uint) rsize.x, cast(uint) rsize.y
254         };
255 
256         SDL_RendererFlip rendererFlip = getSDLFlip(flip);
257         SDL_RenderCopyEx(cast(SDL_Renderer*) _sdlRenderer, cast(SDL_Texture*) _texture,
258                 &srcSdlRect, &destSdlRect, angle, null, rendererFlip);
259     }*/
260 
261     /// Render the whole texture here
262     void draw(Vec2f pos, Vec2f anchor = Vec2f.half) const {
263         pos -= anchor * Vec2f(_renderSize.x, _renderSize.y);
264 
265         SDL_Rect destRect = {cast(uint) pos.x, cast(uint) pos.y, _renderSize.x, _renderSize.y};
266 
267         SDL_RenderCopy(cast(SDL_Renderer*) _sdlRenderer,
268                 cast(SDL_Texture*) _texture, null, &destRect);
269     }
270 
271     /// Render a section of the texture here
272     void draw(Vec2f pos, Vec4i srcRect, Vec2f anchor = Vec2f.half) const {
273         pos -= anchor * cast(Vec2f) srcRect.zw;
274 
275         SDL_Rect srcSdlRect = srcRect.toSdlRect();
276         SDL_Rect destSdlRect = {
277             cast(uint) pos.x, cast(uint) pos.y, srcSdlRect.w, srcSdlRect.h
278         };
279 
280         SDL_RenderCopy(cast(SDL_Renderer*) _sdlRenderer,
281                 cast(SDL_Texture*) _texture, &srcSdlRect, &destSdlRect);
282     }
283 
284     /// Render the whole texture here
285     void draw(Vec2f pos, Vec2f size, Vec2f anchor = Vec2f.half) const {
286         pos -= anchor * size;
287 
288         SDL_Rect destSdlRect = {
289             cast(uint) pos.x, cast(uint) pos.y, cast(uint) size.x, cast(uint) size.y
290         };
291 
292         SDL_RenderCopy(cast(SDL_Renderer*) _sdlRenderer,
293                 cast(SDL_Texture*) _texture, null, &destSdlRect);
294     }
295 
296     /// Render a section of the texture here
297     void draw(Vec2f pos, Vec2f size, Vec4i srcRect, Vec2f anchor = Vec2f.half) const {
298         pos -= anchor * size;
299 
300         SDL_Rect srcSdlRect = srcRect.toSdlRect();
301         SDL_Rect destSdlRect = {
302             cast(uint) pos.x, cast(uint) pos.y, cast(uint) size.x, cast(uint) size.y
303         };
304 
305         SDL_RenderCopy(cast(SDL_Renderer*) _sdlRenderer,
306                 cast(SDL_Texture*) _texture, &srcSdlRect, &destSdlRect);
307     }
308 
309     /// Render a section of the texture here
310     void draw(Vec2f pos, Vec2f size, Vec4i srcRect, float angle,
311             Flip flip = Flip.none, Vec2f anchor = Vec2f.half) const {
312         pos -= anchor * size;
313 
314         SDL_Rect srcSdlRect = srcRect.toSdlRect();
315         SDL_Rect destSdlRect = {
316             cast(uint) pos.x, cast(uint) pos.y, cast(uint) size.x, cast(uint) size.y
317         };
318 
319         const SDL_RendererFlip rendererFlip = getSDLFlip(flip);
320         SDL_RenderCopyEx(cast(SDL_Renderer*) _sdlRenderer, cast(SDL_Texture*) _texture,
321                 &srcSdlRect, &destSdlRect, angle, null, rendererFlip);
322     }
323 }