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.Graphics.Surface; 25 26 private { 27 debug import std.stdio : writefln, writeln; 28 import std..string : format, toStringz; 29 import std.file : exists; 30 import std.conv : to; 31 import std.algorithm : reverse; 32 import std.exception : enforce; 33 import core.stdc..string : memcpy; 34 35 import derelict.sdl2.sdl; 36 import derelict.sdl2.image; 37 38 import Dgame.Internal.Shared; 39 40 import Dgame.Math.Rect; 41 import Dgame.Math.Vector2; 42 import Dgame.Graphics.Color; 43 } 44 45 /** 46 * Surface is a wrapper for a SDL_Surface. 47 * 48 * Author: rschuett 49 */ 50 struct Surface { 51 /** 52 * Supported BlendModes 53 */ 54 enum BlendMode : ubyte { 55 None = SDL_BLENDMODE_NONE, /** no blending */ 56 Blend = SDL_BLENDMODE_BLEND, /** dst = (src * A) + (dst * (1-A)) */ 57 Add = SDL_BLENDMODE_ADD, /** dst = (src * A) + dst */ 58 Mod = SDL_BLENDMODE_MOD /** dst = src * dst */ 59 } 60 61 /** 62 * Supported Color Masks 63 */ 64 enum Mask : ubyte { 65 Red = 1, /** Red Mask */ 66 Green = 2, /** Green Mask */ 67 Blue = 4, /** Blue Mask */ 68 Alpha = 8 /** Alpha Mask */ 69 } 70 71 enum RMask = 0; /** Default Red Mask. */ 72 enum GMask = 0; /** Default Green Mask. */ 73 enum BMask = 0; /** Default Blue Mask. */ 74 75 version(LittleEndian) { 76 enum AMask = 0xff000000; 77 } else { 78 enum AMask = 0x000000ff; 79 } 80 81 /** 82 * Flip mode 83 */ 84 enum Flip : ubyte { 85 Vertical = 1, /** Vertical Flip */ 86 Horizontal = 2 /** Horizontal Flip */ 87 } 88 89 private: 90 shared_ptr!(SDL_Surface) _target; 91 string _filename; 92 93 private: 94 /** 95 * Create a new SDL_Surface* of the given width, height and depth. 96 */ 97 static SDL_Surface* create(ushort width, ushort height, ubyte depth = 32) in { 98 assert(depth >= 8 && depth <= 32, "Invalid depth."); 99 } body { 100 return SDL_CreateRGBSurface(0, width, height, depth, RMask, GMask, BMask, AMask); 101 } 102 103 /** 104 * Create a new SDL_Surface* of the given memory, width, height and depth. 105 */ 106 static SDL_Surface* create(void* memory, ushort width, ushort height, ubyte depth = 32) in { 107 assert(memory !is null, "Memory is empty."); 108 assert(depth >= 8 && depth <= 32, "Invalid depth."); 109 } body { 110 return SDL_CreateRGBSurfaceFrom(memory, width, height, depth, (depth / 8) * width, 111 RMask, GMask, BMask, AMask); 112 } 113 114 /** 115 * CTor 116 */ 117 this(SDL_Surface* srfc) in { 118 assert(srfc !is null, "Invalid SDL_Surface."); 119 assert(srfc.pixels !is null, "Invalid pixel data."); 120 } body { 121 debug writeln("CTor Surface with SDL_Surface: ", srfc); 122 this._target = make_shared(srfc, (SDL_Surface* ptr) => SDL_FreeSurface(ptr)); 123 } 124 125 public: 126 /** 127 * CTor 128 */ 129 this(string filename) { 130 debug writeln("CTor Surface : ", filename); 131 this.loadFromFile(filename); 132 } 133 134 debug(Dgame) 135 this(this) { 136 writeln("Postblit Surface: ", this._target.usage, ':', this.filename); 137 } 138 139 debug(Dgame) 140 ~this() { 141 writeln("DTor Surface", ':', this.filename, "::",this._target.usage); 142 } 143 144 /** 145 * Destroy the current Surface <b>and all</b>, which are linked to this Surface</b>. 146 */ 147 void free() { 148 debug writeln("Free Surface:", this.filename); 149 this._target.terminate(); 150 } 151 152 /** 153 * Returns the current use count 154 */ 155 int useCount() const pure nothrow { 156 return this._target.usage; 157 } 158 159 /** 160 * Make a new Surface of the given width, height and depth. 161 */ 162 static Surface make(ushort width, ushort height, ubyte depth = 32) { 163 SDL_Surface* srfc = Surface.create(width, height, depth); 164 if (srfc is null) { 165 const string err = to!string(SDL_GetError()); 166 throw new Exception("Surface konnte nicht erstellt werden: " ~ err); 167 } 168 169 return Surface(srfc); 170 } 171 172 /** 173 * Make an new Surface of the given memory, width, height and depth. 174 */ 175 static Surface make(void* memory, ushort width, ushort height, ubyte depth = 32) { 176 SDL_Surface* srfc = Surface.create(memory, width, height, depth); 177 if (srfc is null) { 178 const string err = to!string(SDL_GetError()); 179 throw new Exception("Surface konnte nicht erstellt werden: " ~ err); 180 } 181 182 return Surface(srfc); 183 } 184 185 /** 186 * Returns if the Surface is valid. Which means that the Surface has valid data. 187 */ 188 bool isValid() const pure nothrow { 189 return this._target.isValid() && this._target.pixels !is null; 190 } 191 192 /** 193 * Load from filename. If any data is already stored, the data will be freed. 194 */ 195 void loadFromFile(string filename) { 196 debug writefln("Load Image: %s", filename); 197 enforce(filename.length >= 4 && exists(filename), 198 "The file " ~ filename ~ " does not exist."); 199 200 SDL_Surface* srfc = IMG_Load(toStringz(filename)); 201 debug writefln("Image %s loaded :: %X", filename, srfc); 202 if (srfc is null) { 203 const string err = to!string(SDL_GetError()); 204 throw new Exception(.format("Could not load image %s. Error: %s.", filename, err)); 205 } 206 207 this._target = make_shared(srfc, (SDL_Surface* ptr) => SDL_FreeSurface(ptr)); 208 this._filename = filename; 209 } 210 211 /** 212 * Load from memory. 213 */ 214 void loadFromMemory(void* memory, ushort width, ushort height, ubyte depth = 32) in { 215 assert(memory !is null, "Memory is empty."); 216 assert(depth >= 8 && depth <= 32, "Invalid depth."); 217 } body { 218 SDL_Surface* srfc = SDL_CreateRGBSurfaceFrom(memory, width, height, depth, 219 (depth / 8) * width, 220 RMask, GMask, BMask, AMask); 221 222 if (srfc is null) { 223 const string err = to!string(SDL_GetError()); 224 throw new Exception("Could not load image. Error: " ~ err); 225 } 226 227 this._target = make_shared(srfc, (SDL_Surface* ptr) => SDL_FreeSurface(ptr)); 228 } 229 230 /** 231 * Save the current pixel data to the file. 232 */ 233 void saveToFile(string filename) { 234 enforce(filename.length >= 4, "File name is too short."); 235 236 if (SDL_SaveBMP(this.ptr, toStringz(filename)) != 0) { 237 const string err = to!string(SDL_GetError()); 238 throw new Exception(.format("Could not save image %s. Error: %s.", filename, err)); 239 } 240 } 241 242 /** 243 * Fills a specific area of the surface with the given color. 244 * The second parameter is a pointer to the area. 245 * If it's null, the whole Surface is filled. 246 */ 247 void fill(ref const Color col, const ShortRect* rect = null) { 248 SDL_Rect a = void; 249 const SDL_Rect* ptr = transfer(rect, &a); 250 251 const uint key = SDL_MapRGBA(this._target.format, col.red, col.green, col.blue, col.alpha); 252 253 SDL_FillRect(this._target, ptr, key); 254 } 255 256 /** 257 * Rvalue version 258 */ 259 void fill(const Color col, const ShortRect* rect = null) { 260 this.fill(col, rect); 261 } 262 263 /** 264 * Fills multiple areas of the Surface with the given color. 265 */ 266 void fillAreas(ref const Color col, const ShortRect[] rects) { 267 SDL_Rect a = void; 268 const SDL_Rect* ptr_start = (rects.length > 0) ? transfer(&rects[0], &a) : null; 269 const uint key = SDL_MapRGBA(this._target.format, col.red, col.green, col.blue, col.alpha); 270 271 SDL_FillRects(this._target, ptr_start, cast(uint) rects.length, key); 272 } 273 274 /** 275 * Rvalue version 276 */ 277 void fillAreas(const Color col, const ShortRect[] rects) { 278 this.fillAreas(col, rects); 279 } 280 281 /** 282 * Use this function to set the RLE acceleration hint for a surface. 283 * RLE (Run-Length-Encoding) is a way of compressing data. 284 * If RLE is enabled, color key and alpha blending blits are much faster, 285 * but the surface must be locked before directly accessing the pixels. 286 * 287 * Returns: whether the call succeeded or not 288 */ 289 bool optimizeRLE(bool enable) { 290 return SDL_SetSurfaceRLE(this._target, enable) == 0; 291 } 292 293 /** 294 * Use this function to set up a surface for directly accessing the pixels. 295 * 296 * Returns: whether the call succeeded or not 297 */ 298 bool lock() { 299 if (SDL_LockSurface(this._target) == 0) 300 return true; 301 return false; 302 } 303 304 /** 305 * Use this function to release a surface after directly accessing the pixels. 306 */ 307 void unlock() { 308 SDL_UnlockSurface(this._target); 309 } 310 311 /** 312 * Returns whether this Surface is locked or not. 313 */ 314 bool isLocked() const pure nothrow { 315 return this._target.locked != 0; 316 } 317 318 /** 319 * Use this function to determine whether a surface must be locked for access. 320 */ 321 bool mustLock() { 322 return SDL_MUSTLOCK(this._target) == SDL_TRUE; 323 } 324 325 /** 326 * Use this function to adapt the format of another Surface to this surface. 327 * Works like <code>SDL_DisplayFormat</code>. 328 */ 329 void adaptTo(ref Surface srfc) in { 330 assert(srfc.isValid(), "Could not adapt to invalid surface."); 331 assert(this.isValid(), "Could not adapt a invalid surface."); 332 } body { 333 this.adaptTo(srfc.ptr.format); 334 } 335 336 /** 337 * Use this function to adapt the format of another Surface to this surface. 338 * Works like <code>SLD_DisplayFormat</code>. 339 */ 340 void adaptTo(SDL_PixelFormat* fmt) in { 341 assert(fmt !is null, "Null format is invalid."); 342 } body { 343 SDL_Surface* adapted = SDL_ConvertSurface(this._target, fmt, 0); 344 enforce(adapted !is null, "Could not adapt surface."); 345 346 this._target = make_shared(adapted, (SDL_Surface* ptr) => SDL_FreeSurface(ptr)); 347 } 348 349 /** 350 * Set the colorkey. 351 */ 352 void setColorkey(ref const Color col) { 353 this.setColorkey(col.red, col.green, col.blue, col.alpha); 354 } 355 356 /** 357 * Rvalue version 358 */ 359 void setColorkey(const Color col) { 360 this.setColorkey(col); 361 } 362 363 /** 364 * Set the colorkey. 365 */ 366 void setColorkey(ubyte red, ubyte green, ubyte blue) { 367 const uint key = SDL_MapRGB(this._target.format, red, green, blue); 368 SDL_SetColorKey(this._target, SDL_TRUE, key); 369 } 370 371 /** 372 * Set the colorkey. 373 */ 374 void setColorkey(ubyte red, ubyte green, ubyte blue, ubyte alpha) { 375 const uint key = SDL_MapRGBA(this._target.format, red, green, blue, alpha); 376 SDL_SetColorKey(this._target, SDL_TRUE, key); 377 } 378 379 /** 380 * Returns the current colorkey. 381 */ 382 Color getColorkey() { 383 uint key = 0; 384 SDL_GetColorKey(this._target, &key); 385 386 ubyte r, g, b, a; 387 SDL_GetRGBA(key, this._target.format, &r, &g, &b, &a); 388 389 return Color(r, g, b, a); 390 } 391 392 /** 393 * Set the Alpha mod. 394 */ 395 void setAlphaMod(ubyte alpha) { 396 SDL_SetSurfaceAlphaMod(this._target, alpha); 397 } 398 399 /** 400 * Returns the current Alpha mod. 401 */ 402 ubyte getAlphaMod() { 403 ubyte alpha; 404 SDL_GetSurfaceAlphaMod(this._target, &alpha); 405 406 return alpha; 407 } 408 409 /** 410 * Set the Blendmode. 411 */ 412 void setBlendMode(BlendMode mode) { 413 SDL_SetSurfaceBlendMode(this._target, mode); 414 } 415 416 /** 417 * Returns the current Blendmode. 418 */ 419 BlendMode getBlendMode() { 420 SDL_BlendMode mode; 421 SDL_GetSurfaceBlendMode(this._target, &mode); 422 423 return cast(BlendMode) mode; 424 } 425 426 /** 427 * Returns the clip rect of this surface. 428 * The clip rect is the area of the surface which is drawn. 429 */ 430 ShortRect getClipRect() { 431 SDL_Rect clip = void; 432 SDL_GetClipRect(this._target, &clip); 433 434 return ShortRect(clip); 435 } 436 437 /** 438 * Set the clip rect. 439 */ 440 void setClipRect(ref const ShortRect clip) { 441 SDL_Rect a = void; 442 clip.transferTo(&a); 443 444 SDL_SetClipRect(this._target, &a); 445 } 446 447 /** 448 * Rvalue version 449 */ 450 void setClipRect(const ShortRect clip) { 451 this.setClipRect(clip); 452 } 453 454 @property { 455 /** 456 * Returns the current filename, if any 457 */ 458 string filename() const pure nothrow { 459 return this._filename; 460 } 461 462 /** 463 * Returns the width. 464 */ 465 ushort width() const pure nothrow { 466 return this._target.isValid() ? cast(ushort) this._target.w : 0; 467 } 468 469 /** 470 * Returns the height. 471 */ 472 ushort height() const pure nothrow { 473 return this._target.isValid() ? cast(ushort) this._target.h : 0; 474 } 475 476 /** 477 * Returns the pixel data of this surface. 478 */ 479 inout(void*) pixels() inout { 480 return this._target.isValid() ? this._target.pixels : null; 481 } 482 483 /** 484 * Count the bits of this surface. 485 * Could be 32, 24, 16, 8, 0. 486 */ 487 ubyte bits() const pure nothrow { 488 return this._target.isValid() ? this._target.format.BitsPerPixel : 0; 489 } 490 491 /** 492 * Count the bytes of this surface. 493 * Could be 4, 3, 2, 1, 0. (countBits / 8) 494 */ 495 ubyte bytes() const pure nothrow { 496 return this._target.isValid() ? this._target.format.BytesPerPixel : 0; 497 } 498 499 /** 500 * Returns the Surface pitch or 0. 501 */ 502 int pitch() const pure nothrow { 503 return this._target.isValid() ? this._target.pitch : 0; 504 } 505 506 /** 507 * Returns the PixelFormat 508 */ 509 const(SDL_PixelFormat*) pixelFormat() const pure nothrow { 510 return this._target.format; 511 } 512 } 513 514 /** 515 * Returns if the given color match the color of the given mask of the surface. 516 * 517 * See: Surface.Mask enum. 518 */ 519 bool isMask(Mask mask, ref const Color col) const { 520 const uint map = SDL_MapRGBA(this._target.format, col.red, col.green, col.blue, col.alpha); 521 522 return this.isMask(mask, map); 523 } 524 525 /** 526 * Rvalue version 527 */ 528 bool isMask(Mask mask, const Color col) const { 529 return this.isMask(mask, col); 530 } 531 532 /** 533 * Returns if the given converted color match the color of the given mask of the surface. 534 * 535 * See: Surface.Mask enum. 536 */ 537 bool isMask(Mask mask, uint col) const pure nothrow { 538 bool[4] result = void; 539 ubyte index = 0; 540 541 if (mask & Mask.Red) 542 result[index++] = this._target.format.Rmask == col; 543 if (mask & Mask.Green) 544 result[index++] = this._target.format.Gmask == col; 545 if (mask & Mask.Blue) 546 result[index++] = this._target.format.Bmask == col; 547 if (mask & Mask.Alpha) 548 result[index++] = this._target.format.Amask == col; 549 550 for (ubyte i = 0; i < index; ++i) { 551 if (!result[i]) 552 return false; 553 } 554 555 return true; 556 } 557 558 /** 559 * Returns the pixel at the given coordinates. 560 */ 561 uint getPixelAt(ref const Vector2s pos) const { 562 return this.getPixelAt(pos.x, pos.y); 563 } 564 565 /** 566 * Returns the pixel at the given coordinates. 567 */ 568 uint getPixelAt(ushort x, ushort y) const { 569 uint* pixels = cast(uint*) this.pixels; 570 enforce(pixels !is null, "No pixel at this point."); 571 572 return pixels[(y * this._target.w) + x]; 573 } 574 575 /** 576 * Put a new pixel at the given coordinates. 577 */ 578 void putPixelAt(ref const Vector2s pos, uint pixel) { 579 this.putPixelAt(pos.x, pos.y, pixel); 580 } 581 582 /** 583 * Put a new pixel at the given coordinates. 584 */ 585 void putPixelAt(ushort x, ushort y, uint pixel) { 586 uint* pixels = cast(uint*) this.pixels; 587 enforce(pixels !is null, "No pixel at this point."); 588 589 pixels[(y * this._target.w) + x] = pixel; 590 } 591 592 /** 593 * Returns the color on the given position. 594 */ 595 Color getColorAt(ref const Vector2s pos) const { 596 return this.getColorAt(pos.x, pos.y); 597 } 598 599 /** 600 * Returns the color on the given position. 601 */ 602 Color getColorAt(ushort x, ushort y) const { 603 const uint len = this.width * this.height; 604 605 if ((x * y) <= len) { 606 const uint pixel = this.getPixelAt(x, y); 607 608 ubyte r, g, b, a; 609 SDL_GetRGBA(pixel, this._target.format, &r, &g, &b, &a); 610 611 return Color(r, g, b, a); 612 } 613 614 enforce(len != 0, "Invalid Surface for getColorAt."); 615 616 throw new Exception("No color at this position."); 617 } 618 619 /** 620 * Returns a pointer to the SDL_Surface 621 */ 622 @property 623 inout(SDL_Surface)* ptr() inout pure nothrow { 624 return this._target.ptr; 625 } 626 627 /** 628 * Use this function to perform a fast, low quality, 629 * stretch blit between two surfaces of the same pixel format. 630 * src is the a pointer to a Rect structure which represents the rectangle to be copied, 631 * or null to copy the entire surface. 632 * dst is a pointer to a Rect structure which represents the rectangle that is copied into. 633 * null means, that the whole srfc is copied to (0|0). 634 */ 635 bool blitScaled(ref Surface srfc, const ShortRect* src = null, ShortRect* dst = null) { 636 return this.blitScaled(srfc.ptr, src, dst); 637 } 638 639 /** 640 * Same as above, but with a SDL_Surface* instead of a Surface. 641 */ 642 bool blitScaled(SDL_Surface* srfc, const ShortRect* src = null, ShortRect* dst = null) in { 643 assert(srfc !is null, "Null surface cannot be blit."); 644 } body { 645 SDL_Rect a = void; 646 SDL_Rect b = void; 647 648 const SDL_Rect* src_ptr = transfer(src, &a); 649 SDL_Rect* dst_ptr = transfer(dst, &b); 650 651 return SDL_BlitScaled(srfc, src_ptr, this._target, dst_ptr) == 0; 652 } 653 654 /** 655 * Use this function to perform low-level surface blitting only. 656 */ 657 bool lowerBlit(ref Surface srfc, ShortRect* src = null, ShortRect* dst = null) { 658 return this.lowerBlit(srfc.ptr, src, dst); 659 } 660 661 /** 662 * Same as above, but with a SDL_Surface* instead of a Surface. 663 */ 664 bool lowerBlit(SDL_Surface* srfc, ShortRect* src = null, ShortRect* dst = null) in { 665 assert(srfc !is null, "Null surface cannot be blit."); 666 } body { 667 SDL_Rect a = void; 668 SDL_Rect b = void; 669 670 SDL_Rect* src_ptr = transfer(src, &a); 671 SDL_Rect* dst_ptr = transfer(dst, &b); 672 673 return SDL_LowerBlit(srfc, src_ptr, this._target, dst_ptr) == 0; 674 } 675 676 /** 677 * Use this function to perform a fast blit from the source surface to the this surface. 678 * src is the a pointer to a Rect structure which represents the rectangle to be copied, 679 * or null to copy the entire surface. 680 * dst is a pointer to a Rect structure which represents the rectangle that is copied into. 681 * null means, that the whole srfc is copied to (0|0). 682 */ 683 bool blit(ref Surface srfc, const ShortRect* src = null, ShortRect* dst = null) { 684 return this.blit(srfc.ptr, src, dst); 685 } 686 687 /** 688 * Same as above, but with a SDL_Surface* instead of a Surface. 689 */ 690 bool blit(SDL_Surface* srfc, const ShortRect* src = null, ShortRect* dst = null) in { 691 assert(srfc !is null, "Null surface cannot be blit."); 692 } body { 693 SDL_Rect a = void; 694 SDL_Rect b = void; 695 696 const SDL_Rect* src_ptr = transfer(src, &a); 697 SDL_Rect* dst_ptr = transfer(dst, &b); 698 699 return SDL_BlitSurface(srfc, src_ptr, this._target, dst_ptr) == 0; 700 } 701 702 /** 703 * Returns a subsurface from this surface. rect represents the viewport. 704 * The subsurface is a separate Surface object. 705 */ 706 Surface subSurface(ref const ShortRect rect) { 707 SDL_Surface* sub = this.create(rect.width, rect.height); 708 enforce(sub !is null, "Failed to construct a sub surface."); 709 710 SDL_Rect clip = void; 711 rect.transferTo(&clip); 712 713 if (SDL_BlitSurface(this._target, &clip, sub, null) != 0) 714 throw new Exception("An error occured by blitting the subsurface."); 715 716 return Surface(sub); 717 } 718 719 /** 720 * Rvalue version 721 */ 722 Surface subSurface(const ShortRect rect) { 723 return this.subSurface(rect); 724 } 725 726 /** 727 * Returns an new flipped Surface 728 * The current Surface is not modified. 729 * 730 * Note: This function may be slow 731 */ 732 Surface flip(Flip flip) { 733 ubyte* pixels = cast(ubyte*) this.pixels; 734 735 const ubyte bytes = this.bytes; 736 const uint memSize = this.width * this.height * bytes; 737 738 Surface flipped = Surface.make(this.width, this.height); 739 ubyte* newPixels = cast(ubyte*) flipped.pixels; 740 741 final switch (flip) { 742 case Flip.Vertical: 743 const uint rowSize = this.width * bytes; 744 745 ubyte* source = &pixels[this.width * (this.height - 1) * bytes]; 746 ubyte* dest = &newPixels[0]; 747 748 for (ushort y = 0; y < this.height; ++y) { 749 .memcpy(dest, source, rowSize); 750 //std.algorithm.reverse(dest[0 .. rowSize]); 751 source -= rowSize; 752 dest += rowSize; 753 } 754 break; 755 case Flip.Horizontal: 756 for (ushort y = 0; y < this.height; ++y) { 757 ubyte* source = &pixels[y * this.width * bytes]; 758 ubyte* dest = &newPixels[(y + 1) * this.width * bytes - bytes]; 759 760 for (ushort x = 0; x < this.width; ++x) { 761 dest[0] = source[0]; 762 dest[1] = source[1]; 763 dest[2] = source[2]; 764 if (bytes == 4) 765 dest[3] = source[3]; 766 767 source += bytes; 768 dest -= bytes; 769 } 770 } 771 break; 772 case Flip.Vertical | Flip.Horizontal: 773 newPixels[0 .. memSize] = pixels[0 .. memSize]; 774 .reverse(newPixels[0 .. memSize]); 775 break; 776 } 777 778 return flipped; 779 } 780 } unittest { 781 writeln("<Surface unittest>"); 782 783 Surface s1 = Surface.make(64, 64, 32); 784 785 assert(s1.useCount() == 1, to!string(s1.useCount())); 786 { 787 Surface s2 = s1; 788 789 assert(s1.useCount() == 2, to!string(s1.useCount())); 790 assert(s2.useCount() == 2, to!string(s2.useCount())); 791 792 s2 = s1; 793 794 assert(s1.useCount() == 2, to!string(s1.useCount())); 795 assert(s2.useCount() == 2, to!string(s2.useCount())); 796 797 { 798 Surface s3 = s2; 799 800 assert(s1.useCount() == 3, to!string(s1.useCount())); 801 assert(s2.useCount() == 3, to!string(s2.useCount())); 802 assert(s3.useCount() == 3, to!string(s3.useCount())); 803 } 804 805 assert(s1.useCount() == 2, to!string(s1.useCount())); 806 assert(s2.useCount() == 2, to!string(s2.useCount())); 807 } 808 assert(s1.useCount() == 1, to!string(s1.useCount())); 809 810 writeln("</Surface unittest>"); 811 }