1 /*
2  * Copyright (C) 2015-2017, by Laszlo Szeremi under the Boost license.
3  *
4  * pixel Perfect Engine, graphics.bitmap module
5  */
6 
7 module pixelperfectengine.graphics.bitmap;
8 import std.bitmanip;
9 import pixelperfectengine.system.exc;
10 import bitleveld.reinterpret;
11 import bitleveld.datatypes;
12 
13 //public import pixelperfectengine.system.advBitArray;
14 public import pixelperfectengine.graphics.common;
15 
16 /**
17  * Bitmap attributes, mainly for layers.
18  */
19 public struct BitmapAttrib{
20 	mixin(bitfields!(
21 		bool, "horizMirror", 1,
22 		bool, "vertMirror", 1,
23 		ubyte, "priority", 6));
24 	public this(bool horizMirror, bool vertMirror, ubyte priority = 0) @nogc nothrow @safe pure{
25 		this.horizMirror = horizMirror;
26 		this.vertMirror = vertMirror;
27 		this.priority = priority;
28 	}
29 	string toString() const @safe pure nothrow{
30 		return "[horizMirror: " ~ horizMirror ~ " ; vertMirror: " ~ vertMirror ~ " ; priority: " ~ priority ~ "]";
31 	}
32 }
33 
34 /**
35  * Base bitmap functions, for enable the use of the same .
36  */
37 abstract class ABitmap{
38 	private Color* palettePtr;		///Set this to either a portion of the master palette or to a self-defined place. Not used in 32 bit bitmaps. DEPRECATED!
39     private int _width;
40     private int _height;
41 	/**
42 	 * Returns the width of the bitmap.
43 	 */
44 	public int width() pure @safe @property @nogc nothrow const {
45 		return _width;
46 	}
47 	/**
48 	 * Returns the height of the bitmap.
49 	 */
50 	public int height() pure @safe @property @nogc nothrow const {
51 		return _height;
52 	}
53 	/**
54 	 * Returns the palette pointer.
55 	 * DEPRECATED!
56 	 */
57 	public deprecated Color* getPalettePtr() pure @trusted @property @nogc nothrow {
58 		return palettePtr;
59 	}
60 	/**
61 	 * Sets the palette pointer. Make sure that you set it to a valid memory location.
62 	 * DEPRECATED!
63 	 */
64 	public deprecated void setPalettePtr(Color* p) pure @safe @property @nogc nothrow {
65 		palettePtr = p;
66 	}
67 	/**
68 	 * Returns the wordlength of the type
69 	 */
70 	abstract string wordLengthByString() pure @safe @property @nogc nothrow const;
71 	/**
72 	 * Clears the whole bitmap to a transparent color.
73 	 */
74 	abstract void clear() pure @safe @nogc nothrow;
75 }
76 /*
77  * S: Wordlength by usage. Possible values:
78  * - b: bit (for collision shapes)
79  * - QB: QuarterByte or 2Bit (currently unimplemented)
80  * - HB: HalfByte or 4Bit
81  * - B: Byte or 8Bit
82  * - HW: HalfWord or 16Bit
83  * - W: Word or 32Bit
84  * T: Type. Possible values:
85  * - size_t: used for bitarrays
86  * - ubyte: 8Bit or under
87  * - ushort: 16Bit
88  * - Color: 32Bit
89  */
90 alias Bitmap1Bit = Bitmap!("b",size_t);
91 alias Bitmap2Bit = Bitmap!("QB",ubyte);
92 alias Bitmap4Bit = Bitmap!("HB",ubyte);
93 alias Bitmap8Bit = Bitmap!("B",ubyte);
94 alias Bitmap16Bit = Bitmap!("HW",ushort);
95 alias Bitmap32Bit = Bitmap!("W",Color);
96 /**
97  * Implements a bitmap with variable bit depth. Use the aliases to initialize them.
98  *
99  * Note on 16 bit bitmaps: It's using the master palette, It's not implementing any 16 bit RGB or RGBA color space 
100  * directly. Can implement such
101  * colorspaces via proper lookup tables.
102  *
103  * Note on 4 bit bitmaps: It's width needs to be an even number (for rendering simplicity), otherwise it'll cause an 
104  * exception.
105  *
106  * Note on 1 bit bitmaps: Uses size_t based paddings for more than one bit testing at the time in the future, through
107  * the use of logic functions.
108  */
109 public class Bitmap(string S,T) : ABitmap {
110 	protected size_t 	pitch;			///Total length of a line in bits
111 	static if (S == "b") { 
112 		BitArray 			pixelAccess;
113 		//bool				invertHoriz;	///Horizontal invertion for reading and writing
114 		//bool				invertVert;		///Vertical invertion for reading anr writing
115 	} else static if (S == "QB") {
116 		QuadArray			pixelAccess;
117 	} else static if (S == "HB") {
118 		NibbleArray			pixelAccess;
119 	}
120 	/**
121 	 * Image data.
122 	 */
123 	T[] 					pixels;
124 	static if(S != "HB" && S != "QB" && S != "b"){
125 		/**
126 		 * Unified CTOR to create empty bitmap.
127 		 */
128 		public this(int w, int h) @safe pure {
129 			_width = w;
130 			_height = h;
131 			pixels.length = w * h;
132 		}
133 		/**
134 		 * Unified CTOR tor create bitmap from preexisting data.
135 		 */
136 		public this(T[] src, int w, int h) @safe pure {
137 			_width = w;
138 			_height = h;
139 			pixels = src;
140 			if(pixels.length != w * h)
141 				throw new BitmapFormatException("Bitmap size mismatch!");
142 		}
143 		/**
144 		 * Resizes the bitmap.
145 		 * NOTE: It's not for scaling.
146 		 */
147 		public void resize(int x, int y) @safe pure {
148 			pixels.length=x*y;
149 			_width = x;
150 			_height = y;
151 		}
152 		///Returns the pixel at the given position.
153 		@nogc public T readPixel(int x, int y) @safe pure {
154 			return pixels[x+(_width*y)];
155 		}
156 		///Writes the pixel at the given position.
157 		@nogc public T writePixel(int x, int y, T color) @safe pure {
158 			return pixels[x+(_width*y)]=color;
159 		}
160 		/**
161 		 * Returns a 2D slice (window) of the bitmap.
162 		 */
163 		public Bitmap!(S,T) window(int iX0, int iY0, int iX1, int iY1) @safe pure {
164 			T[] workpad;
165 			const int localWidth = (iX1 - iX0 + 1), localHeight = (iY1 - iY0 + 1);
166 			assert (localWidth <= width && localWidth);
167 			assert (localHeight <= height && localHeight);
168 			workpad.length  = localWidth * localHeight;
169 			for (int y ; y < localHeight ; y++) {
170 				for (int x ; x < localWidth ; x++) {
171 					workpad[x + (y * localWidth)] = pixels[iX0 + x + ((y + iY0) * _width)];
172 				}
173 			}
174 			return new Bitmap!(S,T)(workpad, localWidth, localHeight);
175 		}
176 	} else static if(S == "HB"){
177 		
178 		///Creates an empty bitmap.
179 		this(int w, int h) @safe pure{
180 			if (w & 1)
181 				throw new BitmapFormatException("Incorrect Bitmap size exception!");
182 			_width=w;
183 			_height=h;
184 			pitch = _width / 2;
185 			pixels.length = pitch * _height;
186 			pixelAccess = NibbleArray(pixels, _width * _height);
187 		}
188 		///Creates a bitmap from an array.
189 		this(ubyte[] p, int w, int h) @safe pure{
190 			if (p.length * 2 != w * h || w & 1)
191 				throw new BitmapFormatException("Incorrect Bitmap size exception!");
192 			_width=w;
193 			_height=h;
194 			pitch = _width / 2;
195 			pixels=p;
196 			pixelAccess = NibbleArray(pixels, _width * _height);
197 		}
198 		///Resizes the array behind the bitmap.
199 		public void resize(int x,int y) @safe pure {
200 			if(x & 1)
201 				throw new BitmapFormatException("Incorrect Bitmap size exception!");
202 			pixels.length = (x / 2) * y;
203 			_width = x;
204 			_height = y;
205 		}
206 	} else static if(S == "QB") {
207 		///Creates an empty bitmap.
208 		this(int w, int h) @safe pure{
209 			if (w & 1)
210 				throw new BitmapFormatException("Incorrect Bitmap size exception!");
211 			_width=w;
212 			_height=h;
213 			pitch = _width / 2;
214 			pixels.length = pitch * _height;
215 			pixelAccess = QuadArray(pixels, _width * _height);
216 		}
217 		///Creates a bitmap from an array.
218 		this(ubyte[] p, int w, int h) @safe pure{
219 			if (p.length * 4 != w * h || w & 3)
220 				throw new BitmapFormatException("Incorrect Bitmap size exception!");
221 			_width=w;
222 			_height=h;
223 			pitch = _width / 2;
224 			pixels=p;
225 			pixelAccess = QuadArray(pixels, _width * _height);
226 		}
227 	} else static if(S == "b") {
228 		/**
229 		 * CTOR for 1 bit bitmaps with no preexisting source.
230 		 */
231 		public this(int w, int h) @trusted pure {
232 			_width = w;
233 			_height = h;
234 			pitch = w + (size_t.sizeof * 8 - (w % (size_t.sizeof * 8)));
235 			pixels.length = pitch / (size_t.sizeof * 8) * h;
236 			pixelAccess = BitArray(pixels, pitch * height);
237 		}
238 		/**
239 		 * CTOR to convert 8bit aligned bitmaps to 32/64bit ones.
240 		 */
241 		public this(ubyte[] src, int w, int h) @trusted pure {
242 			_width = w;
243 			_height = h;
244 			pitch = w + (size_t.sizeof * 8 - (w % (size_t.sizeof * 8)));
245 			const size_t pitch0 = w + (8 - (w % 8));
246 			const size_t len = pitch / (size_t.sizeof * 8), len0 = pitch0 / 8;
247 			for (size_t i ; i < len0 * h; i+= len0) {
248 				ubyte[] workpad = src[i..i+len0];
249 				workpad.length = len;
250 				pixels ~= reinterpretCast!size_t(workpad);
251 			}
252 			pixelAccess = BitArray(pixels, pitch * height);
253 		}
254 		/**
255 		 * CTOR for 1 bit bitmaps with a preexisting source.
256 		 * Alignment and padding is for size_t (32 and 64 bit, on their respected systems)
257 		 */
258 		public this(size_t[] src, int w, int h) @trusted pure {
259 			_width = w;
260 			_height = h;
261 			pitch = w + (size_t.sizeof * 8 - (w % (size_t.sizeof * 8)));
262 			pixels = src;
263 			pixelAccess = BitArray(pixels, pitch * height);
264 		}
265 		static if (size_t.sizeof == 8) {
266 			static enum SHAM = 6;
267 			static enum SHFLAG = 0x3F;
268 			static enum BITAM = 64;
269 		} else {
270 			static enum SHAM = 5;
271 			static enum SHFLAG = 0x1F;
272 			static enum BITAM = 32;
273 		}
274 		/** 
275 		 * Tests two collision models against each other.
276 		 * Params:
277 		 *   line = The first line to test against on the left hand side collision model.
278 		 *   lineAm = The amount of lines to be tested.
279 		 *   other = The right hand side collision model.
280 		 *   otherLine = The first line to test against on the right hand side collision model.
281 		 *   offset = The amount, which the right hand side collision model's left edge is away from the left hand
282 		 * side collision model's left edge.
283 		 *   overlapAm = The amount of overlap between the two objects.
284 		 * Returns: 
285 		 */
286 		public bool testCollision(int line, int lineAm, Bitmap1Bit other, int otherLine, const int offset, 
287 				const int overlapAm) @safe @nogc nothrow pure const {
288 			const int chOffset = offset>>SHAM;
289 			assert(overlapAm, "This function should not be called if `overlapAm == 0`");
290 			assert(lineAm, "This function should not be called if `lineAm == 0`");
291 			for ( ; lineAm > 0 ; lineAm--, line++, otherLine++) {
292 				int overlapCntr = overlapAm;
293 				int loffset = offset;
294 				int lchOffset = chOffset;
295 				if (testChunkCollision(line, chOffset, other, otherLine, loffset))
296 					return true;
297 				overlapCntr -= BITAM;
298 				loffset += BITAM;
299 				lchOffset++;
300 				for ( ; overlapCntr > 0 ; overlapCntr -= BITAM, loffset += BITAM, lchOffset++) {
301 					if (testChunkCollisionB(line, chOffset, other, otherLine, loffset))
302 						return true;
303 				}
304 			}
305 			return false;
306 		}
307 		/**
308 		 * Tests a single chunk of pixels between two 1 bit bitmaps for collision, using a single chunk (size_t) of 
309 		 * pixels.
310 		 * Params:
311 		 *   line: The (first) line, which is being tested in the current object.
312 		 *   other: The other object that this is being tested against.
313 		 *   otherLine: The (first) line, which is being tested in the other object.
314 		 *   offset: The horizontal offset of the other object to the right.
315 		 * Returns: True is collision has been detected.
316 		 */
317 		final protected bool testChunkCollision(int line, int chOffset, Bitmap1Bit other, int otherLine, int offset) 
318 				@safe @nogc nothrow pure const {
319 			if (pixels[chOffset + (line * (pitch>>SHAM))] & 
320 					(other.pixels[(offset>>SHAM) + ((other.pitch>>SHAM) * otherLine)]<<(offset & SHFLAG)))
321 				return true;
322 			return false;
323 		}
324 		/**
325 		 * Tests a single chunk of pixels between two 1 bit bitmaps for collision, using a single chunk (size_t) of 
326 		 * pixels, if other is wider than what size_t allows.
327 		 * Params:
328 		 *   line: The (first) line, which is being tested in the current object.
329 		 *   other: The other object that this is being tested against.
330 		 *   otherLine: The (first) line, which is being tested in the other object.
331 		 *   offset: The horizontal offset of the other object to the right.
332 		 * Returns: True is collision has been detected.
333 		 */
334 		final protected bool testChunkCollisionB(int line, int chOffset, Bitmap1Bit other, int otherLine, int offset) 
335 				@safe @nogc nothrow pure const {
336 			if (pixels[chOffset + (line * (pitch>>SHAM))] & 
337 					((other.pixels[(offset>>SHAM) + ((other.pitch>>SHAM) * otherLine)]<<(offset & SHFLAG)) |
338 					(other.pixels[((offset>>SHAM) - 1) + ((other.pitch>>SHAM) * otherLine)]>>(SHFLAG - (offset & SHFLAG)))))
339 				return true;
340 			return false;
341 		}
342 	}
343 	static if(S == "B" || S == "HW") {
344 		/**
345 		 * Offsets all indexes in the bitmap by a certain value. Keeps zeroth index (usually for transparency) if needed. Useful when converting bitmaps.
346 		 */
347 		public @nogc void offsetIndexes(ushort offset, bool keepZerothIndex = true) @safe pure{
348 			for(int i ; i < pixels.length ; i++){
349 				if(!(pixels[i] == 0 && keepZerothIndex)){
350 					pixels[i] += offset;
351 				}
352 			}
353 		}
354 	}
355 	static if (S == "b" || S == "QB" || S == "HB") {
356 		/**
357 		 * Returns a 2D slice (window) of the bitmap.
358 		 */
359 		public Bitmap!(S,T) window(int iX0, int iY0, int iX1, int iY1) @safe pure {
360 			const int localWidth = iX1 - iX0 + 1, localHeight = iY1 - iY0 + 1;
361 			assert (localWidth <= width && localWidth > 0);
362 			assert (localHeight <= height && localHeight > 0);
363 			Bitmap!(S,T) result = new Bitmap!(S,T)(localWidth, localHeight);
364 			for (int y ; y < localHeight ; y++) {
365 				for (int x ; x < localWidth ; x++) {
366 					result.writePixel(x, y, readPixel(iX0 + x, iY0 + y));
367 				}
368 			}
369 			return result;
370 		}
371 	}
372 	static if (S == "QB" || S == "HB") {
373 		///Returns the pixel at the given position.
374 		@nogc public T readPixel(int x, int y) @trusted pure {
375 			assert (x >= 0 && x < _width && y >= 0 && y < _height);
376 			return pixelAccess[x + (y * pitch)];
377 		}
378 		///Writes the pixel at the given position.
379 	    @nogc public T writePixel(int x, int y, T val) @trusted pure {
380 			assert (x >= 0 && x < _width && y >= 0 && y < _height);
381 			return pixelAccess[x + (y * pitch)] = val;
382 		}}
383 	static if(S == "W"){
384 		/**
385 		 * Clears the Bitmap
386 		 */
387 		override void clear() @nogc @safe pure nothrow {
388 			for(int i ; i < pixels.length ; i++){
389 				pixels[i] = Color(0x0);
390 			}
391 		}
392 	}else{
393 		override void clear() @nogc @safe pure nothrow {
394 			for(int i ; i < pixels.length ; i++){
395 				pixels[i] = 0;
396 			}
397 		}
398 	}
399 	@nogc public T* getPtr() pure @trusted nothrow {
400 		return pixels.ptr;
401 	}
402 	override string wordLengthByString() @safe @nogc pure nothrow @property const {
403 		return S;
404 	}
405 	static if (S != "b") {
406 		/**
407 		 * Generates a standard collision model by checking against a transparency value (default vaule is T.init).
408 		 */
409 		Bitmap1Bit generateStandardCollisionModel(const T transparency = T.init) {
410 			Bitmap1Bit output = new Bitmap1Bit(width, height);
411 			for (int y ; y < height ; y++) {
412 				for (int x ; x < width ; x++) {
413 					output.writePixel(x, y, readPixel(x, y) != transparency);
414 				}
415 			}
416 			return output;
417 		}
418 	} else {
419 		///Returns the pixel at the given position.
420 		@nogc public bool readPixel(int x, int y) @trusted pure {
421 			assert (x >= 0 && x < _width && y >= 0 && y < _height);
422 			return pixelAccess[x + (y * pitch)];
423 		}
424 		///Writes the pixel at the given position.
425 	    @nogc public bool writePixel(int x, int y, bool val) @trusted pure {
426 			assert (x >= 0 && x < _width && y >= 0 && y < _height);
427 			return pixelAccess[x + (y * pitch)] = val;
428 		}
429 	}
430 }
431 
432 /**
433  * Defines Bitmap types
434  */
435 public enum BitmapTypes : ubyte {
436 	Undefined,			///Can be used for error checking, e.g. if a tile was initialized or not
437 	Bmp1Bit,
438 	Bmp2Bit,
439 	Bmp4Bit,
440 	Bmp8Bit,
441 	Bmp16Bit,
442 	Bmp32Bit,
443 	Planar,				///Mainly used as a placeholder
444 }