1 /* 2 * Copyright (C) 2015-2017, by Laszlo Szeremi under the Boost license. 3 * 4 * pixel Perfect Engine, graphics.bitmap module 5 */ 6 7 module pixelperfectengine.graphics.bitmap; 8 import std.bitmanip; 9 import pixelperfectengine.system.exc; 10 import bitleveld.reinterpret; 11 import bitleveld.datatypes; 12 13 //public import pixelperfectengine.system.advBitArray; 14 public import pixelperfectengine.graphics.common; 15 16 /** 17 * Bitmap attributes, mainly for layers. 18 */ 19 public struct BitmapAttrib{ 20 mixin(bitfields!( 21 bool, "horizMirror", 1, 22 bool, "vertMirror", 1, 23 ubyte, "priority", 6)); 24 public this(bool horizMirror, bool vertMirror, ubyte priority = 0) @nogc nothrow @safe pure{ 25 this.horizMirror = horizMirror; 26 this.vertMirror = vertMirror; 27 this.priority = priority; 28 } 29 string toString() const @safe pure nothrow{ 30 return "[horizMirror: " ~ horizMirror ~ " ; vertMirror: " ~ vertMirror ~ " ; priority: " ~ priority ~ "]"; 31 } 32 } 33 34 /** 35 * Base bitmap functions, for enable the use of the same . 36 */ 37 abstract class ABitmap{ 38 private Color* palettePtr; ///Set this to either a portion of the master palette or to a self-defined place. Not used in 32 bit bitmaps. DEPRECATED! 39 private int _width; 40 private int _height; 41 /** 42 * Returns the width of the bitmap. 43 */ 44 public int width() pure @safe @property @nogc nothrow const { 45 return _width; 46 } 47 /** 48 * Returns the height of the bitmap. 49 */ 50 public int height() pure @safe @property @nogc nothrow const { 51 return _height; 52 } 53 /** 54 * Returns the palette pointer. 55 * DEPRECATED! 56 */ 57 public deprecated Color* getPalettePtr() pure @trusted @property @nogc nothrow { 58 return palettePtr; 59 } 60 /** 61 * Sets the palette pointer. Make sure that you set it to a valid memory location. 62 * DEPRECATED! 63 */ 64 public deprecated void setPalettePtr(Color* p) pure @safe @property @nogc nothrow { 65 palettePtr = p; 66 } 67 /** 68 * Returns the wordlength of the type 69 */ 70 abstract string wordLengthByString() pure @safe @property @nogc nothrow const; 71 /** 72 * Clears the whole bitmap to a transparent color. 73 */ 74 abstract void clear() pure @safe @nogc nothrow; 75 } 76 /* 77 * S: Wordlength by usage. Possible values: 78 * - b: bit (for collision shapes) 79 * - QB: QuarterByte or 2Bit (currently unimplemented) 80 * - HB: HalfByte or 4Bit 81 * - B: Byte or 8Bit 82 * - HW: HalfWord or 16Bit 83 * - W: Word or 32Bit 84 * T: Type. Possible values: 85 * - size_t: used for bitarrays 86 * - ubyte: 8Bit or under 87 * - ushort: 16Bit 88 * - Color: 32Bit 89 */ 90 alias Bitmap1Bit = Bitmap!("b",size_t); 91 alias Bitmap2Bit = Bitmap!("QB",ubyte); 92 alias Bitmap4Bit = Bitmap!("HB",ubyte); 93 alias Bitmap8Bit = Bitmap!("B",ubyte); 94 alias Bitmap16Bit = Bitmap!("HW",ushort); 95 alias Bitmap32Bit = Bitmap!("W",Color); 96 /** 97 * Implements a bitmap with variable bit depth. Use the aliases to initialize them. 98 * 99 * Note on 16 bit bitmaps: It's using the master palette, It's not implementing any 16 bit RGB or RGBA color space 100 * directly. Can implement such 101 * colorspaces via proper lookup tables. 102 * 103 * Note on 4 bit bitmaps: It's width needs to be an even number (for rendering simplicity), otherwise it'll cause an 104 * exception. 105 * 106 * Note on 1 bit bitmaps: Uses size_t based paddings for more than one bit testing at the time in the future, through 107 * the use of logic functions. 108 */ 109 public class Bitmap(string S,T) : ABitmap { 110 protected size_t pitch; ///Total length of a line in bits 111 static if (S == "b") { 112 BitArray pixelAccess; 113 //bool invertHoriz; ///Horizontal invertion for reading and writing 114 //bool invertVert; ///Vertical invertion for reading anr writing 115 } else static if (S == "QB") { 116 QuadArray pixelAccess; 117 } else static if (S == "HB") { 118 NibbleArray pixelAccess; 119 } 120 /** 121 * Image data. 122 */ 123 T[] pixels; 124 static if(S != "HB" && S != "QB" && S != "b"){ 125 /** 126 * Unified CTOR to create empty bitmap. 127 */ 128 public this(int w, int h) @safe pure { 129 _width = w; 130 _height = h; 131 pixels.length = w * h; 132 } 133 /** 134 * Unified CTOR tor create bitmap from preexisting data. 135 */ 136 public this(T[] src, int w, int h) @safe pure { 137 _width = w; 138 _height = h; 139 pixels = src; 140 if(pixels.length != w * h) 141 throw new BitmapFormatException("Bitmap size mismatch!"); 142 } 143 /** 144 * Resizes the bitmap. 145 * NOTE: It's not for scaling. 146 */ 147 public void resize(int x, int y) @safe pure { 148 pixels.length=x*y; 149 _width = x; 150 _height = y; 151 } 152 ///Returns the pixel at the given position. 153 @nogc public T readPixel(int x, int y) @safe pure { 154 return pixels[x+(_width*y)]; 155 } 156 ///Writes the pixel at the given position. 157 @nogc public T writePixel(int x, int y, T color) @safe pure { 158 return pixels[x+(_width*y)]=color; 159 } 160 /** 161 * Returns a 2D slice (window) of the bitmap. 162 */ 163 public Bitmap!(S,T) window(int iX0, int iY0, int iX1, int iY1) @safe pure { 164 T[] workpad; 165 const int localWidth = (iX1 - iX0 + 1), localHeight = (iY1 - iY0 + 1); 166 assert (localWidth <= width && localWidth); 167 assert (localHeight <= height && localHeight); 168 workpad.length = localWidth * localHeight; 169 for (int y ; y < localHeight ; y++) { 170 for (int x ; x < localWidth ; x++) { 171 workpad[x + (y * localWidth)] = pixels[iX0 + x + ((y + iY0) * _width)]; 172 } 173 } 174 return new Bitmap!(S,T)(workpad, localWidth, localHeight); 175 } 176 } else static if(S == "HB"){ 177 178 ///Creates an empty bitmap. 179 this(int w, int h) @safe pure{ 180 if (w & 1) 181 throw new BitmapFormatException("Incorrect Bitmap size exception!"); 182 _width=w; 183 _height=h; 184 pitch = _width / 2; 185 pixels.length = pitch * _height; 186 pixelAccess = NibbleArray(pixels, _width * _height); 187 } 188 ///Creates a bitmap from an array. 189 this(ubyte[] p, int w, int h) @safe pure{ 190 if (p.length * 2 != w * h || w & 1) 191 throw new BitmapFormatException("Incorrect Bitmap size exception!"); 192 _width=w; 193 _height=h; 194 pitch = _width / 2; 195 pixels=p; 196 pixelAccess = NibbleArray(pixels, _width * _height); 197 } 198 ///Resizes the array behind the bitmap. 199 public void resize(int x,int y) @safe pure { 200 if(x & 1) 201 throw new BitmapFormatException("Incorrect Bitmap size exception!"); 202 pixels.length = (x / 2) * y; 203 _width = x; 204 _height = y; 205 } 206 } else static if(S == "QB") { 207 ///Creates an empty bitmap. 208 this(int w, int h) @safe pure{ 209 if (w & 1) 210 throw new BitmapFormatException("Incorrect Bitmap size exception!"); 211 _width=w; 212 _height=h; 213 pitch = _width / 2; 214 pixels.length = pitch * _height; 215 pixelAccess = QuadArray(pixels, _width * _height); 216 } 217 ///Creates a bitmap from an array. 218 this(ubyte[] p, int w, int h) @safe pure{ 219 if (p.length * 4 != w * h || w & 3) 220 throw new BitmapFormatException("Incorrect Bitmap size exception!"); 221 _width=w; 222 _height=h; 223 pitch = _width / 2; 224 pixels=p; 225 pixelAccess = QuadArray(pixels, _width * _height); 226 } 227 } else static if(S == "b") { 228 /** 229 * CTOR for 1 bit bitmaps with no preexisting source. 230 */ 231 public this(int w, int h) @trusted pure { 232 _width = w; 233 _height = h; 234 pitch = w + (size_t.sizeof * 8 - (w % (size_t.sizeof * 8))); 235 pixels.length = pitch / (size_t.sizeof * 8) * h; 236 pixelAccess = BitArray(pixels, pitch * height); 237 } 238 /** 239 * CTOR to convert 8bit aligned bitmaps to 32/64bit ones. 240 */ 241 public this(ubyte[] src, int w, int h) @trusted pure { 242 _width = w; 243 _height = h; 244 pitch = w + (size_t.sizeof * 8 - (w % (size_t.sizeof * 8))); 245 const size_t pitch0 = w + (8 - (w % 8)); 246 const size_t len = pitch / (size_t.sizeof * 8), len0 = pitch0 / 8; 247 for (size_t i ; i < len0 * h; i+= len0) { 248 ubyte[] workpad = src[i..i+len0]; 249 workpad.length = len; 250 pixels ~= reinterpretCast!size_t(workpad); 251 } 252 pixelAccess = BitArray(pixels, pitch * height); 253 } 254 /** 255 * CTOR for 1 bit bitmaps with a preexisting source. 256 * Alignment and padding is for size_t (32 and 64 bit, on their respected systems) 257 */ 258 public this(size_t[] src, int w, int h) @trusted pure { 259 _width = w; 260 _height = h; 261 pitch = w + (size_t.sizeof * 8 - (w % (size_t.sizeof * 8))); 262 pixels = src; 263 pixelAccess = BitArray(pixels, pitch * height); 264 } 265 static if (size_t.sizeof == 8) { 266 static enum SHAM = 6; 267 static enum SHFLAG = 0x3F; 268 static enum BITAM = 64; 269 } else { 270 static enum SHAM = 5; 271 static enum SHFLAG = 0x1F; 272 static enum BITAM = 32; 273 } 274 /** 275 * Tests two collision models against each other. 276 * Params: 277 * line = The first line to test against on the left hand side collision model. 278 * lineAm = The amount of lines to be tested. 279 * other = The right hand side collision model. 280 * otherLine = The first line to test against on the right hand side collision model. 281 * offset = The amount, which the right hand side collision model's left edge is away from the left hand 282 * side collision model's left edge. 283 * overlapAm = The amount of overlap between the two objects. 284 * Returns: 285 */ 286 public bool testCollision(int line, int lineAm, Bitmap1Bit other, int otherLine, const int offset, 287 const int overlapAm) @safe @nogc nothrow pure const { 288 const int chOffset = offset>>SHAM; 289 assert(overlapAm, "This function should not be called if `overlapAm == 0`"); 290 assert(lineAm, "This function should not be called if `lineAm == 0`"); 291 for ( ; lineAm > 0 ; lineAm--, line++, otherLine++) { 292 int overlapCntr = overlapAm; 293 int loffset = offset; 294 int lchOffset = chOffset; 295 if (testChunkCollision(line, chOffset, other, otherLine, loffset)) 296 return true; 297 overlapCntr -= BITAM; 298 loffset += BITAM; 299 lchOffset++; 300 for ( ; overlapCntr > 0 ; overlapCntr -= BITAM, loffset += BITAM, lchOffset++) { 301 if (testChunkCollisionB(line, chOffset, other, otherLine, loffset)) 302 return true; 303 } 304 } 305 return false; 306 } 307 /** 308 * Tests a single chunk of pixels between two 1 bit bitmaps for collision, using a single chunk (size_t) of 309 * pixels. 310 * Params: 311 * line: The (first) line, which is being tested in the current object. 312 * other: The other object that this is being tested against. 313 * otherLine: The (first) line, which is being tested in the other object. 314 * offset: The horizontal offset of the other object to the right. 315 * Returns: True is collision has been detected. 316 */ 317 final protected bool testChunkCollision(int line, int chOffset, Bitmap1Bit other, int otherLine, int offset) 318 @safe @nogc nothrow pure const { 319 if (pixels[chOffset + (line * (pitch>>SHAM))] & 320 (other.pixels[(offset>>SHAM) + ((other.pitch>>SHAM) * otherLine)]<<(offset & SHFLAG))) 321 return true; 322 return false; 323 } 324 /** 325 * Tests a single chunk of pixels between two 1 bit bitmaps for collision, using a single chunk (size_t) of 326 * pixels, if other is wider than what size_t allows. 327 * Params: 328 * line: The (first) line, which is being tested in the current object. 329 * other: The other object that this is being tested against. 330 * otherLine: The (first) line, which is being tested in the other object. 331 * offset: The horizontal offset of the other object to the right. 332 * Returns: True is collision has been detected. 333 */ 334 final protected bool testChunkCollisionB(int line, int chOffset, Bitmap1Bit other, int otherLine, int offset) 335 @safe @nogc nothrow pure const { 336 if (pixels[chOffset + (line * (pitch>>SHAM))] & 337 ((other.pixels[(offset>>SHAM) + ((other.pitch>>SHAM) * otherLine)]<<(offset & SHFLAG)) | 338 (other.pixels[((offset>>SHAM) - 1) + ((other.pitch>>SHAM) * otherLine)]>>(SHFLAG - (offset & SHFLAG))))) 339 return true; 340 return false; 341 } 342 } 343 static if(S == "B" || S == "HW") { 344 /** 345 * Offsets all indexes in the bitmap by a certain value. Keeps zeroth index (usually for transparency) if needed. Useful when converting bitmaps. 346 */ 347 public @nogc void offsetIndexes(ushort offset, bool keepZerothIndex = true) @safe pure{ 348 for(int i ; i < pixels.length ; i++){ 349 if(!(pixels[i] == 0 && keepZerothIndex)){ 350 pixels[i] += offset; 351 } 352 } 353 } 354 } 355 static if (S == "b" || S == "QB" || S == "HB") { 356 /** 357 * Returns a 2D slice (window) of the bitmap. 358 */ 359 public Bitmap!(S,T) window(int iX0, int iY0, int iX1, int iY1) @safe pure { 360 const int localWidth = iX1 - iX0 + 1, localHeight = iY1 - iY0 + 1; 361 assert (localWidth <= width && localWidth > 0); 362 assert (localHeight <= height && localHeight > 0); 363 Bitmap!(S,T) result = new Bitmap!(S,T)(localWidth, localHeight); 364 for (int y ; y < localHeight ; y++) { 365 for (int x ; x < localWidth ; x++) { 366 result.writePixel(x, y, readPixel(iX0 + x, iY0 + y)); 367 } 368 } 369 return result; 370 } 371 } 372 static if (S == "QB" || S == "HB") { 373 ///Returns the pixel at the given position. 374 @nogc public T readPixel(int x, int y) @trusted pure { 375 assert (x >= 0 && x < _width && y >= 0 && y < _height); 376 return pixelAccess[x + (y * pitch)]; 377 } 378 ///Writes the pixel at the given position. 379 @nogc public T writePixel(int x, int y, T val) @trusted pure { 380 assert (x >= 0 && x < _width && y >= 0 && y < _height); 381 return pixelAccess[x + (y * pitch)] = val; 382 }} 383 static if(S == "W"){ 384 /** 385 * Clears the Bitmap 386 */ 387 override void clear() @nogc @safe pure nothrow { 388 for(int i ; i < pixels.length ; i++){ 389 pixels[i] = Color(0x0); 390 } 391 } 392 }else{ 393 override void clear() @nogc @safe pure nothrow { 394 for(int i ; i < pixels.length ; i++){ 395 pixels[i] = 0; 396 } 397 } 398 } 399 @nogc public T* getPtr() pure @trusted nothrow { 400 return pixels.ptr; 401 } 402 override string wordLengthByString() @safe @nogc pure nothrow @property const { 403 return S; 404 } 405 static if (S != "b") { 406 /** 407 * Generates a standard collision model by checking against a transparency value (default vaule is T.init). 408 */ 409 Bitmap1Bit generateStandardCollisionModel(const T transparency = T.init) { 410 Bitmap1Bit output = new Bitmap1Bit(width, height); 411 for (int y ; y < height ; y++) { 412 for (int x ; x < width ; x++) { 413 output.writePixel(x, y, readPixel(x, y) != transparency); 414 } 415 } 416 return output; 417 } 418 } else { 419 ///Returns the pixel at the given position. 420 @nogc public bool readPixel(int x, int y) @trusted pure { 421 assert (x >= 0 && x < _width && y >= 0 && y < _height); 422 return pixelAccess[x + (y * pitch)]; 423 } 424 ///Writes the pixel at the given position. 425 @nogc public bool writePixel(int x, int y, bool val) @trusted pure { 426 assert (x >= 0 && x < _width && y >= 0 && y < _height); 427 return pixelAccess[x + (y * pitch)] = val; 428 } 429 } 430 } 431 432 /** 433 * Defines Bitmap types 434 */ 435 public enum BitmapTypes : ubyte { 436 Undefined, ///Can be used for error checking, e.g. if a tile was initialized or not 437 Bmp1Bit, 438 Bmp2Bit, 439 Bmp4Bit, 440 Bmp8Bit, 441 Bmp16Bit, 442 Bmp32Bit, 443 Planar, ///Mainly used as a placeholder 444 }