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), localHeight = (iY1 - iY0);
166 			workpad.length  = localWidth * localHeight;
167 			for (int y ; y < localHeight ; y++) {
168 				for (int x ; x < localWidth ; x++) {
169 					workpad[x = (y * localWidth)] = pixels[iX0 + x + ((y + iY0) * _width)];
170 				}
171 			}
172 			return new Bitmap!(S,T)(workpad, localWidth, localHeight);
173 		}
174 	} else static if(S == "HB"){
175 		
176 		///Creates an empty bitmap.
177 		this(int w, int h) @safe pure{
178 			if (w & 1)
179 				throw new BitmapFormatException("Incorrect Bitmap size exception!");
180 			_width=w;
181 			_height=h;
182 			pitch = _width / 2;
183 			pixels.length = pitch * _height;
184 			pixelAccess = NibbleArray(pixels, _width * _height);
185 		}
186 		///Creates a bitmap from an array.
187 		this(ubyte[] p, int w, int h) @safe pure{
188 			if (p.length * 2 != w * h || w & 1)
189 				throw new BitmapFormatException("Incorrect Bitmap size exception!");
190 			_width=w;
191 			_height=h;
192 			pitch = _width / 2;
193 			pixels=p;
194 			pixelAccess = NibbleArray(pixels, _width * _height);
195 		}
196 		///Resizes the array behind the bitmap.
197 		public void resize(int x,int y) @safe pure {
198 			if(x & 1)
199 				throw new BitmapFormatException("Incorrect Bitmap size exception!");
200 			pixels.length = (x / 2) * y;
201 			_width = x;
202 			_height = y;
203 		}
204 	} else static if(S == "QB") {
205 		///Creates an empty bitmap.
206 		this(int w, int h) @safe pure{
207 			if (w & 1)
208 				throw new BitmapFormatException("Incorrect Bitmap size exception!");
209 			_width=w;
210 			_height=h;
211 			pitch = _width / 2;
212 			pixels.length = pitch * _height;
213 			pixelAccess = QuadArray(pixels, _width * _height);
214 		}
215 		///Creates a bitmap from an array.
216 		this(ubyte[] p, int w, int h) @safe pure{
217 			if (p.length * 4 != w * h || w & 3)
218 				throw new BitmapFormatException("Incorrect Bitmap size exception!");
219 			_width=w;
220 			_height=h;
221 			pitch = _width / 2;
222 			pixels=p;
223 			pixelAccess = QuadArray(pixels, _width * _height);
224 		}
225 	} else static if(S == "b") {
226 		/**
227 		 * CTOR for 1 bit bitmaps with no preexisting source.
228 		 */
229 		public this(int w, int h) @trusted pure {
230 			_width = w;
231 			_height = h;
232 			pitch = w + (size_t.sizeof * 8 - (w % (size_t.sizeof * 8)));
233 			pixels.length = pitch / (size_t.sizeof * 8) * h;
234 			pixelAccess = BitArray(pixels, pitch * height);
235 		}
236 		/**
237 		 * CTOR to convert 8bit aligned bitmaps to 32/64bit ones.
238 		 */
239 		public this(ubyte[] src, int w, int h) @trusted pure {
240 			_width = w;
241 			_height = h;
242 			pitch = w + (size_t.sizeof * 8 - (w % (size_t.sizeof * 8)));
243 			const size_t pitch0 = w + (8 - (w % 8));
244 			const size_t len = pitch / (size_t.sizeof * 8), len0 = pitch0 / 8;
245 			for (size_t i ; i < len0 * h; i+= len0) {
246 				ubyte[] workpad = src[i..i+len0];
247 				workpad.length = len;
248 				pixels ~= reinterpretCast!size_t(workpad);
249 			}
250 			pixelAccess = BitArray(pixels, pitch * height);
251 		}
252 		/**
253 		 * CTOR for 1 bit bitmaps with a preexisting source.
254 		 * Alignment and padding is for size_t (32 and 64 bit, on their respected systems)
255 		 */
256 		public this(size_t[] src, int w, int h) @trusted pure {
257 			_width = w;
258 			_height = h;
259 			pitch = w + (size_t.sizeof * 8 - (w % (size_t.sizeof * 8)));
260 			pixels = src;
261 			pixelAccess = BitArray(pixels, pitch * height);
262 		}
263 		static if (size_t.sizeof == 8) {
264 			static enum SHAM = 6;
265 			static enum SHFLAG = 0x3F;
266 			static enum BITAM = 64;
267 		} else {
268 			static enum SHAM = 5;
269 			static enum SHFLAG = 0x1F;
270 			static enum BITAM = 32;
271 		}
272 		/** 
273 		 * Tests two collision models against each other.
274 		 * Params:
275 		 *   line = The first line to test against on the left hand side collision model.
276 		 *   lineAm = The amount of lines to be tested.
277 		 *   other = The right hand side collision model.
278 		 *   otherLine = The first line to test against on the right hand side collision model.
279 		 *   offset = The amount, which the right hand side collision model's left edge is away from the left hand
280 		 * side collision model's left edge.
281 		 *   overlapAm = The amount of overlap between the two objects.
282 		 * Returns: 
283 		 */
284 		public bool testCollision(int line, int lineAm, Bitmap1Bit other, int otherLine, const int offset, 
285 				const int overlapAm) @safe @nogc nothrow pure const {
286 			const int chOffset = offset>>SHAM;
287 			assert(overlapAm, "This function should not be called if `overlapAm == 0`");
288 			assert(lineAm, "This function should not be called if `lineAm == 0`");
289 			for ( ; lineAm > 0 ; lineAm--, line++, otherLine++) {
290 				int overlapCntr = overlapAm;
291 				int loffset = offset;
292 				int lchOffset = chOffset;
293 				if (testChunkCollision(line, chOffset, other, otherLine, loffset))
294 					return true;
295 				overlapCntr -= BITAM;
296 				loffset += BITAM;
297 				lchOffset++;
298 				for ( ; overlapCntr > 0 ; overlapCntr -= BITAM, loffset += BITAM, lchOffset++) {
299 					if (testChunkCollisionB(line, chOffset, other, otherLine, loffset))
300 						return true;
301 				}
302 			}
303 			return false;
304 		}
305 		/**
306 		 * Tests a single chunk of pixels between two 1 bit bitmaps for collision, using a single chunk (size_t) of 
307 		 * pixels.
308 		 * Params:
309 		 *   line: The (first) line, which is being tested in the current object.
310 		 *   other: The other object that this is being tested against.
311 		 *   otherLine: The (first) line, which is being tested in the other object.
312 		 *   offset: The horizontal offset of the other object to the right.
313 		 * Returns: True is collision has been detected.
314 		 */
315 		final protected bool testChunkCollision(int line, int chOffset, Bitmap1Bit other, int otherLine, int offset) 
316 				@safe @nogc nothrow pure const {
317 			if (pixels[chOffset + (line * (pitch>>SHAM))] & 
318 					(other.pixels[(offset>>SHAM) + ((other.pitch>>SHAM) * otherLine)]<<(offset & SHFLAG)))
319 				return true;
320 			return false;
321 		}
322 		/**
323 		 * Tests a single chunk of pixels between two 1 bit bitmaps for collision, using a single chunk (size_t) of 
324 		 * pixels, if other is wider than what size_t allows.
325 		 * Params:
326 		 *   line: The (first) line, which is being tested in the current object.
327 		 *   other: The other object that this is being tested against.
328 		 *   otherLine: The (first) line, which is being tested in the other object.
329 		 *   offset: The horizontal offset of the other object to the right.
330 		 * Returns: True is collision has been detected.
331 		 */
332 		final protected bool testChunkCollisionB(int line, int chOffset, Bitmap1Bit other, int otherLine, int offset) 
333 				@safe @nogc nothrow pure const {
334 			if (pixels[chOffset + (line * (pitch>>SHAM))] & 
335 					((other.pixels[(offset>>SHAM) + ((other.pitch>>SHAM) * otherLine)]<<(offset & SHFLAG)) |
336 					(other.pixels[((offset>>SHAM) - 1) + ((other.pitch>>SHAM) * otherLine)]>>(SHFLAG - (offset & SHFLAG)))))
337 				return true;
338 			return false;
339 		}
340 	}
341 	static if(S == "B" || S == "HW") {
342 		/**
343 		 * Offsets all indexes in the bitmap by a certain value. Keeps zeroth index (usually for transparency) if needed. Useful when converting bitmaps.
344 		 */
345 		public @nogc void offsetIndexes(ushort offset, bool keepZerothIndex = true) @safe pure{
346 			for(int i ; i < pixels.length ; i++){
347 				if(!(pixels[i] == 0 && keepZerothIndex)){
348 					pixels[i] += offset;
349 				}
350 			}
351 		}
352 	}
353 	static if (S == "b" || S == "QB" || S == "HB") {
354 		/**
355 		 * Returns a 2D slice (window) of the bitmap.
356 		 */
357 		public Bitmap!(S,T) window(int iX0, int iY0, int iX1, int iY1) @safe pure {
358 			const int localWidth = (iX1 - iX0), localHeight = (iY1 - iY0);
359 			Bitmap!(S,T) result = new Bitmap!(S,T)(localWidth, localHeight);
360 			for (int y ; y < localHeight ; y++) {
361 				for (int x ; x < localWidth ; x++) {
362 					result.writePixel(x, y, readPixel(iX0 + x, iY0 + y));
363 				}
364 			}
365 			return result;
366 		}
367 	}
368 	static if (S == "QB" || S == "HB") {
369 		///Returns the pixel at the given position.
370 		@nogc public T readPixel(int x, int y) @trusted pure {
371 			assert (x >= 0 && x < _width && y >= 0 && y < _height);
372 			return pixelAccess[x + (y * pitch)];
373 		}
374 		///Writes the pixel at the given position.
375 	    @nogc public T writePixel(int x, int y, T val) @trusted pure {
376 			assert (x >= 0 && x < _width && y >= 0 && y < _height);
377 			return pixelAccess[x + (y * pitch)] = val;
378 		}}
379 	static if(S == "W"){
380 		/**
381 		 * Clears the Bitmap
382 		 */
383 		override void clear() @nogc @safe pure nothrow {
384 			for(int i ; i < pixels.length ; i++){
385 				pixels[i] = Color(0x0);
386 			}
387 		}
388 	}else{
389 		override void clear() @nogc @safe pure nothrow {
390 			for(int i ; i < pixels.length ; i++){
391 				pixels[i] = 0;
392 			}
393 		}
394 	}
395 	@nogc public T* getPtr() pure @trusted nothrow {
396 		return pixels.ptr;
397 	}
398 	override string wordLengthByString() @safe @nogc pure nothrow @property const {
399 		return S;
400 	}
401 	static if (S != "b") {
402 		/**
403 		 * Generates a standard collision model by checking against a transparency value (default vaule is T.init).
404 		 */
405 		Bitmap1Bit generateStandardCollisionModel(const T transparency = T.init) {
406 			Bitmap1Bit output = new Bitmap1Bit(width, height);
407 			for (int y ; y < height ; y++) {
408 				for (int x ; x < width ; x++) {
409 					output.writePixel(x, y, readPixel(x, y) != transparency);
410 				}
411 			}
412 			return output;
413 		}
414 	} else {
415 		///Returns the pixel at the given position.
416 		@nogc public bool readPixel(int x, int y) @trusted pure {
417 			assert (x >= 0 && x < _width && y >= 0 && y < _height);
418 			return pixelAccess[x + (y * pitch)];
419 		}
420 		///Writes the pixel at the given position.
421 	    @nogc public bool writePixel(int x, int y, bool val) @trusted pure {
422 			assert (x >= 0 && x < _width && y >= 0 && y < _height);
423 			return pixelAccess[x + (y * pitch)] = val;
424 		}
425 	}
426 }
427 
428 /**
429  * Defines Bitmap types
430  */
431 public enum BitmapTypes : ubyte {
432 	Undefined,			///Can be used for error checking, e.g. if a tile was initialized or not
433 	Bmp1Bit,
434 	Bmp2Bit,
435 	Bmp4Bit,
436 	Bmp8Bit,
437 	Bmp16Bit,
438 	Bmp32Bit,
439 	Planar,				///Mainly used as a placeholder
440 }