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 }