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 }