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 }