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 /**
54 * Creates a display list item according to the newer architecture.
55 * Params:
56 * x = X position of the sprite.
57 * y = Y position of the sprite.
58 * sprite = The bitmap to be used as the sprite.
59 * pri = Priority identifier.
60 * paletteSel = Selects a given palette.
61 * paletteSh = Determines how many bits are being used.
62 * alpha = The transparency of the sprite.
63 * scaleHoriz = Horizontal scaling of the sprite. 1024 is the base value, anything less will stretch, greater will shrink the sprite.
64 * scaleVert = Ditto for vertical.
65 */
66 this(int x, int y, ABitmap sprite, int priority, ushort paletteSel = 0, ubyte paletteSh = 0, ubyte alpha = ubyte.max,
67 int scaleHoriz = 1024, int scaleVert = 1024) pure @trusted nothrow {
68 this.width = sprite.width();
69 this.height = sprite.height();
70 this.position = Box.bySize(x, y, cast(int)scaleNearestLength(width, scaleHoriz),
71 cast(int)scaleNearestLength(height, scaleVert));
72 this.priority = priority;
73 this.paletteSel = paletteSel;
74 this.scaleVert = scaleVert;
75 this.scaleHoriz = scaleHoriz;
76 slice = Box(0,0,sprite.width,sprite.height);
77 if (typeid(sprite) is typeid(Bitmap2Bit)) {
78 bmpType = BitmapTypes.Bmp2Bit;
79 this.paletteSh = paletteSh ? paletteSh : 2;
80 pixelData = (cast(Bitmap2Bit)(sprite)).getPtr;
81 } else if (typeid(sprite) is typeid(Bitmap4Bit)) {
82 bmpType = BitmapTypes.Bmp4Bit;
83 this.paletteSh = paletteSh ? paletteSh : 4;
84 pixelData = (cast(Bitmap4Bit)(sprite)).getPtr;
85 } else if (typeid(sprite) is typeid(Bitmap8Bit)) {
86 bmpType = BitmapTypes.Bmp8Bit;
87 this.paletteSh = paletteSh ? paletteSh : 8;
88 pixelData = (cast(Bitmap8Bit)(sprite)).getPtr;
89 } else if (typeid(sprite) is typeid(Bitmap16Bit)) {
90 bmpType = BitmapTypes.Bmp16Bit;
91 pixelData = (cast(Bitmap16Bit)(sprite)).getPtr;
92 } else if (typeid(sprite) is typeid(Bitmap32Bit)) {
93 bmpType = BitmapTypes.Bmp32Bit;
94 pixelData = (cast(Bitmap32Bit)(sprite)).getPtr;
95 }
96 }
97 /**
98 * Resets the slice to its original position.
99 */
100 void resetSlice() pure @nogc @safe nothrow {
101 slice.left = 0;
102 slice.top = 0;
103 slice.right = position.width - 1;
104 slice.bottom = position.height - 1;
105 }
106 /**
107 * Replaces the sprite with a new one.
108 * If the sizes are mismatching, the top-left coordinates are left as is, but the slicing is reset.
109 */
110 void replaceSprite(ABitmap sprite) @trusted pure nothrow {
111 //this.sprite = sprite;
112 //palette = sprite.getPalettePtr();
113 if(this.width != sprite.width || this.height != sprite.height){
114 this.width = sprite.width;
115 this.height = sprite.height;
116 position.right = position.left + cast(int)scaleNearestLength(width, scaleHoriz);
117 position.bottom = position.top + cast(int)scaleNearestLength(height, scaleVert);
118 resetSlice();
119 }
120 if (typeid(sprite) is typeid(Bitmap2Bit)) {
121 bmpType = BitmapTypes.Bmp2Bit;
122 //paletteSh = 2;
123 pixelData = (cast(Bitmap2Bit)(sprite)).getPtr;
124 } else if (typeid(sprite) is typeid(Bitmap4Bit)) {
125 bmpType = BitmapTypes.Bmp4Bit;
126 //paletteSh = 4;
127 pixelData = (cast(Bitmap4Bit)(sprite)).getPtr;
128 } else if (typeid(sprite) is typeid(Bitmap8Bit)) {
129 bmpType = BitmapTypes.Bmp8Bit;
130 //paletteSh = 8;
131 pixelData = (cast(Bitmap8Bit)(sprite)).getPtr;
132 } else if (typeid(sprite) is typeid(Bitmap16Bit)) {
133 bmpType = BitmapTypes.Bmp16Bit;
134 pixelData = (cast(Bitmap16Bit)(sprite)).getPtr;
135 } else if (typeid(sprite) is typeid(Bitmap32Bit)) {
136 bmpType = BitmapTypes.Bmp32Bit;
137 pixelData = (cast(Bitmap32Bit)(sprite)).getPtr;
138 }
139 }
140 @nogc int opCmp(const DisplayListItem d) const pure @safe nothrow {
141 return priority - d.priority;
142 }
143 @nogc bool opEquals(const DisplayListItem d) const pure @safe nothrow {
144 return priority == d.priority;
145 }
146 @nogc int opCmp(const int pri) const pure @safe nothrow {
147 return priority - pri;
148 }
149 @nogc bool opEquals(const int pri) const pure @safe nothrow {
150 return priority == pri;
151 }
152
153 string toString() const {
154 import std.conv : to;
155 return "{Position: " ~ position.toString ~ ";\nDisplayed portion: " ~ slice.toString ~";\nPriority: " ~
156 to!string(priority) ~ "; PixelData: " ~ to!string(pixelData) ~
157 "; PaletteSel: " ~ to!string(paletteSel) ~ "; bmpType: " ~ to!string(bmpType) ~ "}";
158 }
159 }
160 //alias DisplayList = TreeMap!(int, DisplayListItem);
161 alias DisplayList = SortedList!(DisplayListItem, "a < b", false);
162 protected DisplayList allSprites; ///All sprites of this layer
163 //protected OnScreenList displayedSprites; ///Sprites that are being displayed
164 protected Color[2048] src; ///Local buffer for scaling
165 //size_t[8] prevSize;
166 ///Default ctor
167 public this(RenderingMode renderMode = RenderingMode.AlphaBlend) nothrow @safe {
168 setRenderingMode(renderMode);
169 //Bug workaround: Sometimes when attempting to append an element to a zero-length array, it causes an exception
170 //to be thrown, due to access errors. This bug is unstable, and as such hard to debug for (memory leakage issue?)
171 //displayedSprites.reserve(128);
172 //src[0].length = 1024;
173 }
174 /**
175 * Checks all sprites for whether they're on screen or not.
176 * Called every time the layer is being scrolled.
177 */
178 public void checkAllSprites() @safe nothrow {
179 foreach (key; allSprites) {
180 checkSprite(key);
181 }
182 }
183 /**
184 * Checks whether a sprite would be displayed on the screen, then updates the display list.
185 * Returns true if it's on screen.
186 */
187 public bool checkSprite(int n) @safe nothrow {
188 return checkSprite(allSprites[n]);
189 }
190 ///Ditto.
191 protected bool checkSprite(DisplayListItem sprt) @safe nothrow {
192 //assert(sprt.bmpType != BitmapTypes.Undefined && sprt.pixelData, "DisplayList error!");
193 if(sprt.slice.width && sprt.slice.height
194 && (sprt.position.right > sX && sprt.position.bottom > sY &&
195 sprt.position.left < sX + rasterX && sprt.position.top < sY + rasterY)) {
196 //displayedSprites.put(sprt.priority);
197 return true;
198 } else {
199 //displayedSprites.removeByElem(sprt.priority);
200 return false;
201 }
202 }
203 /**
204 * Searches the DisplayListItem by priority and returns it.
205 * Can be used for external use without any safety issues.
206 */
207 public DisplayListItem getDisplayListItem(int n) @nogc pure @trusted nothrow {
208 return allSprites[n];
209 }
210 /*
211 * Searches the DisplayListItem by priority and returns it.
212 * Intended for internal use, as it returns it as a reference value.
213
214 protected final DisplayListItem* getDisplayListItem_internal(int n) @nogc pure @safe nothrow {
215 return allSprites.searchByPtr(n);
216 }*/
217 /+override public void setRasterizer(int rX,int rY) {
218 super.setRasterizer(rX,rY);
219 }+/
220 ///Returns the displayed portion of the sprite.
221 public Coordinate getSlice(int n) @nogc pure @trusted nothrow {
222 return getDisplayListItem(n).slice;
223 }
224 ///Writes the displayed portion of the sprite.
225 ///Returns the new slice, if invalid (greater than the bitmap, etc.) returns Coordinate.init.
226 public Coordinate setSlice(int n, Coordinate slice) @trusted nothrow {
227 DisplayListItem* sprt = allSprites.searchByPtr(n);
228 if(sprt) {
229 sprt.slice = slice;
230 checkSprite(*sprt);
231 return sprt.slice;
232 } else {
233 return Coordinate.init;
234 }
235 }
236 ///Returns the selected paletteID of the sprite.
237 public ushort getPaletteID(int n) @nogc pure @trusted nothrow {
238 return allSprites.searchBy(n).paletteSel;
239 }
240 ///Sets the paletteID of the sprite. Returns the new ID, which is truncated to the possible values with a simple binary and operation
241 ///Palette must exist in the parent Raster, otherwise AccessError might happen during
242 public ushort setPaletteID(int n, ushort paletteID) @nogc pure @trusted nothrow {
243 DisplayListItem* item = allSprites.searchByPtr(n);
244 if (item is null) return 0;
245 return item.paletteSel = paletteID;
246 }
247 /**
248 * Returns the sprite rendering function.
249 * Params:
250 * n = Sprite priority ID.
251 */
252 public RenderFunc getSpriteRenderingFunc(int n) @nogc @trusted pure nothrow {
253 return allSprites[n].renderFunc;
254 }
255 /**
256 * Sets the sprite's rendering function from a predefined ones.
257 * Params:
258 * n = Sprite priority ID.
259 * mode = The rendering mode. (init for layer default)
260 * Returns: The new rendering function.
261 */
262 public RenderFunc setSpriteRenderingMode(int n, RenderingMode mode) @nogc @trusted pure nothrow {
263 DisplayListItem* item = allSprites.searchByPtr(n);
264 if (item is null) return null;
265 return item.renderFunc = mode == RenderingMode.init ? mainRenderingFunction : getRenderingFunc(mode);
266 }
267 /**
268 * Sets the sprite's rendering function. Can be a custom one.
269 * Params:
270 * n = Sprite priority ID.
271 * mode = The rendering mode. (init for layer default)
272 * Returns: The new rendering function.
273 */
274 public RenderFunc setSpriteRenderingFunc(int n, RenderFunc func) @nogc @trusted pure nothrow {
275 DisplayListItem* item = allSprites.searchByPtr(n);
276 if (item is null) return null;
277 return item.renderFunc = func;
278 }
279 /**
280 * Creates a sprite from a bitmap with the given data, then places it to the display list. (New architecture)
281 * Params:
282 * sprt = The bitmap to be used as the sprite.
283 * n = Priority ID of the sprite. Both identifies the sprite and decides it's display priority. Larger numbers will be drawn first,
284 * and thus will appear behind of smaller numbers, which also include negatives.
285 * x = X position of the sprite (top-left corner).
286 * y = Y position of the sprite (top-left corner).
287 * paletteSel = Selects a given palette.
288 * paletteSh = Determines how many bits are being used, and thus the palette size for selection.
289 * alpha = The transparency of the sprite.
290 * scaleHoriz = Horizontal scaling of the sprite. 1024 is the base value, anything less will stretch, greater will shrink the sprite.
291 * scaleVert = Ditto for vertical.
292 * renderMode = Determines the rendering mode of the sprite. By default, it's determined by the layer itself. Any of the default
293 * other methods can be selected here, or a specially written rendering function can be specified with a different function.
294 * Returns: The current area of the sprite.
295 */
296 public Box addSprite(ABitmap sprt, int n, int x, int y, ushort paletteSel = 0, ubyte paletteSh = 0,
297 ubyte alpha = ubyte.max, int scaleHoriz = 1024, int scaleVert = 1024, RenderingMode renderMode = RenderingMode.init)
298 @safe nothrow {
299 DisplayListItem d = DisplayListItem(x, y, sprt, n, paletteSel, paletteSh, alpha, scaleHoriz, scaleVert);
300 if (renderMode == RenderingMode.init)
301 d.renderFunc = mainRenderingFunction;
302 else
303 d.renderFunc = getRenderingFunc(renderMode);
304 //synchronized{
305 allSprites.put(d);
306 //checkSprite(d);
307 //}
308 return d.position;
309 }
310 /**
311 * Adds a sprite to the layer.
312 */
313 /+public void addSprite(ABitmap s, int n, Box c, ushort paletteSel = 0, int scaleHoriz = 1024,
314 int scaleVert = 1024) @safe nothrow {
315 DisplayListItem d = DisplayListItem(c, s, n, paletteSel, scaleHoriz, scaleVert);
316 d.renderFunc = mainRenderingFunction;
317 synchronized
318 allSprites[n] = d;
319 checkSprite(d);
320 }+/
321 ///Ditto
322 /+public void addSprite(ABitmap s, int n, int x, int y, ushort paletteSel = 0, int scaleHoriz = 1024,
323 int scaleVert = 1024) @safe nothrow {
324 DisplayListItem d = DisplayListItem(Box.bySize(x, y, cast(int)scaleNearestLength(s.width, scaleHoriz),
325 cast(int)scaleNearestLength(s.height, scaleVert)), s, n, paletteSel, scaleHoriz,
326 scaleVert);
327 d.renderFunc = mainRenderingFunction;
328 synchronized
329 allSprites[n] = d;
330 checkSprite(d);
331 }+/
332 /**
333 * Replaces the bitmap of the given sprite.
334 */
335 public void replaceSprite(ABitmap s, int n) @trusted nothrow {
336 DisplayListItem* item = allSprites.searchByPtr(n);
337 if (item is null) return;
338 item.replaceSprite(s);
339 }
340 ///Ditto with move
341 public void replaceSprite(ABitmap s, int n, int x, int y) @trusted nothrow {
342 DisplayListItem* item = allSprites.searchByPtr(n);
343 if (item is null) return;
344 item.replaceSprite(s);
345 item.position.move(x, y);
346 }
347 ///Ditto with move
348 public void replaceSprite(ABitmap s, int n, Coordinate c) @trusted nothrow {
349 DisplayListItem* item = allSprites.searchByPtr(n);
350 if (item is null) return;
351 item.replaceSprite(s);
352 item.position = c;
353 }
354 /**
355 * Removes a sprite from both displaylists by priority.
356 */
357 public void removeSprite(int n) @safe nothrow {
358 allSprites.removeBy(n);
359 }
360 ///Clears all sprite from the layer.
361 public void clear() @safe nothrow {
362 allSprites = DisplayList.init;
363 }
364 /**
365 * Moves a sprite to the given position.
366 */
367 public void moveSprite(int n, int x, int y) @trusted nothrow {
368 DisplayListItem* item = allSprites.searchByPtr(n);
369 if (item is null) return;
370 item.position.move(x, y);
371 }
372 /**
373 * Moves a sprite by the given amount.
374 */
375 public void relMoveSprite(int n, int x, int y) @trusted nothrow {
376 DisplayListItem* item = allSprites.searchByPtr(n);
377 if (item is null) return;
378 item.position.relMove(x, y);
379 //checkSprite(*sprt);
380 }
381 /* ///Sets the rendering function for the sprite (defaults to the layer's rendering function)
382 public void setSpriteRenderingMode(int n, RenderingMode mode) @safe nothrow {
383 DisplayListItem* item = allSprites.searchByPtr(n);
384 if (item is null) return 0;
385 item.renderFunc = getRenderingFunc(mode);
386 } */
387 public @nogc Box getSpriteCoordinate(int n) @trusted nothrow {
388 DisplayListItem* sprt = allSprites.searchByPtr(n);
389 if(!sprt) return Box.init;
390 return sprt.position;
391 }
392 ///Scales sprite horizontally. Returns the new size, or -1 if the scaling value is invalid, or -2 if spriteID not found.
393 public int scaleSpriteHoriz(int n, int hScl) @trusted nothrow {
394 DisplayListItem* sprt = allSprites.searchByPtr(n);
395 if(!sprt) return -2;
396 else if(!hScl) return -1;
397 else {
398 sprt.scaleHoriz = hScl;
399 const int newWidth = cast(int)scaleNearestLength(sprt.width, hScl);
400 sprt.slice.right = newWidth;
401 sprt.position.right = sprt.position.left + newWidth;
402 checkSprite(*sprt);
403 return newWidth;
404 }
405 }
406 ///Scales sprite vertically. Returns the new size, or -1 if the scaling value is invalid, or -2 if spriteID not found.
407 public int scaleSpriteVert(int n, int vScl) @trusted nothrow {
408 DisplayListItem* sprt = allSprites.searchByPtr(n);
409 if(!sprt) return -2;
410 else if(!vScl) return -1;
411 else {
412 sprt.scaleVert = vScl;
413 const int newHeight = cast(int)scaleNearestLength(sprt.height, vScl);
414 sprt.slice.bottom = newHeight;
415 sprt.position.bottom = sprt.position.top + newHeight;
416 checkSprite(*sprt);
417 return newHeight;
418 }
419 }
420 ///Gets the sprite's current horizontal scale value
421 public int getScaleSpriteHoriz(int n) @nogc @trusted nothrow {
422 DisplayListItem* item = allSprites.searchByPtr(n);
423 if (item is null) return 0;
424 return item.scaleHoriz;
425 }
426 ///Gets the sprite's current vertical scale value
427 public int getScaleSpriteVert(int n) @nogc @trusted nothrow {
428 DisplayListItem* item = allSprites.searchByPtr(n);
429 if (item is null) return 0;
430 return item.scaleVert;
431 }
432 public override LayerType getLayerType() @nogc @safe pure nothrow const {
433 return LayerType.Sprite;
434 }
435 public override @nogc void updateRaster(void* workpad, int pitch, Color* palette) {
436 /*
437 * BUG 1: If sprite is wider than 2048 pixels, it'll cause issues (mostly memory leaks) due to a hack. (Fixed!)
438 * BUG 2: Obscuring the top part of a sprite when scaleVert is not 1024 will cause glitches. (Fixed!!!)
439 * TO DO: Replace AVL tree with an automatically sorting array with keying abilities.
440 */
441 foreach (i ; allSprites) {
442 if(!(i.slice.width && i.slice.height
443 && (i.position.right > sX && i.position.bottom > sY &&
444 i.position.left < sX + rasterX && i.position.top < sY + rasterY))) continue;
445 const int left = i.position.left + i.slice.left;
446 const int top = i.position.top + i.slice.top;
447 const int right = i.position.left + i.slice.right;
448 const int bottom = i.position.top + i.slice.bottom;
449 int offsetXA = sX > left ? sX - left : 0;//Left hand side offset, zero if not obscured
450 const int offsetXB = sX + rasterX < right ? right - (sX + rasterX) : 0; //Right hand side offset, zero if not obscured
451 const int offsetYA = sY > top ? sY - top : 0; //top offset of sprite, zero if not obscured
452 const int offsetYB = sY + rasterY < bottom ? bottom - (sY + rasterY) + 1 : 1; //bottom offset of sprite, zero if not obscured
453 const int sizeX = i.slice.width(); //total displayed width after slicing
454 const int offsetX = left - sX;
455 const int length = sizeX - offsetXA - offsetXB - 1; //total displayed width after considering screen borders
456 const int offsetY = sY < top ? (top-sY)*pitch : 0; //used if top portion of the sprite is off-screen
457 const int scaleVertAbs = i.scaleVert * (i.scaleVert < 0 ? -1 : 1); //absolute value of vertical scaling, used in various calculations
458 const int scaleHorizAbs = i.scaleHoriz * (i.scaleHoriz < 0 ? -1 : 1);
459 const int offsetYA0 = (offsetYA * scaleVertAbs)>>>10; //amount of skipped lines in the bitmap source
460 const int sizeXOffset = i.width * (i.scaleVert < 0 ? -1 : 1);
461 int offsetTarget; //the target fractional lines
462 int offset = (offsetYA * scaleVertAbs) & 1023; //the current amount of fractional lines, also contains the fractional offset bias by defauls
463 //const size_t p0offset = (i.scaleHoriz > 0 ? offsetXA : offsetXB); //determines offset based on mirroring
464 //const int scalelength = i.position.width < 2048 ? i.width : 2048; //limit width to 2048, the minimum required for this scaling method to work
465 void* dest = workpad + (offsetX + offsetXA)*4 + offsetY;
466 final switch (i.bmpType) with (BitmapTypes) {
467 case Bmp2Bit:
468 ubyte* p0 = cast(ubyte*)i.pixelData + i.width * ((i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0)>>2);
469 const size_t _pitch = i.width>>>2;
470 for (int y = offsetYA ; y < i.slice.height - offsetYB ; ) {
471 /+horizontalScaleNearest4BitAndCLU(p0, src.ptr, palette + (i.paletteSel<<i.paletteSh), scalelength, offsetXA & 1,
472 i.scaleHoriz);+/
473 horizontalScaleNearestAndCLU(QuadArray(p0[0.._pitch], i.width), src.ptr, palette + (i.paletteSel<<i.paletteSh),
474 length, i.scaleHoriz, offsetXA * scaleHorizAbs);
475 offsetTarget += 1024;
476 for (; offset < offsetTarget && y < i.slice.height - offsetYB ; offset += scaleVertAbs) {
477 y++;
478 i.renderFunc(cast(uint*)src.ptr, cast(uint*)dest, length, i.masterAlpha);
479 dest += pitch;
480 }
481 p0 += _pitch;
482 }
483 break;
484 case Bmp4Bit:
485 ubyte* p0 = cast(ubyte*)i.pixelData + i.width * ((i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0)>>1);
486 const size_t _pitch = i.width>>>1;
487 for (int y = offsetYA ; y < i.slice.height - offsetYB ; ) {
488 /+horizontalScaleNearest4BitAndCLU(p0, src.ptr, palette + (i.paletteSel<<i.paletteSh), scalelength, offsetXA & 1,
489 i.scaleHoriz);+/
490 horizontalScaleNearestAndCLU(NibbleArray(p0[0.._pitch], i.width), src.ptr, palette + (i.paletteSel<<i.paletteSh),
491 length, i.scaleHoriz, offsetXA * scaleHorizAbs);
492 offsetTarget += 1024;
493 for (; offset < offsetTarget && y < i.slice.height - offsetYB ; offset += scaleVertAbs) {
494 y++;
495 i.renderFunc(cast(uint*)src.ptr, cast(uint*)dest, length, i.masterAlpha);
496 dest += pitch;
497 }
498 p0 += _pitch;
499 }
500 break;
501 case Bmp8Bit:
502 ubyte* p0 = cast(ubyte*)i.pixelData + i.width * (i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0);
503 for (int y = offsetYA ; y < i.slice.height - offsetYB ; ) {
504 //horizontalScaleNearestAndCLU(p0, src.ptr, palette + (i.paletteSel<<i.paletteSh), scalelength, i.scaleHoriz);
505 horizontalScaleNearestAndCLU(p0[0..i.width], src.ptr, palette + (i.paletteSel<<i.paletteSh), length, i.scaleHoriz,
506 offsetXA * scaleHorizAbs);
507 offsetTarget += 1024;
508 for (; offset < offsetTarget && y < i.slice.height - offsetYB ; offset += scaleVertAbs) {
509 y++;
510 i.renderFunc(cast(uint*)src.ptr, cast(uint*)dest, length, i.masterAlpha);
511 dest += pitch;
512 }
513 p0 += sizeXOffset;
514 }
515 break;
516 case Bmp16Bit:
517 ushort* p0 = cast(ushort*)i.pixelData + i.width * (i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0);
518 for (int y = offsetYA ; y < i.slice.height - offsetYB ; ) {
519 //horizontalScaleNearestAndCLU(p0, src.ptr, palette, scalelength, i.scaleHoriz);
520 horizontalScaleNearestAndCLU(p0[0..i.width], src.ptr, palette, length, i.scaleHoriz, offsetXA * scaleHorizAbs);
521 offsetTarget += 1024;
522 for (; offset < offsetTarget && y < i.slice.height - offsetYB ; offset += scaleVertAbs) {
523 y++;
524 i.renderFunc(cast(uint*)src.ptr, cast(uint*)dest, length, i.masterAlpha);
525 dest += pitch;
526 }
527 p0 += sizeXOffset;
528 }
529 break;
530 case Bmp32Bit:
531 Color* p0 = cast(Color*)i.pixelData + i.width * (i.scaleVert < 0 ? (i.height - offsetYA0 - 1) : offsetYA0);
532 for (int y = offsetYA ; y < i.slice.height - offsetYB ; ) {
533 horizontalScaleNearest(p0[0..i.width], src, length, i.scaleHoriz, offsetXA * scaleHorizAbs);
534 offsetTarget += 1024;
535 for (; offset < offsetTarget && y < i.slice.height - offsetYB; offset += scaleVertAbs) {
536 y++;
537 i.renderFunc(cast(uint*)src.ptr, cast(uint*)dest, length, i.masterAlpha);
538 dest += pitch;
539 }
540 p0 += sizeXOffset;
541 }
542 //}
543 break;
544 case Undefined, Bmp1Bit, Planar:
545 break;
546 }
547
548 //}
549 }
550 //foreach(int threadOffset; threads.parallel)
551 //free(src[threadOffset]);
552 }
553 ///Absolute scrolling.
554 public override void scroll(int x, int y) @safe nothrow {
555 sX = x;
556 sY = y;
557 //checkAllSprites;
558 }
559 ///Relative scrolling. Positive values scrolls the layer left and up, negative values scrolls the layer down and right.
560 public override void relScroll(int x, int y) @safe nothrow {
561 sX += x;
562 sY += y;
563 //checkAllSprites;
564 }
565 }