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