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