1 /*
2  * Copyright (C) 2015-2017, by Laszlo Szeremi under the Boost license.
3  *
4  * Pixel Perfect Engine, graphics.draw module
5  */
6 
7 module pixelperfectengine.graphics.draw;
8 
9 import std.stdio;
10 import std.math;
11 import std.conv;
12 
13 import pixelperfectengine.graphics.bitmap;
14 import compose = CPUblit.composing;
15 import specblt = CPUblit.composing.specblt;
16 import draw = CPUblit.draw;
17 import bmfont;
18 public import pixelperfectengine.graphics.fontsets;
19 public import pixelperfectengine.graphics.common;
20 import pixelperfectengine.graphics.text : Text;
21 //import system.etc;
22 /**
23  * Draws into a 8bit bitmap.
24  */
25 public class BitmapDrawer{
26 	public Bitmap8Bit output;
27 	public ubyte brushTransparency;
28 	///Creates the object alongside its output.
29 	public this(int x, int y) pure {
30 		output = new Bitmap8Bit(x, y);
31 
32 	}
33 	/+
34 	///Draws a single line. DEPRECATED!
35 	deprecated public void drawLine(int xa, int xb, int ya, int yb, ubyte color) pure {
36 		draw.drawLine(xa, ya, xb, yb, color, output.getPtr(), output.width);
37 	}+/
38 	/**
39 	 * Draws a single line.
40 	 * Parameters:
41 	 *  from = The beginning of the line.
42 	 *  to = The endpoint of the line.
43 	 *  color = The color index to be used for the line.
44 	 */
45 	public void drawLine(Point from, Point to, ubyte color) pure {
46 		draw.drawLine(from.x, from.y, to.x, to.y, color, output.getPtr(), output.width);
47 	}
48 	/**
49 	 * Draws a line with a pattern.
50 	 * Parameters:
51 	 *  from = The beginning of the line.
52 	 *  to = The end of the line.
53 	 *  pattern = Contains the color indexes for the line, to draw the pattern.
54 	 */
55 	public void drawLinePattern(Point from, Point to, ubyte[] pattern) pure {
56 		draw.drawLinePattern(from.x, from.y, to.x, to.y, pattern, output.getPtr(), output.width);
57 	}
58 	/**
59 	 * Draws a box.
60 	 * Parameters:
61 	 *  target = Containst the coordinates of the box to be drawn.
62 	 *  color = The color index which the box will be drawn.
63 	 */
64 	public void drawBox(Box target, ubyte color) pure {
65 		draw.drawLine(target.left, target.top, target.right, target.top, color, output.getPtr(), output.width);
66 		draw.drawLine(target.left, target.top, target.left, target.bottom, color, output.getPtr(), output.width);
67 		draw.drawLine(target.left, target.bottom, target.right, target.bottom, color, output.getPtr(), output.width);
68 		draw.drawLine(target.right, target.top, target.right, target.bottom, color, output.getPtr(), output.width);
69 	}
70 	/**
71 	 * Draws a box with the supplied pattern as the 
72 	 * Parameters:
73 	 *  target = Containst the coordinates of the box to be drawn.
74 	 *  color = The color index which the box will be drawn.
75 	 */
76 	public void drawBox(Coordinate target, ubyte[] pattern) pure {
77 		draw.drawLinePattern(target.left, target.top, target.right, target.top, pattern, output.getPtr(), output.width);
78 		draw.drawLinePattern(target.left, target.top, target.left, target.bottom, pattern, output.getPtr(), output.width);
79 		draw.drawLinePattern(target.left, target.bottom, target.right, target.bottom, pattern, output.getPtr(), output.width);
80 		draw.drawLinePattern(target.right, target.top, target.right, target.bottom, pattern, output.getPtr(), output.width);
81 	}
82 	/**
83 	 * Draws a filled box.
84 	 * Parameters:
85 	 *  target = The position of the box.
86 	 *  color = The color of the box (both the line and fill color).
87 	 */
88 	public void drawFilledBox(Coordinate target, ubyte color) pure {
89 		draw.drawFilledRectangle(target.left, target.top, target.right, target.bottom, color, output.getPtr(), output.width);
90 	}
91 	/**
92 	 * Copies a bitmap to the canvas using 0th index transparency.
93 	 * Parameters:
94 	 *  target = Where the top-left corner should fall.
95 	 *  source = The bitmap to be copied into the output.
96 	 */
97 	public void bitBLT(Point target, Bitmap8Bit source) pure {
98 		ubyte* src = source.getPtr;
99 		ubyte* dest = output.getPtr + (output.width * target.y) + target.x;
100 		for (int y ; y < source.height ; y++){
101 			compose.blitter(src, dest, source.width);
102 			src += source.width;
103 			dest += output.width;
104 		}
105 	}
106 	/**
107 	 * Copies a bitmap slice to the canvas using 0th index transparency.
108 	 * Parameters:
109 	 *  target = Where the top-left corner should fall.
110 	 *  source = The bitmap to be copied into the output.
111 	 *  slice = Defines what  part of the bitmap should be copied.
112 	 */
113 	public void bitBLT(Point target, Bitmap8Bit source, Coordinate slice) pure {
114 		ubyte* src = source.getPtr + (source.width * slice.top) + slice.left;
115 		ubyte* dest = output.getPtr + (output.width * target.y) + target.x;
116 		for (int y ; y < slice.height ; y++){
117 			compose.blitter(src, dest, slice.width);
118 			src += source.width;
119 			dest += output.width;
120 		}
121 	}
122 	/**
123 	 * Fills the specified area with a pattern.
124 	 * Parameters:
125 	 *  pos = The area that needs to be filled with the pattern.
126 	 *  pattern = The pattern to be used.
127 	 */
128 	public void bitBLTPattern(Box pos, Bitmap8Bit pattern) pure {
129 		const int targetX = pos.width / pattern.width;
130 		const int targetX0 = pos.width % pattern.width;
131 		const int targetY = pos.height / pattern.height;
132 		const int targetY0 = pos.height % pattern.height;
133 		for(int y ; y < targetY ; y++) {
134 			for(int x ; x < targetX; x++) 
135 				bitBLT(Point(pos.left + (x * pattern.width), pos.top + (y * pattern.height)), pattern);
136 			if(targetX0) 
137 				bitBLT(Point(pos.left + (pattern.width * targetX), pos.top + (y * pattern.height)), pattern,
138 						Box(0, 0, targetX0, pattern.height + 1));
139 		}
140 		if(targetY0) {
141 			for(int x ; x < targetX; x++) 
142 				bitBLT(Point(pos.left + (x * pattern.width), pos.top + (targetY * pattern.height)), pattern,
143 						Box(0, 0, pattern.width - 1, targetY0));
144 			if(targetX0) 
145 				bitBLT(Point(pos.left + (pattern.width * targetX), pos.top + (targetY * pattern.height)), pattern,
146 						Box(0, 0, targetX0, targetY0));
147 		}
148 	}
149 	/**
150 	 * XOR blits a repeated bitmap pattern over the specified area.
151 	 * Parameters:
152 	 *  target = The area to be XOR blitted.
153 	 *  pattern = Specifies the pattern to be used.
154 	 */
155 	public void xorBitBLT(Box target, Bitmap8Bit pattern) pure {
156 		import CPUblit.composing.specblt;
157 		ubyte* dest = output.getPtr + target.left + (target.top * output.width);
158 		for (int y ; y < target.height ; y++) {
159 			for (int x ; x < target.width ; x += pattern.width) {
160 				const size_t l = x + pattern.width <= target.width ? pattern.width : target.width - pattern.width;
161 				const size_t lineNum = (y % pattern.height);
162 				xorBlitter(pattern.getPtr + pattern.width * lineNum, dest + output.width * y, l);
163 			}
164 		}
165 	}
166 	/**
167 	 * XOR blits a color index over a specified area.
168 	 * Parameters:
169 	 *  target = The area to be XOR blitted.
170 	 *  color = The color index to be used.
171 	 */
172 	public void xorBitBLT(Coordinate target, ubyte color) pure {
173 		import CPUblit.composing.specblt;
174 		ubyte* dest = output.getPtr + target.left + (target.top * output.width);
175 		for (int y ; y < target.height ; y++) {
176 			xorBlitter(dest + output.width * y, target.width, color);
177 		}
178 	}
179 	
180 	///Inserts a color letter.
181 	public void insertColorLetter(int x, int y, Bitmap8Bit bitmap, ubyte color) pure {
182 		ubyte* psrc = bitmap.getPtr, pdest = output.getPtr;
183 		pdest += x + output.width * y;
184 		int length = bitmap.width;
185 		for(int iy ; iy < bitmap.height ; iy++){
186 			specblt.textBlitter(psrc,pdest,length,color);
187 			psrc += length;
188 			pdest += output.width;
189 		}
190 	}
191 	///Inserts a midsection of the bitmap defined by slice (DEPRECATED)
192 	public void insertBitmapSlice(int x, int y, Bitmap8Bit bitmap, Coordinate slice) pure {
193 		ubyte* psrc = bitmap.getPtr, pdest = output.getPtr;
194 		pdest += x + output.width * y;
195 		int bmpWidth = bitmap.width;
196 		psrc += slice.left + bmpWidth * slice.top;
197 		int length = slice.width;
198 		for(int iy ; iy < slice.height ; iy++){
199 			compose.blitter(psrc,pdest,length);
200 			psrc += bmpWidth;
201 			pdest += output.width;
202 		}
203 	}
204 	///Inserts a midsection of the bitmap defined by slice as a color letter (DEPRECATED)
205 	public void insertColorLetter(int x, int y, Bitmap8Bit bitmap, ubyte color, Coordinate slice) pure {
206 		if(slice.width - 1 <= 0) return;
207 		if(slice.height - 1 <= 0) return;
208 		ubyte* psrc = bitmap.getPtr, pdest = output.getPtr;
209 		pdest += x + output.width * y;
210 		const int bmpWidth = bitmap.width;
211 		psrc += slice.left + bmpWidth * slice.top;
212 		//int length = slice.width;
213 		for(int iy ; iy < slice.height ; iy++){
214 			specblt.textBlitter(psrc,pdest,slice.width,color);
215 			psrc += bmpWidth;
216 			pdest += output.width;
217 		}
218 	}
219 	/+
220 	///Draws a rectangle. DEPRECATED!
221 	deprecated public void drawRectangle(int xa, int xb, int ya, int yb, ubyte color) pure {
222 		drawLine(xa, xa, ya, yb, color);
223 		drawLine(xb, xb, ya, yb, color);
224 		drawLine(xa, xb, ya, ya, color);
225 		drawLine(xa, xb, yb, yb, color);
226 	}+/
227 	/+
228 	deprecated public void drawRectangle(int xa, int xb, int ya, int yb, Bitmap8Bit brush) pure {
229 		xa = xa + brush.width;
230 		ya = ya + brush.height;
231 		xb = xb - brush.width;
232 		yb = yb - brush.height;
233 		drawLine(xa, xa, ya, yb, brush);
234 		drawLine(xb, xb, ya, yb, brush);
235 		drawLine(xa, xb, ya, ya, brush);
236 		drawLine(xa, xb, yb, yb, brush);
237 	}
238 	///Draws a filled rectangle. DEPRECATED!
239 	deprecated public void drawFilledRectangle(int xa, int xb, int ya, int yb, ubyte color) pure {
240 		draw.drawFilledRectangle(xa, ya, xb, yb, color, output.getPtr(), output.width);
241 	}+/
242 	
243 	///Draws colored text from monocromatic font.
244 	public void drawColorText(int x, int y, dstring text, Fontset!(Bitmap8Bit) fontset, ubyte color, uint style = 0) pure {
245 		//color = 1;
246 		const int length = fontset.getTextLength(text);
247 		if(style & FontFormat.HorizCentered)
248 			x = x - (length / 2);
249 		if(style & FontFormat.VertCentered)
250 			y -= fontset.size / 2;
251 		if(style & FontFormat.RightJustified)
252 			x -= length;
253 		//int fontheight = fontset.getSize();
254 		foreach(dchar c ; text){
255 			const Font.Char chinfo = fontset.chars(c);
256 			const Coordinate letterSlice = Coordinate(chinfo.x, chinfo.y, chinfo.x + chinfo.width, chinfo.y + chinfo.height);
257 			insertColorLetter(x + chinfo.xoffset, y + chinfo.yoffset, fontset.pages[chinfo.page], color, letterSlice);
258 			x += chinfo.xadvance;
259 		}
260 	}
261 	/**
262 	 * Draws fully formatted text within a given prelimiter specified by pos.
263 	 * Offset specifies how much of the text is being obscured from the left hand side.
264 	 * lineOffset specifies how much lines in pixels are skipped on the top.
265 	 * Return value contains state flags on wheter certain portions of the text were out of bound.
266 	 */
267 	public uint drawMultiLineText(Coordinate pos, Text text, int offset = 0, int lineOffset = 0) pure {
268 		int lineCount = lineOffset;
269 		const int maxLines = pos.height + lineOffset;
270 			if(lineCount >= lineOffset) {	//draw if linecount is greater or equal than offset
271 				//special
272 			}
273 		do {
274 
275 		} while(lineCount < maxLines);
276 		return 0;
277 	}
278 	/**
279 	 * Draws a single line fully formatted text within a given prelimiter specified by pos.
280 	 * Offset specifies how much of the text is being obscured from the left hand side.
281 	 * lineOffset specifies how much lines in pixels are skipped on the top.
282 	 * Return value contains state flags on wheter certain portions of the text were out of bound.
283 	 */
284 	public uint drawSingleLineText(Box pos, Text text, int offset = 0, int lineOffset = 0) {
285 		uint status;						//contains status flags
286 		bool needsIcon; 					//set to true if icon is inserted or doesn't exist.
287 		dchar prevChar;						//previous character, used for kerning
288 
289 		const int textWidth = text.getWidth();	//Total with of the text
290 		if (textWidth < offset) return TextDrawStatus.TooMuchOffset;
291 		int pX = text.frontTab;							//The current position, where the first letter will be drawn
292 		
293 		//Currently it was chosen to use a workpad to make things simpler
294 		//TODO: modify the algorithm to work without a workpad
295 		Bitmap8Bit workPad = new Bitmap8Bit(textWidth + 1, text.font.size * 2);
296 		///Inserts a color letter.
297 		void _insertColorLetter(Point pos, Bitmap8Bit bitmap, ubyte color, Coordinate slice) pure nothrow {
298 			if(slice.width - 1 <= 0) return;
299 			if(slice.height - 1 <= 0) return;
300 			ubyte* psrc = bitmap.getPtr, pdest = workPad.getPtr;
301 			pdest += pos.x + workPad.width * pos.y;
302 			const int bmpWidth = bitmap.width;
303 			psrc += slice.left + bmpWidth * slice.top;
304 			//int length = slice.width;
305 			for(int iy ; iy < slice.height ; iy++){
306 				specblt.textBlitter(psrc,pdest,slice.width,color);
307 				psrc += bmpWidth;
308 				pdest += workPad.width;
309 			}
310 		}
311 		///Copies a bitmap to the canvas using 0th index transparency.
312 		void _bitBLT(Point target, Bitmap8Bit source) pure nothrow {
313 			ubyte* src = source.getPtr;
314 			ubyte* dest = workPad.getPtr + (workPad.width * target.y) + target.x;
315 			for (int y ; y < source.height ; y++){
316 				compose.blitter(src, dest, source.width);
317 				src += source.width;
318 				dest += workPad.width;
319 			}
320 		}
321 		//const int targetX = textWidth - offset > pos.width ? pos.right : pos.left + textWidth;
322 		Text currTextChunk = text;
323 		int currCharPos;// = text.offsetAmount(offset);
324 		if (currCharPos == 0 && currTextChunk.icon && offset < currTextChunk.icon.width + currTextChunk.iconOffsetX)
325 			needsIcon = true;
326 		//pX += currTextChunk.frontTab - offset > 0 ? currTextChunk.frontTab - offset : 0;
327 		/+int firstCharOffset = offset;// - text.getWidth(0, currCharPos);
328 
329 		if (currCharPos > 0)
330 			firstCharOffset -= text.getWidth(0, currCharPos);+/
331 		
332 		while (pX < textWidth) {	//Per character/symbol drawing
333 			if(needsIcon) {
334 				//if there's enough space for the icon, then draw it
335 				pX += currTextChunk.iconOffsetX >= 0 ? currTextChunk.iconOffsetX : 0;
336 				//if(pX + currTextChunk.icon.width < targetX) {
337 				//const int targetHeight = pos.height > currTextChunk.icon.height - lineOffset ? currTextChunk.icon.height : pos.height;
338 				_bitBLT(Point(pX, currTextChunk.iconOffsetY), currTextChunk.icon);
339 				pX += text.iconSpacing;
340 				needsIcon = false;
341 				//} else return status | TextDrawStatus.RHSOutOfBound;
342 			} else {
343 				//check if there's any characters left in the current text chunk, if not step onto the next one if any, if not then return
344 				if(currCharPos >= currTextChunk.text.length) {
345 					if(currTextChunk.next) currTextChunk = currTextChunk.next;
346 					else return status;
347 					if(currTextChunk.icon) needsIcon = true;
348 					else needsIcon = false;
349 					currCharPos = 0;
350 					pX += currTextChunk.frontTab;
351 				} else {
352 					//if there's enough space for the next character, then draw it
353 					const dchar chr = text.text[currCharPos];
354 					Font.Char chrInfo = text.font.chars(chr);
355 					//check if the character exists in the fontset, if not, then substitute it and set flag for missing character
356 					if(chrInfo.id == 0xFFFD && chr != 0xFFFD) status |= TextDrawStatus.CharacterNotFound;
357 					//const int chrAdvAmount = chrInfo.xadvance + currTextChunk.formatting.getKerning(prevChar, chr);
358 					/+if(pX < targetX) {
359 						//if(pX + chrInfo.xadvance >= offset) {
360 						Box letterSlice = Box(chrInfo.x, chrInfo.y, chrInfo.x + chrInfo.width, chrInfo.y + 
361 							chrInfo.height);
362 						const int xOffset = pX < offset ? offset - pX : 0;
363 						letterSlice.left += xOffset > chrInfo.xoffset ? xOffset : 0; //Draw portion of letter if it's partly obscured at the left
364 						letterSlice.top += lineOffset > chrInfo.yoffset ? chrInfo.yoffset - lineOffset : 0;
365 						if(pX + chrInfo.xoffset + chrAdvAmount >= targetX){//Draw portion of letter if it's partly obscured at the right
366 							letterSlice.right -= targetX - (pX + chrInfo.xoffset + chrInfo.width);
367 							status |= TextDrawStatus.RHSOutOfBound;
368 						}
369 						/+letterSlice.right -= pX + chrInfo.xoffset + chrInfo.width >= targetX ? 
370 								(pX - chrInfo.xoffset) - (targetX - chrInfo.xoffset) : 0;+/
371 						if (letterSlice.width > 0 && letterSlice.height > 0)
372 							insertColorLetter(pX + chrInfo.xoffset - xOffset, pos.top + chrInfo.yoffset - lineOffset, 
373 									text.font.pages[chrInfo.page], text.formatting.color, letterSlice);
374 						//}
375 						pX += chrAdvAmount;
376 						firstCharOffset = 0;
377 						currCharPos++;
378 						prevChar = chr;
379 					} else return status | TextDrawStatus.RHSOutOfBound;+/
380 					Box letterSrc = Box(chrInfo.x, chrInfo.y, chrInfo.x + chrInfo.width, chrInfo.y + 
381 							chrInfo.height);
382 					Point chrPos = Point (pX + chrInfo.xoffset, chrInfo.yoffset);
383 					_insertColorLetter(chrPos, currTextChunk.font.pages[chrInfo.page], currTextChunk.formatting.color, letterSrc);
384 					pX += chrInfo.xadvance + currTextChunk.formatting.getKerning(prevChar, chr);
385 					currCharPos++;
386 				}
387 			}
388 		}
389 		Point renderTarget = Point(pos.left,pos.top);
390 		//Offset text vertically in needed
391 		if (text.formatting.formatFlags & FormattingFlags.slHorizCenter) {
392 			renderTarget.y += (pos.height / 2) - (text.font.size);
393 		}
394 		//Offset text to the right if needed
395 		if (pos.width > textWidth) {
396 			switch (text.formatting.formatFlags & FormattingFlags.justifyMask) {
397 				case FormattingFlags.centerJustify:
398 					renderTarget.x += (pos.width - textWidth) / 2;
399 					break;
400 				case FormattingFlags.rightJustify:
401 					renderTarget.x += pos.width - textWidth;
402 					break;
403 				default: break;
404 			}
405 		}
406 		Box textSlice = Box(offset, lineOffset + text.formatting.offsetV, workPad.width - 1, workPad.height - 1);
407 		if (textSlice.width > pos.width) textSlice.width = pos.width;	//clamp down text width
408 		if (textSlice.height > pos.height) textSlice.height = pos.height;	//clamp down text height
409 		if (textSlice.width > 0 && textSlice.height > 0)
410 			bitBLT(renderTarget, workPad, textSlice);
411 		return status;
412 	}
413 	///Draws text to the given point. DEPRECATED!
414 	deprecated public void drawText(int x, int y, dstring text, Fontset!(Bitmap8Bit) fontset, uint style = 0) pure {
415 		const int length = fontset.getTextLength(text);
416 		//writeln(text);
417 		/+if(style == 0){
418 			x = x - (length / 2);
419 			y -= fontset.getSize() / 2;
420 		}else if(style == 2){
421 			y -= fontset.getSize();
422 		}+/
423 		if(style & FontFormat.HorizCentered)
424 			x = x - (length / 2);
425 		if(style & FontFormat.VertCentered)
426 			y -= fontset.size / 2;
427 		foreach(dchar c ; text){
428 			const Font.Char chinfo = fontset.chars(c);
429 			const Coordinate letterSlice = Coordinate(chinfo.x, chinfo.y, chinfo.x + chinfo.width, chinfo.y + chinfo.height);
430 			insertBitmapSlice(x + chinfo.xoffset, y + chinfo.yoffset, fontset.pages[chinfo.page], letterSlice);
431 			x += chinfo.xadvance;
432 		}
433 	}
434 	///Inserts a bitmap using blitter. DEPRECATED
435 	deprecated public void insertBitmap(int x, int y, Bitmap8Bit bitmap) pure {
436 		ubyte* psrc = bitmap.getPtr, pdest = output.getPtr;
437 		pdest += x + output.width * y;
438 		int length = bitmap.width;
439 		for(int iy ; iy < bitmap.height ; iy++){
440 			compose.blitter(psrc,pdest,length);
441 			psrc += length;
442 			pdest += output.width;
443 		}
444 	}
445 }
446 /**
447  * Font formatting flags.
448  */
449 enum FontFormat : uint {
450 	HorizCentered			=	0x1,
451 	VertCentered			=	0x2,
452 	RightJustified			=	0x10,
453 	SingleLine				=	0x100,	///Forces text as single line
454 	
455 }
456 /**
457  * Text drawing return flags.
458  */
459 enum TextDrawStatus : uint {
460 	CharacterNotFound		=	0x01_00,	///Set if there's any character that was not found in the character set
461 	LHSOutOfBound			=	0x00_01,
462 	RHSOutOfBound			=	0x00_02,
463 	TPOutOfBound			=	0x00_04,
464 	BPOutOfBound			=	0x00_08,
465 	TooMuchOffset			=	0x1_00_00,
466 }