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