1 module pixelperfectengine.graphics.layers.spritelayer; 2 3 public import pixelperfectengine.graphics.layers.base; 4 5 import collections.treemap; 6 import collections.sortedlist; 7 import std.bitmanip : bitfields; 8 import bitleveld.datatypes; 9 10 /** 11 * General-purpose sprite controller and renderer. 12 */ 13 public class SpriteLayer : Layer, ISpriteLayer { 14 /** 15 * Helps to determine the displaying properties and order of sprites. 16 */ 17 public struct DisplayListItem { 18 Box position; /// Stores the position relative to the origin point. Actual display position is determined by the scroll positions. 19 Box slice; /// To compensate for the lack of scanline interrupt capabilities, this enables chopping off parts of a sprite. 20 void* pixelData; /// Points to the pixel data. 21 /** 22 * From version 0.10.0 onwards, each sprites can have their own rendering function set up to 23 * allow different effect on a single layer. 24 * If not specified otherwise, the layer's main rendering function will be used instead. 25 * Custom rendering functions can be written by the user, it requires knowledge of writing 26 * pixel shader-like functions using fixed-point arithmetics. Use of vector optimizatons 27 * techniques (SSE2, AVX, NEON, etc) are needed for optimal performance. 28 */ 29 @nogc pure nothrow void function(uint* src, uint* dest, size_t length, ubyte value) renderFunc; 30 int width; /// Width of the sprite 31 int height; /// Height of the sprite 32 int scaleHoriz; /// Horizontal scaling 33 int scaleVert; /// Vertical scaling 34 int priority; /// Used for automatic sorting and identification. 35 /** 36 * Selects the palette of the sprite. 37 * Amount of accessable color depends on the palette access shifting value. A value of 8 enables 38 * 256 * 256 color palettes, and a value of 4 enables 4096 * 16 color palettes. 39 * `paletteSh` can be set lower than what the bitmap is capable of storing at its maximum, this 40 * can enable the packing of more palettes within the main one, e.g. a `paletteSh` value of 7 41 * means 512 * 128 color palettes, while the bitmaps are still stored in the 8 bit "chunky" mode 42 * instead of 7 bit planar that would require way more processing power. However this doesn't 43 * limit the bitmap's ability to access 256 colors, and this can result in memory leakage if 44 * the developer isn't careful enough. 45 */ 46 ushort paletteSel; 47 //ubyte flags; /// Flags packed into a single byte (bitmapType, paletteSh) 48 mixin(bitfields!( 49 ubyte, "paletteSh", 4, 50 ubyte, "bmpType", 4, 51 )); 52 ubyte masterAlpha = ubyte.max;/// Sets the master alpha value of the sprite, e.g. opacity 53 //ubyte wordLength; /// Determines the word length of a sprite in a much quicker way than getting classinfo. 54 //ubyte paletteSh; /// Palette shifting value. 8 is default for 8 bit, and 4 for 4 bit bitmaps. (see paletteSel for more info) 55 //static enum ubyte PALETTESH_MASK = 0x0F; /// Mask for paletteSh 56 //static enum ubyte BMPTYPE_MASK = 0x80; /// Mask for bmpType 57 /+ /** 58 * Creates a display list item with palette selector. 59 */ 60 this(Coordinate position, ABitmap sprite, int priority, ushort paletteSel = 0, int scaleHoriz = 1024, 61 int scaleVert = 1024) pure @trusted nothrow { 62 this.position = position; 63 this.width = sprite.width; 64 this.height = sprite.height; 65 this.priority = priority; 66 this.paletteSel = paletteSel; 67 this.scaleVert = scaleVert; 68 this.scaleHoriz = scaleHoriz; 69 slice = Coordinate(0,0,sprite.width,sprite.height); 70 if (typeid(sprite) is typeid(Bitmap2Bit)) { 71 bmpType = BitmapTypes.Bmp2Bit; 72 73 } else if (typeid(sprite) is typeid(Bitmap4Bit)) { 74 bmpType = BitmapTypes.Bmp4Bit; 75 paletteSh = 4; 76 pixelData = (cast(Bitmap4Bit)(sprite)).getPtr; 77 } else if (typeid(sprite) is typeid(Bitmap8Bit)) { 78 bmpType = BitmapTypes.Bmp8Bit; 79 paletteSh = 8; 80 pixelData = (cast(Bitmap8Bit)(sprite)).getPtr; 81 } else if (typeid(sprite) is typeid(Bitmap16Bit)) { 82 bmpType = BitmapTypes.Bmp16Bit; 83 pixelData = (cast(Bitmap16Bit)(sprite)).getPtr; 84 } else if (typeid(sprite) is typeid(Bitmap32Bit)) { 85 bmpType = BitmapTypes.Bmp32Bit; 86 pixelData = (cast(Bitmap32Bit)(sprite)).getPtr; 87 } 88 }+/ 89 /** 90 * Creates a display list item according to the newer architecture. 91 * Params: 92 * x = X position of the sprite. 93 * y = Y position of the sprite. 94 * sprite = The bitmap to be used as the sprite. 95 * pri = Priority identifier. 96 * paletteSel = Selects a given palette. 97 * paletteSh = Determines how many bits are being used. 98 * alpha = The transparency of the sprite. 99 * scaleHoriz = Horizontal scaling of the sprite. 1024 is the base value, anything less will stretch, greater will shrink the sprite. 100 * scaleVert = Ditto for vertical. 101 */ 102 this(int x, int y, ABitmap sprite, int priority, ushort paletteSel = 0, ubyte paletteSh = 0, ubyte alpha = ubyte.max, 103 int scaleHoriz = 1024, int scaleVert = 1024) pure @trusted nothrow { 104 this.width = sprite.width(); 105 this.height = sprite.height(); 106 this.position = Box.bySize(x, y, cast(int)scaleNearestLength(width, scaleHoriz), 107 cast(int)scaleNearestLength(height, scaleVert)); 108 this.priority = priority; 109 this.paletteSel = paletteSel; 110 this.scaleVert = scaleVert; 111 this.scaleHoriz = scaleHoriz; 112 slice = Box(0,0,sprite.width,sprite.height); 113 if (typeid(sprite) is typeid(Bitmap2Bit)) { 114 bmpType = BitmapTypes.Bmp2Bit; 115 this.paletteSh = paletteSh ? paletteSh : 2; 116 pixelData = (cast(Bitmap2Bit)(sprite)).getPtr; 117 } else if (typeid(sprite) is typeid(Bitmap4Bit)) { 118 bmpType = BitmapTypes.Bmp4Bit; 119 this.paletteSh = paletteSh ? paletteSh : 4; 120 pixelData = (cast(Bitmap4Bit)(sprite)).getPtr; 121 } else if (typeid(sprite) is typeid(Bitmap8Bit)) { 122 bmpType = BitmapTypes.Bmp8Bit; 123 this.paletteSh = paletteSh ? paletteSh : 8; 124 pixelData = (cast(Bitmap8Bit)(sprite)).getPtr; 125 } else if (typeid(sprite) is typeid(Bitmap16Bit)) { 126 bmpType = BitmapTypes.Bmp16Bit; 127 pixelData = (cast(Bitmap16Bit)(sprite)).getPtr; 128 } else if (typeid(sprite) is typeid(Bitmap32Bit)) { 129 bmpType = BitmapTypes.Bmp32Bit; 130 pixelData = (cast(Bitmap32Bit)(sprite)).getPtr; 131 } 132 } 133 /** 134 * Resets the slice to its original position. 135 */ 136 void resetSlice() pure @nogc @safe nothrow { 137 slice.left = 0; 138 slice.top = 0; 139 slice.right = position.width - 1; 140 slice.bottom = position.height - 1; 141 } 142 /** 143 * Replaces the sprite with a new one. 144 * If the sizes are mismatching, the top-left coordinates are left as is, but the slicing is reset. 145 */ 146 void replaceSprite(ABitmap sprite) @trusted pure nothrow { 147 //this.sprite = sprite; 148 //palette = sprite.getPalettePtr(); 149 if(this.width != sprite.width || this.height != sprite.height){ 150 this.width = sprite.width; 151 this.height = sprite.height; 152 position.right = position.left + cast(int)scaleNearestLength(width, scaleHoriz); 153 position.bottom = position.top + cast(int)scaleNearestLength(height, scaleVert); 154 resetSlice(); 155 } 156 if (typeid(sprite) is typeid(Bitmap2Bit)) { 157 bmpType = BitmapTypes.Bmp2Bit; 158 //paletteSh = 2; 159 pixelData = (cast(Bitmap2Bit)(sprite)).getPtr; 160 } else if (typeid(sprite) is typeid(Bitmap4Bit)) { 161 bmpType = BitmapTypes.Bmp4Bit; 162 //paletteSh = 4; 163 pixelData = (cast(Bitmap4Bit)(sprite)).getPtr; 164 } else if (typeid(sprite) is typeid(Bitmap8Bit)) { 165 bmpType = BitmapTypes.Bmp8Bit; 166 //paletteSh = 8; 167 pixelData = (cast(Bitmap8Bit)(sprite)).getPtr; 168 } else if (typeid(sprite) is typeid(Bitmap16Bit)) { 169 bmpType = BitmapTypes.Bmp16Bit; 170 pixelData = (cast(Bitmap16Bit)(sprite)).getPtr; 171 } else if (typeid(sprite) is typeid(Bitmap32Bit)) { 172 bmpType = BitmapTypes.Bmp32Bit; 173 pixelData = (cast(Bitmap32Bit)(sprite)).getPtr; 174 } 175 } 176 @nogc int opCmp(in DisplayListItem d) const pure @safe nothrow { 177 return priority - d.priority; 178 } 179 @nogc bool opEquals(in DisplayListItem d) const pure @safe nothrow { 180 return priority == d.priority; 181 } 182 @nogc int opCmp(in int pri) const pure @safe nothrow { 183 return priority - pri; 184 } 185 @nogc bool opEquals(in int pri) const pure @safe nothrow { 186 return priority == pri; 187 } 188 189 string toString() const { 190 import std.conv : to; 191 return "{Position: " ~ position.toString ~ ";\nDisplayed portion: " ~ slice.toString ~";\nPriority: " ~ 192 to!string(priority) ~ "; PixelData: " ~ to!string(pixelData) ~ 193 "; PaletteSel: " ~ to!string(paletteSel) ~ "; bmpType: " ~ to!string(bmpType) ~ "}"; 194 } 195 } 196 alias DisplayList = TreeMap!(int, DisplayListItem); 197 //alias OnScreenList = SortedList!(int, "a < b", false, "a == b"); 198 //protected DisplayListItem[] displayList; ///Stores the display data 199 protected DisplayList allSprites; ///All sprites of this layer 200 //protected OnScreenList displayedSprites; ///Sprites that are being displayed 201 protected Color[2048] src; ///Local buffer for scaling 202 //size_t[8] prevSize; 203 ///Default ctor 204 public this(RenderingMode renderMode = RenderingMode.AlphaBlend) nothrow @safe { 205 setRenderingMode(renderMode); 206 //Bug workaround: Sometimes when attempting to append an element to a zero-length array, it causes an exception 207 //to be thrown, due to access errors. This bug is unstable, and as such hard to debug for (memory leakage issue?) 208 //displayedSprites.reserve(128); 209 //src[0].length = 1024; 210 } 211 /** 212 * Checks all sprites for whether they're on screen or not. 213 * Called every time the layer is being scrolled. 214 */ 215 public void checkAllSprites() @safe nothrow { 216 foreach (key; allSprites) { 217 checkSprite(key); 218 } 219 } 220 /** 221 * Checks whether a sprite would be displayed on the screen, then updates the display list. 222 * Returns true if it's on screen. 223 */ 224 public bool checkSprite(int n) @safe nothrow { 225 return checkSprite(allSprites[n]); 226 } 227 ///Ditto. 228 protected bool checkSprite(DisplayListItem sprt) @safe nothrow { 229 //assert(sprt.bmpType != BitmapTypes.Undefined && sprt.pixelData, "DisplayList error!"); 230 if(sprt.slice.width && sprt.slice.height 231 && (sprt.position.right > sX && sprt.position.bottom > sY && 232 sprt.position.left < sX + rasterX && sprt.position.top < sY + rasterY)) { 233 //displayedSprites.put(sprt.priority); 234 return true; 235 } else { 236 //displayedSprites.removeByElem(sprt.priority); 237 return false; 238 } 239 } 240 /** 241 * Searches the DisplayListItem by priority and returns it. 242 * Can be used for external use without any safety issues. 243 */ 244 public DisplayListItem getDisplayListItem(int n) @nogc pure @safe nothrow { 245 return allSprites[n]; 246 } 247 /** 248 * Searches the DisplayListItem by priority and returns it. 249 * Intended for internal use, as it returns it as a reference value. 250 */ 251 protected final DisplayListItem* getDisplayListItem_internal(int n) @nogc pure @safe nothrow { 252 return allSprites.ptrOf(n); 253 } 254 /+override public void setRasterizer(int rX,int rY) { 255 super.setRasterizer(rX,rY); 256 }+/ 257 ///Returns the displayed portion of the sprite. 258 public Coordinate getSlice(int n) @nogc pure @safe nothrow { 259 return getDisplayListItem(n).slice; 260 } 261 ///Writes the displayed portion of the sprite. 262 ///Returns the new slice, if invalid (greater than the bitmap, etc.) returns Coordinate.init. 263 public Coordinate setSlice(int n, Coordinate slice) @safe nothrow { 264 DisplayListItem* sprt = allSprites.ptrOf(n); 265 if(sprt) { 266 sprt.slice = slice; 267 checkSprite(*sprt); 268 return sprt.slice; 269 } else { 270 return Coordinate.init; 271 } 272 } 273 ///Returns the selected paletteID of the sprite. 274 public ushort getPaletteID(int n) @nogc pure @safe nothrow { 275 return allSprites[n].paletteSel; 276 } 277 ///Sets the paletteID of the sprite. Returns the new ID, which is truncated to the possible values with a simple binary and operation 278 ///Palette must exist in the parent Raster, otherwise AccessError might happen 279 public ushort setPaletteID(int n, ushort paletteID) @nogc pure @safe nothrow { 280 return getDisplayListItem_internal(n).paletteSel = paletteID; 281 } 282 /** 283 * Returns the sprite rendering function. 284 * Params: 285 * n = Sprite priority ID. 286 */ 287 public RenderFunc getSpriteRenderingFunc(int n) @nogc @safe pure nothrow { 288 return allSprites[n].renderFunc; 289 } 290 /** 291 * Sets the sprite's rendering function from a predefined ones. 292 * Params: 293 * n = Sprite priority ID. 294 * mode = The rendering mode. (init for layer default) 295 * Returns: The new rendering function. 296 */ 297 public RenderFunc setSpriteRenderingMode(int n, RenderingMode mode) @nogc @safe pure nothrow { 298 return allSprites.ptrOf(n).renderFunc = mode == RenderingMode.init ? mainRenderingFunction : getRenderingFunc(mode); 299 } 300 /** 301 * Sets the sprite's rendering function. Can be a custom one. 302 * Params: 303 * n = Sprite priority ID. 304 * mode = The rendering mode. (init for layer default) 305 * Returns: The new rendering function. 306 */ 307 public RenderFunc setSpriteRenderingFunc(int n, RenderFunc func) @nogc @safe pure nothrow { 308 return allSprites.ptrOf(n).renderFunc = func; 309 } 310 /** 311 * Creates a sprite from a bitmap with the given data, then places it to the display list. (New architecture) 312 * Params: 313 * sprt = The bitmap to be used as the sprite. 314 * n = Priority ID of the sprite. Both identifies the sprite and decides it's display priority. Larger numbers will be drawn first, 315 * and thus will appear behind of smaller numbers, which also include negatives. 316 * x = X position of the sprite (top-left corner). 317 * y = Y position of the sprite (top-left corner). 318 * paletteSel = Selects a given palette. 319 * paletteSh = Determines how many bits are being used, and thus the palette size for selection. 320 * alpha = The transparency of the sprite. 321 * scaleHoriz = Horizontal scaling of the sprite. 1024 is the base value, anything less will stretch, greater will shrink the sprite. 322 * scaleVert = Ditto for vertical. 323 * renderMode = Determines the rendering mode of the sprite. By default, it's determined by the layer itself. Any of the default 324 * other methods can be selected here, or a specially written rendering function can be specified with a different function. 325 * Returns: The current area of the sprite. 326 */ 327 public Box addSprite(ABitmap sprt, int n, int x, int y, ushort paletteSel = 0, ubyte paletteSh = 0, 328 ubyte alpha = ubyte.max, int scaleHoriz = 1024, int scaleVert = 1024, RenderingMode renderMode = RenderingMode.init) 329 @safe nothrow { 330 DisplayListItem d = DisplayListItem(x, y, sprt, n, paletteSel, paletteSh, alpha, scaleHoriz, scaleVert); 331 if (renderMode == RenderingMode.init) 332 d.renderFunc = mainRenderingFunction; 333 else 334 d.renderFunc = getRenderingFunc(renderMode); 335 synchronized{ 336 allSprites[n] = d; 337 //checkSprite(d); 338 } 339 return allSprites[n].position; 340 } 341 /** 342 * Adds a sprite to the layer. 343 */ 344 /+public void addSprite(ABitmap s, int n, Box c, ushort paletteSel = 0, int scaleHoriz = 1024, 345 int scaleVert = 1024) @safe nothrow { 346 DisplayListItem d = DisplayListItem(c, s, n, paletteSel, scaleHoriz, scaleVert); 347 d.renderFunc = mainRenderingFunction; 348 synchronized 349 allSprites[n] = d; 350 checkSprite(d); 351 }+/ 352 ///Ditto 353 /+public void addSprite(ABitmap s, int n, int x, int y, ushort paletteSel = 0, int scaleHoriz = 1024, 354 int scaleVert = 1024) @safe nothrow { 355 DisplayListItem d = DisplayListItem(Box.bySize(x, y, cast(int)scaleNearestLength(s.width, scaleHoriz), 356 cast(int)scaleNearestLength(s.height, scaleVert)), s, n, paletteSel, scaleHoriz, 357 scaleVert); 358 d.renderFunc = mainRenderingFunction; 359 synchronized 360 allSprites[n] = d; 361 checkSprite(d); 362 }+/ 363 /** 364 * Replaces the bitmap of the given sprite. 365 */ 366 public void replaceSprite(ABitmap s, int n) @safe nothrow { 367 DisplayListItem* sprt = getDisplayListItem_internal(n); 368 sprt.replaceSprite(s); 369 //checkSprite(*sprt); 370 } 371 ///Ditto with move 372 public void replaceSprite(ABitmap s, int n, int x, int y) @safe nothrow { 373 DisplayListItem* sprt = getDisplayListItem_internal(n); 374 sprt.replaceSprite(s); 375 sprt.position.move(x, y); 376 //checkSprite(*sprt); 377 } 378 ///Ditto with move 379 public void replaceSprite(ABitmap s, int n, Coordinate c) @safe nothrow { 380 DisplayListItem* sprt = allSprites.ptrOf(n); 381 sprt.replaceSprite(s); 382 sprt.position = c; 383 checkSprite(*sprt); 384 } 385 /** 386 * Removes a sprite from both displaylists by priority. 387 */ 388 public void removeSprite(int n) @safe nothrow { 389 synchronized { 390 //displayedSprites.removeByElem(n); 391 allSprites.remove(n); 392 } 393 } 394 ///Clears all sprite from the layer. 395 public void clear() @safe nothrow { 396 //displayedSprites = OnScreenList.init; 397 allSprites = DisplayList.init; 398 } 399 /** 400 * Moves a sprite to the given position. 401 */ 402 public void moveSprite(int n, int x, int y) @safe nothrow { 403 DisplayListItem* sprt = allSprites.ptrOf(n); 404 sprt.position.move(x, y); 405 //checkSprite(*sprt); 406 } 407 /** 408 * Moves a sprite by the given amount. 409 */ 410 public void relMoveSprite(int n, int x, int y) @safe nothrow { 411 DisplayListItem* sprt = allSprites.ptrOf(n); 412 sprt.position.relMove(x, y); 413 //checkSprite(*sprt); 414 } 415 ///Sets the rendering function for the sprite (defaults to the layer's rendering function) 416 public void setSpriteRenderingMode(int n, RenderingMode mode) @safe nothrow { 417 DisplayListItem* sprt = allSprites.ptrOf(n); 418 sprt.renderFunc = getRenderingFunc(mode); 419 } 420 public @nogc Coordinate getSpriteCoordinate(int n) @safe nothrow { 421 return allSprites[n].position; 422 } 423 ///Scales sprite horizontally. Returns the new size, or -1 if the scaling value is invalid, or -2 if spriteID not found. 424 public int scaleSpriteHoriz(int n, int hScl) @trusted nothrow { 425 DisplayListItem* sprt = allSprites.ptrOf(n); 426 if(!sprt) return -2; 427 else if(!hScl) return -1; 428 else { 429 sprt.scaleHoriz = hScl; 430 const int newWidth = cast(int)scaleNearestLength(sprt.width, hScl); 431 sprt.slice.right = newWidth; 432 sprt.position.right = sprt.position.left + newWidth; 433 checkSprite(*sprt); 434 return newWidth; 435 } 436 } 437 ///Scales sprite vertically. Returns the new size, or -1 if the scaling value is invalid, or -2 if spriteID not found. 438 public int scaleSpriteVert(int n, int vScl) @trusted nothrow { 439 DisplayListItem* sprt = allSprites.ptrOf(n); 440 if(!sprt) return -2; 441 else if(!vScl) return -1; 442 else { 443 sprt.scaleVert = vScl; 444 const int newHeight = cast(int)scaleNearestLength(sprt.height, vScl); 445 sprt.slice.bottom = newHeight; 446 sprt.position.bottom = sprt.position.top + newHeight; 447 checkSprite(*sprt); 448 return newHeight; 449 } 450 /+if (!vScl) return -1; 451 for(int i; i < displayList.length ; i++){ 452 if(displayList[i].priority == n){ 453 displayList[i].scaleVert = vScl; 454 const int newHeight = cast(int)scaleNearestLength(displayList[i].height, vScl); 455 displayList[i].slice.bottom = newHeight; 456 return displayList[i].position.bottom = displayList[i].position.top + newHeight; 457 } 458 } 459 return -2;+/ 460 } 461 ///Gets the sprite's current horizontal scale value 462 public int getScaleSpriteHoriz(int n) @nogc @trusted nothrow { 463 return allSprites[n].scaleHoriz; 464 } 465 ///Gets the sprite's current vertical scale value 466 public int getScaleSpriteVert(int n) @nogc @trusted nothrow { 467 return allSprites[n].scaleVert; 468 } 469 public override LayerType getLayerType() @nogc @safe pure nothrow const { 470 return LayerType.TransformableTile; 471 } 472 public override @nogc void updateRaster(void* workpad, int pitch, Color* palette) { 473 /* 474 * BUG 1: If sprite is wider than 2048 pixels, it'll cause issues (mostly memory leaks) due to a hack. (Fixed!) 475 * BUG 2: Obscuring the top part of a sprite when scaleVert is not 1024 will cause glitches. (Fixed!!!) 476 */ 477 //foreach (priority ; displayedSprites) { 478 foreach_reverse (i ; allSprites) { 479 if(!(i.slice.width && i.slice.height 480 && (i.position.right > sX && i.position.bottom > sY && 481 i.position.left < sX + rasterX && i.position.top < sY + rasterY))) continue; 482 //DisplayListItem i = allSprites[priority]; 483 const int left = i.position.left + i.slice.left; 484 const int top = i.position.top + i.slice.top; 485 const int right = i.position.left + i.slice.right; 486 const int bottom = i.position.top + i.slice.bottom; 487 /+if((i.position.right > sX && i.position.bottom > sY) && (i.position.left < sX + rasterX && i.position.top < sY + 488 rasterY)){+/ 489 //if((right > sX && left < sX + rasterX) && (bottom > sY && top < sY + rasterY) && i.slice.width && i.slice.height){ 490 int offsetXA = sX > left ? sX - left : 0;//Left hand side offset, zero if not obscured 491 const int offsetXB = sX + rasterX < right ? right - (sX + rasterX) : 0; //Right hand side offset, zero if not obscured 492 const int offsetYA = sY > top ? sY - top : 0; //top offset of sprite, zero if not obscured 493 const int offsetYB = sY + rasterY < bottom ? bottom - (sY + rasterY) + 1 : 1; //bottom offset of sprite, zero if not obscured 494 //const int offsetYB0 = cast(int)scaleNearestLength(offsetYB, i.scaleVert); 495 const int sizeX = i.slice.width(); //total displayed width after slicing 496 const int offsetX = left - sX; 497 const int length = sizeX - offsetXA - offsetXB - 1; //total displayed width after considering screen borders 498 //int lengthY = i.slice.height - offsetYA - offsetYB; 499 //const int lfour = length * 4; 500 const int offsetY = sY < top ? (top-sY)*pitch : 0; //used if top portion of the sprite is off-screen 501 //offset = i.scaleVert % 1024; 502 const int scaleVertAbs = i.scaleVert * (i.scaleVert < 0 ? -1 : 1); //absolute value of vertical scaling, used in various calculations 503 const int scaleHorizAbs = i.scaleHoriz * (i.scaleHoriz < 0 ? -1 : 1); 504 //const int offsetAmount = scaleVertAbs;//= scaleVertAbs <= 1024 ? 1024 : scaleVertAbs; //used to limit the amount of re-rendering every line 505 const int offsetYA0 = (offsetYA * scaleVertAbs)>>>10; //amount of skipped lines in the bitmap source 506 //const int offsetYA0 = cast(int)(cast(double)offsetYA / (1024.0 / cast(double)scaleVertAbs)); //amount of skipped lines (I think) TODO: remove floating-point arithmetic 507 const int sizeXOffset = i.width * (i.scaleVert < 0 ? -1 : 1); 508 int offsetTarget; //the target fractional lines 509 int offset = (offsetYA * scaleVertAbs) & 1023; //the current amount of fractional lines, also contains the fractional offset bias by defauls 510 //const size_t p0offset = (i.scaleHoriz > 0 ? offsetXA : offsetXB); //determines offset based on mirroring 511 const int scalelength = i.position.width < 2048 ? i.width : 2048; //limit width to 2048, the minimum required for this scaling method to work 512 void* dest = workpad + (offsetX + offsetXA)*4 + offsetY; 513 final switch (i.bmpType) with (BitmapTypes) { 514 case Bmp2Bit: 515 ubyte* p0 = cast(ubyte*)i.pixelData + i.width * ((i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0)>>2); 516 const size_t _pitch = i.width>>>2; 517 for (int y = offsetYA ; y < i.slice.height - offsetYB ; ) { 518 /+horizontalScaleNearest4BitAndCLU(p0, src.ptr, palette + (i.paletteSel<<i.paletteSh), scalelength, offsetXA & 1, 519 i.scaleHoriz);+/ 520 horizontalScaleNearestAndCLU(QuadArray(p0[0.._pitch], i.width), src.ptr, palette + (i.paletteSel<<i.paletteSh), 521 length, i.scaleHoriz, offsetXA * scaleHorizAbs); 522 offsetTarget += 1024; 523 for (; offset < offsetTarget && y < i.slice.height - offsetYB ; offset += scaleVertAbs) { 524 y++; 525 i.renderFunc(cast(uint*)src.ptr, cast(uint*)dest, length, i.masterAlpha); 526 dest += pitch; 527 } 528 p0 += _pitch; 529 } 530 break; 531 case Bmp4Bit: 532 ubyte* p0 = cast(ubyte*)i.pixelData + i.width * ((i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0)>>1); 533 const size_t _pitch = i.width>>>1; 534 for (int y = offsetYA ; y < i.slice.height - offsetYB ; ) { 535 /+horizontalScaleNearest4BitAndCLU(p0, src.ptr, palette + (i.paletteSel<<i.paletteSh), scalelength, offsetXA & 1, 536 i.scaleHoriz);+/ 537 horizontalScaleNearestAndCLU(NibbleArray(p0[0.._pitch], i.width), src.ptr, palette + (i.paletteSel<<i.paletteSh), 538 length, i.scaleHoriz, offsetXA * scaleHorizAbs); 539 offsetTarget += 1024; 540 for (; offset < offsetTarget && y < i.slice.height - offsetYB ; offset += scaleVertAbs) { 541 y++; 542 i.renderFunc(cast(uint*)src.ptr, cast(uint*)dest, length, i.masterAlpha); 543 dest += pitch; 544 } 545 p0 += _pitch; 546 } 547 break; 548 case Bmp8Bit: 549 ubyte* p0 = cast(ubyte*)i.pixelData + i.width * (i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0); 550 for (int y = offsetYA ; y < i.slice.height - offsetYB ; ) { 551 //horizontalScaleNearestAndCLU(p0, src.ptr, palette + (i.paletteSel<<i.paletteSh), scalelength, i.scaleHoriz); 552 horizontalScaleNearestAndCLU(p0[0..i.width], src.ptr, palette + (i.paletteSel<<i.paletteSh), length, i.scaleHoriz, 553 offsetXA * scaleHorizAbs); 554 offsetTarget += 1024; 555 for (; offset < offsetTarget && y < i.slice.height - offsetYB ; offset += scaleVertAbs) { 556 y++; 557 i.renderFunc(cast(uint*)src.ptr, cast(uint*)dest, length, i.masterAlpha); 558 dest += pitch; 559 } 560 p0 += sizeXOffset; 561 } 562 break; 563 case Bmp16Bit: 564 ushort* p0 = cast(ushort*)i.pixelData + i.width * (i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0); 565 for (int y = offsetYA ; y < i.slice.height - offsetYB ; ) { 566 //horizontalScaleNearestAndCLU(p0, src.ptr, palette, scalelength, i.scaleHoriz); 567 horizontalScaleNearestAndCLU(p0[0..i.width], src.ptr, palette, length, i.scaleHoriz, offsetXA * scaleHorizAbs); 568 offsetTarget += 1024; 569 for (; offset < offsetTarget && y < i.slice.height - offsetYB ; offset += scaleVertAbs) { 570 y++; 571 i.renderFunc(cast(uint*)src.ptr, cast(uint*)dest, length, i.masterAlpha); 572 dest += pitch; 573 } 574 p0 += sizeXOffset; 575 } 576 break; 577 case Bmp32Bit: 578 Color* p0 = cast(Color*)i.pixelData + i.width * (i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0); 579 for (int y = offsetYA ; y < i.slice.height - offsetYB ; ) { 580 horizontalScaleNearest(p0[0..i.width], src, scalelength, i.scaleHoriz, offsetXA * scaleHorizAbs); 581 offsetTarget += 1024; 582 for (; offset < offsetTarget && y < i.slice.height - offsetYB; offset += scaleVertAbs) { 583 y++; 584 i.renderFunc(cast(uint*)src.ptr, cast(uint*)dest, length, i.masterAlpha); 585 dest += pitch; 586 } 587 p0 += sizeXOffset; 588 } 589 //} 590 break; 591 case Undefined, Bmp1Bit, Planar: 592 break; 593 } 594 595 //} 596 } 597 //foreach(int threadOffset; threads.parallel) 598 //free(src[threadOffset]); 599 } 600 ///Absolute scrolling. 601 public override void scroll(int x, int y) @safe nothrow { 602 sX = x; 603 sY = y; 604 //checkAllSprites; 605 } 606 ///Relative scrolling. Positive values scrolls the layer left and up, negative values scrolls the layer down and right. 607 public override void relScroll(int x, int y) @safe nothrow { 608 sX += x; 609 sY += y; 610 //checkAllSprites; 611 } 612 }