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