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 }