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 }