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 }