1 // Written in the D programming language. 2 3 /** 4 This module contains drawing buffer implementation. 5 6 7 Synopsis: 8 9 ---- 10 import dlangui.graphics.drawbuf; 11 12 ---- 13 14 Copyright: Vadim Lopatin, 2014 15 License: Boost License 1.0 16 Authors: Vadim Lopatin, coolreader.org@gmail.com 17 */ 18 module dlangui.graphics.drawbuf; 19 20 public import dlangui.core.config; 21 public import dlangui.core.types; 22 public import dlangui.core.math3d; 23 import dlangui.core.logger; 24 import dlangui.graphics.colors; 25 26 /** 27 * 9-patch image scaling information (see Android documentation). 28 * 29 * 30 */ 31 struct NinePatch { 32 /// frame (non-scalable) part size for left, top, right, bottom edges. 33 Rect frame; 34 /// padding (distance to content area) for left, top, right, bottom edges. 35 Rect padding; 36 } 37 38 enum PatternType : int { 39 solid, 40 dotted, 41 } 42 43 static if (ENABLE_OPENGL) { 44 /// non thread safe 45 private __gshared uint drawBufIdGenerator = 0; 46 } 47 48 /// Custom draw delegate for OpenGL direct drawing 49 alias OpenGLDrawableDelegate = void delegate(Rect windowRect, Rect rc); 50 51 /// drawing buffer - image container which allows to perform some drawing operations 52 class DrawBuf : RefCountedObject { 53 protected Rect _clipRect; 54 protected NinePatch * _ninePatch; 55 protected uint _alpha; 56 57 /// get current alpha setting (to be applied to all drawing operations) 58 @property uint alpha() { return _alpha; } 59 /// set new alpha setting (to be applied to all drawing operations) 60 @property void alpha(uint alpha) { 61 _alpha = alpha; 62 if (_alpha > 0xFF) 63 _alpha = 0xFF; 64 } 65 66 /// apply additional transparency to current drawbuf alpha value 67 void addAlpha(uint alpha) { 68 _alpha = blendAlpha(_alpha, alpha); 69 } 70 71 /// applies current drawbuf alpha to argb color value 72 uint applyAlpha(uint argb) { 73 if (!_alpha) 74 return argb; // no drawbuf alpha 75 uint a1 = (argb >> 24) & 0xFF; 76 if (a1 == 0xFF) 77 return argb; // fully transparent 78 uint a2 = _alpha & 0xFF; 79 uint a = blendAlpha(a1, a2); 80 return (argb & 0xFFFFFF) | (a << 24); 81 } 82 83 static if (ENABLE_OPENGL) { 84 protected uint _id; 85 /// unique ID of drawbug instance, for using with hardware accelerated rendering for caching 86 @property uint id() { return _id; } 87 } 88 89 this() { 90 static if (ENABLE_OPENGL) { 91 _id = drawBufIdGenerator++; 92 } 93 debug _instanceCount++; 94 } 95 96 debug private static __gshared int _instanceCount; 97 debug @property static int instanceCount() { return _instanceCount; } 98 ~this() { 99 /*import core.memory : gc_inFinalizer;*/ 100 if (APP_IS_SHUTTING_DOWN/* || gc_inFinalizer*/) 101 onResourceDestroyWhileShutdown("DrawBuf", this.classinfo.name); 102 103 debug _instanceCount--; 104 clear(); 105 } 106 107 protected void function(uint) _onDestroyCallback; 108 @property void onDestroyCallback(void function(uint) callback) { _onDestroyCallback = callback; } 109 @property void function(uint) onDestroyCallback() { return _onDestroyCallback; } 110 111 /// Call to remove this image from OpenGL cache when image is updated. 112 void invalidate() { 113 static if (ENABLE_OPENGL) { 114 if (_onDestroyCallback) { 115 // remove from cache 116 _onDestroyCallback(_id); 117 // assign new ID 118 _id = drawBufIdGenerator++; 119 } 120 } 121 } 122 123 // =================================================== 124 // 9-patch functions (image scaling using 9-patch markup - unscaled frame and scaled middle parts). 125 // See Android documentation for details. 126 127 /// get nine patch information pointer, null if this is not a nine patch image buffer 128 @property const (NinePatch) * ninePatch() const { return _ninePatch; } 129 /// set nine patch information pointer, null if this is not a nine patch image buffer 130 @property void ninePatch(NinePatch * ninePatch) { _ninePatch = ninePatch; } 131 /// check whether there is nine-patch information available for drawing buffer 132 @property bool hasNinePatch() { return _ninePatch !is null; } 133 /// override to detect nine patch using image 1-pixel border; returns true if 9-patch markup is found in image. 134 bool detectNinePatch() { return false; } 135 136 /// returns current width 137 @property int width() { return 0; } 138 /// returns current height 139 @property int height() { return 0; } 140 141 // =================================================== 142 // clipping rectangle functions 143 144 /// init clip rectangle to full buffer size 145 void resetClipping() { 146 _clipRect = Rect(0, 0, width, height); 147 } 148 @property bool hasClipping() { 149 return _clipRect.left != 0 || _clipRect.top != 0 || _clipRect.right != width || _clipRect.bottom != height; 150 } 151 /// returns clipping rectangle, when clipRect.isEmpty == true -- means no clipping. 152 @property ref Rect clipRect() { return _clipRect; } 153 /// returns clipping rectangle, or (0,0,dx,dy) when no clipping. 154 //@property Rect clipOrFullRect() { return _clipRect.empty ? Rect(0,0,width,height) : _clipRect; } 155 /// sets new clipping rectangle, when clipRect.isEmpty == true -- means no clipping. 156 @property void clipRect(const ref Rect rect) { 157 _clipRect = rect; 158 _clipRect.intersect(Rect(0, 0, width, height)); 159 } 160 /// sets new clipping rectangle, intersect with previous one. 161 @property void intersectClipRect(const ref Rect rect) { 162 //if (_clipRect.empty) 163 // _clipRect = rect; 164 //else 165 _clipRect.intersect(rect); 166 _clipRect.intersect(Rect(0, 0, width, height)); 167 } 168 /// returns true if rectangle is completely clipped out and cannot be drawn. 169 @property bool isClippedOut(const ref Rect rect) { 170 //Rect rc = clipOrFullRect(); 171 return !_clipRect.intersects(rect); 172 } 173 /// apply clipRect and buffer bounds clipping to rectangle 174 bool applyClipping(ref Rect rc) { 175 //if (!_clipRect.empty()) 176 rc.intersect(_clipRect); 177 if (rc.left < 0) 178 rc.left = 0; 179 if (rc.top < 0) 180 rc.top = 0; 181 if (rc.right > width) 182 rc.right = width; 183 if (rc.bottom > height) 184 rc.bottom = height; 185 return !rc.empty(); 186 } 187 /// apply clipRect and buffer bounds clipping to rectangle; if clippinup applied to first rectangle, reduce second rectangle bounds proportionally. 188 bool applyClipping(ref Rect rc, ref Rect rc2) { 189 if (rc.empty || rc2.empty) 190 return false; 191 //if (!_clipRect.empty()) 192 if (!rc.intersects(_clipRect)) 193 return false; 194 if (rc.width == rc2.width && rc.height == rc2.height) { 195 // unscaled 196 //if (!_clipRect.empty) { 197 if (rc.left < _clipRect.left) { 198 rc2.left += _clipRect.left - rc.left; 199 rc.left = _clipRect.left; 200 } 201 if (rc.top < _clipRect.top) { 202 rc2.top += _clipRect.top - rc.top; 203 rc.top = _clipRect.top; 204 } 205 if (rc.right > _clipRect.right) { 206 rc2.right -= rc.right - _clipRect.right; 207 rc.right = _clipRect.right; 208 } 209 if (rc.bottom > _clipRect.bottom) { 210 rc2.bottom -= rc.bottom - _clipRect.bottom; 211 rc.bottom = _clipRect.bottom; 212 } 213 //} 214 if (rc.left < 0) { 215 rc2.left += -rc.left; 216 rc.left = 0; 217 } 218 if (rc.top < 0) { 219 rc2.top += -rc.top; 220 rc.top = 0; 221 } 222 if (rc.right > width) { 223 rc2.right -= rc.right - width; 224 rc.right = width; 225 } 226 if (rc.bottom > height) { 227 rc2.bottom -= rc.bottom - height; 228 rc.bottom = height; 229 } 230 } else { 231 // scaled 232 int dstdx = rc.width; 233 int dstdy = rc.height; 234 int srcdx = rc2.width; 235 int srcdy = rc2.height; 236 //if (!_clipRect.empty) { 237 if (rc.left < _clipRect.left) { 238 rc2.left += (_clipRect.left - rc.left) * srcdx / dstdx; 239 rc.left = _clipRect.left; 240 } 241 if (rc.top < _clipRect.top) { 242 rc2.top += (_clipRect.top - rc.top) * srcdy / dstdy; 243 rc.top = _clipRect.top; 244 } 245 if (rc.right > _clipRect.right) { 246 rc2.right -= (rc.right - _clipRect.right) * srcdx / dstdx; 247 rc.right = _clipRect.right; 248 } 249 if (rc.bottom > _clipRect.bottom) { 250 rc2.bottom -= (rc.bottom - _clipRect.bottom) * srcdy / dstdy; 251 rc.bottom = _clipRect.bottom; 252 } 253 //} 254 if (rc.left < 0) { 255 rc2.left -= (rc.left) * srcdx / dstdx; 256 rc.left = 0; 257 } 258 if (rc.top < 0) { 259 rc2.top -= (rc.top) * srcdy / dstdy; 260 rc.top = 0; 261 } 262 if (rc.right > width) { 263 rc2.right -= (rc.right - width) * srcdx / dstdx; 264 rc.right = width; 265 } 266 if (rc.bottom > height) { 267 rc2.bottom -= (rc.bottom - height) * srcdx / dstdx; 268 rc.bottom = height; 269 } 270 } 271 return !rc.empty() && !rc2.empty(); 272 } 273 /// reserved for hardware-accelerated drawing - begins drawing batch 274 void beforeDrawing() { _alpha = 0; } 275 /// reserved for hardware-accelerated drawing - ends drawing batch 276 void afterDrawing() { } 277 /// returns buffer bits per pixel 278 @property int bpp() { return 0; } 279 // returns pointer to ARGB scanline, null if y is out of range or buffer doesn't provide access to its memory 280 //uint * scanLine(int y) { return null; } 281 /// resize buffer 282 abstract void resize(int width, int height); 283 284 //======================================================== 285 // Drawing methods. 286 287 /// fill the whole buffer with solid color (no clipping applied) 288 abstract void fill(uint color); 289 /// fill rectangle with solid color (clipping is applied) 290 abstract void fillRect(Rect rc, uint color); 291 /// fill rectangle with solid color and pattern (clipping is applied) 0=solid fill, 1 = dotted 292 void fillRectPattern(Rect rc, uint color, int pattern) { 293 // default implementation: does not support patterns 294 fillRect(rc, color); 295 } 296 /// draw pixel at (x, y) with specified color 297 abstract void drawPixel(int x, int y, uint color); 298 /// draw 8bit alpha image - usually font glyph using specified color (clipping is applied) 299 abstract void drawGlyph(int x, int y, Glyph * glyph, uint color); 300 /// draw source buffer rectangle contents to destination buffer 301 abstract void drawFragment(int x, int y, DrawBuf src, Rect srcrect); 302 /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling 303 abstract void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect); 304 /// draw unscaled image at specified coordinates 305 void drawImage(int x, int y, DrawBuf src) { 306 drawFragment(x, y, src, Rect(0, 0, src.width, src.height)); 307 } 308 /// draws rectangle frame of specified color and widths (per side), and optinally fills inner area 309 void drawFrame(Rect rc, uint frameColor, Rect frameSideWidths, uint innerAreaColor = 0xFFFFFFFF) { 310 // draw frame 311 if (!isFullyTransparentColor(frameColor)) { 312 Rect r; 313 // left side 314 r = rc; 315 r.right = r.left + frameSideWidths.left; 316 if (!r.empty) 317 fillRect(r, frameColor); 318 // right side 319 r = rc; 320 r.left = r.right - frameSideWidths.right; 321 if (!r.empty) 322 fillRect(r, frameColor); 323 // top side 324 r = rc; 325 r.left += frameSideWidths.left; 326 r.right -= frameSideWidths.right; 327 Rect rc2 = r; 328 rc2.bottom = r.top + frameSideWidths.top; 329 if (!rc2.empty) 330 fillRect(rc2, frameColor); 331 // bottom side 332 rc2 = r; 333 rc2.top = r.bottom - frameSideWidths.bottom; 334 if (!rc2.empty) 335 fillRect(rc2, frameColor); 336 } 337 // draw internal area 338 if (!isFullyTransparentColor(innerAreaColor)) { 339 rc.left += frameSideWidths.left; 340 rc.top += frameSideWidths.top; 341 rc.right -= frameSideWidths.right; 342 rc.bottom -= frameSideWidths.bottom; 343 if (!rc.empty) 344 fillRect(rc, innerAreaColor); 345 } 346 } 347 348 /// draw focus rectangle; vertical gradient supported - colors[0] is top color, colors[1] is bottom color 349 void drawFocusRect(Rect rc, const uint[] colors) { 350 // override for faster performance when using OpenGL 351 if (colors.length < 1) 352 return; 353 uint color1 = colors[0]; 354 uint color2 = colors.length > 1 ? colors[1] : color1; 355 if (isFullyTransparentColor(color1) && isFullyTransparentColor(color2)) 356 return; 357 // draw horizontal lines 358 foreach(int x; rc.left .. rc.right) { 359 if ((x ^ rc.top) & 1) 360 fillRect(Rect(x, rc.top, x + 1, rc.top + 1), color1); 361 if ((x ^ (rc.bottom - 1)) & 1) 362 fillRect(Rect(x, rc.bottom - 1, x + 1, rc.bottom), color2); 363 } 364 // draw vertical lines 365 foreach(int y; rc.top + 1 .. rc.bottom - 1) { 366 uint color = color1 == color2 ? color1 : blendARGB(color2, color1, 255 / (rc.bottom - rc.top)); 367 if ((y ^ rc.left) & 1) 368 fillRect(Rect(rc.left, y, rc.left + 1, y + 1), color); 369 if ((y ^ (rc.right - 1)) & 1) 370 fillRect(Rect(rc.right - 1, y, rc.right, y + 1), color); 371 } 372 } 373 374 /// draw filled triangle in float coordinates; clipping is already applied 375 protected void fillTriangleFClipped(PointF p1, PointF p2, PointF p3, uint colour) { 376 // override and implement it 377 } 378 379 /// find intersection of line p1..p2 with clip rectangle 380 protected bool intersectClipF(ref PointF p1, ref PointF p2, ref bool p1moved, ref bool p2moved) { 381 if (p1.x < _clipRect.left && p2.x < _clipRect.left) 382 return true; 383 if (p1.x >= _clipRect.right && p2.x >= _clipRect.right) 384 return true; 385 if (p1.y < _clipRect.top && p2.y < _clipRect.top) 386 return true; 387 if (p1.y >= _clipRect.bottom && p2.y >= _clipRect.bottom) 388 return true; 389 // horizontal clip 390 if (p1.x < _clipRect.left && p2.x >= _clipRect.left) { 391 // move p1 to clip left 392 p1 += (p2 - p1) * ((_clipRect.left - p1.x) / (p2.x - p1.x)); 393 p1moved = true; 394 } 395 if (p2.x < _clipRect.left && p1.x >= _clipRect.left) { 396 // move p2 to clip left 397 p2 += (p1 - p2) * ((_clipRect.left - p2.x) / (p1.x - p2.x)); 398 p2moved = true; 399 } 400 if (p1.x > _clipRect.right && p2.x < _clipRect.right) { 401 // move p1 to clip right 402 p1 += (p2 - p1) * ((_clipRect.right - p1.x) / (p2.x - p1.x)); 403 p1moved = true; 404 } 405 if (p2.x > _clipRect.right && p1.x < _clipRect.right) { 406 // move p1 to clip right 407 p2 += (p1 - p2) * ((_clipRect.right - p2.x) / (p1.x - p2.x)); 408 p2moved = true; 409 } 410 // vertical clip 411 if (p1.y < _clipRect.top && p2.y >= _clipRect.top) { 412 // move p1 to clip left 413 p1 += (p2 - p1) * ((_clipRect.top - p1.y) / (p2.y - p1.y)); 414 p1moved = true; 415 } 416 if (p2.y < _clipRect.top && p1.y >= _clipRect.top) { 417 // move p2 to clip left 418 p2 += (p1 - p2) * ((_clipRect.top - p2.y) / (p1.y - p2.y)); 419 p2moved = true; 420 } 421 if (p1.y > _clipRect.bottom && p2.y < _clipRect.bottom) { 422 // move p1 to clip right <0 <0 423 p1 += (p2 - p1) * ((_clipRect.bottom - p1.y) / (p2.y - p1.y)); 424 p1moved = true; 425 } 426 if (p2.y > _clipRect.bottom && p1.y < _clipRect.bottom) { 427 // move p1 to clip right 428 p2 += (p1 - p2) * ((_clipRect.bottom - p2.y) / (p1.y - p2.y)); 429 p2moved = true; 430 } 431 return false; 432 } 433 434 /// draw filled triangle in float coordinates 435 void fillTriangleF(PointF p1, PointF p2, PointF p3, uint colour) { 436 if (_clipRect.empty) // clip rectangle is empty - all drawables are clipped out 437 return; 438 // apply clipping 439 bool p1insideClip = (p1.x >= _clipRect.left && p1.x < _clipRect.right && p1.y >= _clipRect.top && p1.y < _clipRect.bottom); 440 bool p2insideClip = (p2.x >= _clipRect.left && p2.x < _clipRect.right && p2.y >= _clipRect.top && p2.y < _clipRect.bottom); 441 bool p3insideClip = (p3.x >= _clipRect.left && p3.x < _clipRect.right && p3.y >= _clipRect.top && p3.y < _clipRect.bottom); 442 if (p1insideClip && p2insideClip && p3insideClip) { 443 // all points inside clipping area - no clipping required 444 fillTriangleFClipped(p1, p2, p3, colour); 445 return; 446 } 447 // do triangle clipping 448 // check if all points outside the same bound 449 if ((p1.x < _clipRect.left && p2.x < _clipRect.left && p3.x < _clipRect.left) 450 || (p1.x >= _clipRect.right && p2.x >= _clipRect.right && p3.x >= _clipRect.bottom) 451 || (p1.y < _clipRect.top && p2.y < _clipRect.top && p3.y < _clipRect.top) 452 || (p1.y >= _clipRect.bottom && p2.y >= _clipRect.bottom && p3.y >= _clipRect.bottom)) 453 return; 454 /++ 455 + side 1 456 + p1-------p11------------p21--------------p2 457 + \ / 458 + \ / 459 + \ / 460 + \ / 461 + p13\ /p22 462 + \ / 463 + \ / 464 + \ / 465 + \ / side 2 466 + side 3 \ / 467 + \ / 468 + \ / 469 + \ /p32 470 + p33\ / 471 + \ / 472 + \ / 473 + \ / 474 + \ / 475 + \ / 476 + \ / 477 + p3 478 +/ 479 PointF p11 = p1; 480 PointF p13 = p1; 481 PointF p21 = p2; 482 PointF p22 = p2; 483 PointF p32 = p3; 484 PointF p33 = p3; 485 bool p1moved = false; 486 bool p2moved = false; 487 bool p3moved = false; 488 bool side1clipped = intersectClipF(p11, p21, p1moved, p2moved); 489 bool side2clipped = intersectClipF(p22, p32, p2moved, p3moved); 490 bool side3clipped = intersectClipF(p33, p13, p3moved, p1moved); 491 if (!p1moved && !p2moved && !p3moved) { 492 // no moved - no clipping 493 fillTriangleFClipped(p1, p2, p3, colour); 494 } else if (p1moved && !p2moved && !p3moved) { 495 fillTriangleFClipped(p11, p2, p3, colour); 496 fillTriangleFClipped(p3, p13, p11, colour); 497 } else if (!p1moved && p2moved && !p3moved) { 498 fillTriangleFClipped(p22, p3, p1, colour); 499 fillTriangleFClipped(p1, p21, p22, colour); 500 } else if (!p1moved && !p2moved && p3moved) { 501 fillTriangleFClipped(p33, p1, p2, colour); 502 fillTriangleFClipped(p2, p32, p33, colour); 503 } else if (p1moved && p2moved && !p3moved) { 504 if (!side1clipped) { 505 fillTriangleFClipped(p13, p11, p21, colour); 506 fillTriangleFClipped(p21, p22, p13, colour); 507 } 508 fillTriangleFClipped(p22, p3, p13, colour); 509 } else if (!p1moved && p2moved && p3moved) { 510 if (!side2clipped) { 511 fillTriangleFClipped(p21, p22, p32, colour); 512 fillTriangleFClipped(p32, p33, p21, colour); 513 } 514 fillTriangleFClipped(p21, p33, p1, colour); 515 } else if (p1moved && !p2moved && p3moved) { 516 if (!side3clipped) { 517 fillTriangleFClipped(p13, p11, p32, colour); 518 fillTriangleFClipped(p32, p33, p13, colour); 519 } 520 fillTriangleFClipped(p11, p2, p32, colour); 521 } else if (p1moved && p2moved && p3moved) { 522 if (side1clipped) { 523 fillTriangleFClipped(p13, p22, p32, colour); 524 fillTriangleFClipped(p32, p33, p13, colour); 525 } else if (side2clipped) { 526 fillTriangleFClipped(p11, p21, p33, colour); 527 fillTriangleFClipped(p33, p13, p11, colour); 528 } else if (side3clipped) { 529 fillTriangleFClipped(p11, p21, p22, colour); 530 fillTriangleFClipped(p22, p32, p11, colour); 531 } else { 532 fillTriangleFClipped(p13, p11, p21, colour); 533 fillTriangleFClipped(p21, p22, p13, colour); 534 fillTriangleFClipped(p22, p32, p33, colour); 535 fillTriangleFClipped(p33, p13, p22, colour); 536 } 537 } 538 } 539 540 /// draw filled quad in float coordinates 541 void fillQuadF(PointF p1, PointF p2, PointF p3, PointF p4, uint colour) { 542 fillTriangleF(p1, p2, p3, colour); 543 fillTriangleF(p3, p4, p1, colour); 544 } 545 546 /// draw line of arbitrary width in float coordinates 547 void drawLineF(PointF p1, PointF p2, float width, uint colour) { 548 // direction vector 549 PointF v = (p2 - p1).normalized; 550 // calculate normal vector 551 // calculate normal vector : rotate CCW 90 degrees 552 PointF n = v.rotated90ccw(); 553 // rotate CCW 90 degrees 554 n.y = v.x; 555 n.x = -v.y; 556 // offset by normal * half_width 557 n *= width / 2; 558 // draw line using quad 559 fillQuadF(p1 - n, p2 - n, p2 + n, p1 + n, colour); 560 } 561 562 // find intersection point for two vectors with start points p1, p2 and normalized directions dir1, dir2 563 protected static PointF intersectVectors(PointF p1, PointF dir1, PointF p2, PointF dir2) { 564 /* 565 L1 = P1 + a * V1 566 L2 = P2 + b * V2 567 P1 + a * V1 = P2 + b * V2 568 a * V1 = (P2 - P1) + b * V2 569 a * (V1 X V2) = (P2 - P1) X V2 570 a = (P2 - P1) * V2 / (V1*V2) 571 return P1 + a * V1 572 */ 573 // just return middle point 574 PointF p2p1 = (p2 - p1); //.normalized; 575 float d1 = p2p1.crossProduct(dir2); 576 float d2 = dir1.crossProduct(dir2); 577 // a * d1 = d2 578 if (d2 >= -0.1f && d2 <= 0.1f) { 579 return p1; //PointF((p1.x + p2.x)/2, (p1.y + p2.y)/2); 580 } 581 float a = d1 / d2; 582 return p1 + dir1 * a; 583 } 584 585 protected void calcLineSegmentQuad(PointF p0, PointF p1, PointF p2, PointF p3, float width, ref PointF[4] quad) { 586 // direction vector 587 PointF v = (p2 - p1).normalized; 588 // calculate normal vector : rotate CCW 90 degrees 589 PointF n = v.rotated90ccw(); 590 // offset by normal * half_width 591 n *= width / 2; 592 // draw line using quad 593 PointF pp10 = p1 - n; 594 PointF pp20 = p2 - n; 595 PointF pp11 = p1 + n; 596 PointF pp21 = p2 + n; 597 if ((p1 - p0).length > 0.1f) { 598 // has prev segment 599 PointF prevv = (p1 - p0).normalized; 600 PointF prevn = prevv.rotated90ccw(); 601 PointF prev10 = p1 - prevn * width / 2; 602 PointF prev11 = p1 + prevn * width / 2; 603 PointF intersect0 = intersectVectors(pp10, -v, prev10, prevv); 604 PointF intersect1 = intersectVectors(pp11, -v, prev11, prevv); 605 pp10 = intersect0; 606 pp11 = intersect1; 607 } 608 if ((p3 - p2).length > 0.1f) { 609 // has next segment 610 PointF nextv = (p3 - p2).normalized; 611 PointF nextn = nextv.rotated90ccw(); 612 PointF next20 = p2 - nextn * width / 2; 613 PointF next21 = p2 + nextn * width / 2; 614 PointF intersect0 = intersectVectors(pp20, v, next20, -nextv); 615 PointF intersect1 = intersectVectors(pp21, v, next21, -nextv); 616 pp20 = intersect0; 617 pp21 = intersect1; 618 } 619 quad[0] = pp10; 620 quad[1] = pp20; 621 quad[2] = pp21; 622 quad[3] = pp11; 623 } 624 /// draw line of arbitrary width in float coordinates p1..p2 with angle based on previous (p0..p1) and next (p2..p3) segments 625 void drawLineSegmentF(PointF p0, PointF p1, PointF p2, PointF p3, float width, uint colour) { 626 PointF[4] quad; 627 calcLineSegmentQuad(p0, p1, p2, p3, width, quad); 628 fillQuadF(quad[0], quad[1], quad[2], quad[3], colour); 629 } 630 631 /// draw poly line of arbitrary width in float coordinates; when cycled is true, connect first and last point (optionally fill inner area) 632 void polyLineF(PointF[] points, float width, uint colour, bool cycled = false, uint innerAreaColour = COLOR_TRANSPARENT) { 633 if (points.length < 2) 634 return; 635 bool hasInnerArea = !isFullyTransparentColor(innerAreaColour); 636 if (isFullyTransparentColor(colour)) { 637 if (hasInnerArea) 638 fillPolyF(points, innerAreaColour); 639 return; 640 } 641 int len = cast(int)points.length; 642 if (hasInnerArea) { 643 PointF[] innerArea; 644 innerArea.assumeSafeAppend; 645 //Log.d("fill poly inner: ", points); 646 for(int i = 0; i < len; i++) { 647 PointF[4] quad; 648 int index0 = i - 1; 649 int index1 = i; 650 int index2 = i + 1; 651 int index3 = i + 2; 652 if (index0 < 0) 653 index0 = cycled ? len - 1 : 0; 654 index2 %= len; // only can be if cycled 655 index3 %= len; // only can be if cycled 656 if (!cycled) { 657 if (index1 == len - 1) { 658 index0 = index1; 659 index2 = 0; 660 index3 = 0; 661 } else if (index1 == len - 2) { 662 index2 = len - 1; 663 index3 = len - 1; 664 } 665 } 666 //Log.d("lineSegment - inner ", index0, ", ", index1, ", ", index2, ", ", index3); 667 calcLineSegmentQuad(points[index0], points[index1], points[index2], points[index3], width, quad); 668 innerArea ~= quad[3]; 669 } 670 fillPolyF(innerArea, innerAreaColour); 671 } 672 if (!isFullyTransparentColor(colour)) { 673 for(int i = 0; i < len; i++) { 674 int index0 = i - 1; 675 int index1 = i; 676 int index2 = i + 1; 677 int index3 = i + 2; 678 if (index0 < 0) 679 index0 = cycled ? len - 1 : 0; 680 index2 %= len; // only can be if cycled 681 index3 %= len; // only can be if cycled 682 if (!cycled) { 683 if (index1 == len - 1) { 684 index0 = index1; 685 index2 = 0; 686 index3 = 0; 687 } else if (index1 == len - 2) { 688 index2 = len - 1; 689 index3 = len - 1; 690 } 691 } 692 //Log.d("lineSegment - outer ", index0, ", ", index1, ", ", index2, ", ", index3); 693 if (cycled || i + 1 < len) 694 drawLineSegmentF(points[index0], points[index1], points[index2], points[index3], width, colour); 695 } 696 } 697 } 698 699 /// draw filled polyline (vertexes must be in clockwise order) 700 void fillPolyF(PointF[] points, uint colour) { 701 if (points.length < 3) { 702 return; 703 } 704 if (points.length == 3) { 705 fillTriangleF(points[0], points[1], points[2], colour); 706 return; 707 } 708 PointF[] list = points.dup; 709 bool moved; 710 while (list.length > 3) { 711 moved = false; 712 for (int i = 0; i < list.length; i++) { 713 PointF p1 = list[i + 0]; 714 PointF p2 = list[(i + 1) % list.length]; 715 PointF p3 = list[(i + 2) % list.length]; 716 float cross = (p2 - p1).crossProduct(p3 - p2); 717 if (cross > 0) { 718 // draw triangle 719 fillTriangleF(p1, p2, p3, colour); 720 int indexToRemove = (i + 1) % (cast(int)list.length); 721 // remove triangle from poly 722 for (int j = indexToRemove; j + 1 < list.length; j++) 723 list[j] = list[j + 1]; 724 list.length = list.length - 1; 725 i += 2; 726 moved = true; 727 } 728 } 729 if (list.length == 3) { 730 fillTriangleF(list[0], list[1], list[2], colour); 731 break; 732 } 733 if (!moved) 734 break; 735 } 736 } 737 738 /// draw ellipse or filled ellipse 739 void drawEllipseF(float centerX, float centerY, float xRadius, float yRadius, float lineWidth, uint lineColor, uint fillColor = COLOR_TRANSPARENT) { 740 import std.math : sin, cos, PI; 741 if (xRadius < 0) 742 xRadius = -xRadius; 743 if (yRadius < 0) 744 yRadius = -yRadius; 745 int numLines = cast(int)((xRadius + yRadius) / 5); 746 if (numLines < 4) 747 numLines = 4; 748 float step = PI * 2 / numLines; 749 float angle = 0; 750 PointF[] points; 751 points.assumeSafeAppend; 752 for (int i = 0; i < numLines; i++) { 753 float x = centerX + cos(angle) * xRadius; 754 float y = centerY + sin(angle) * yRadius; 755 angle += step; 756 points ~= PointF(x, y); 757 } 758 polyLineF(points, lineWidth, lineColor, true, fillColor); 759 } 760 761 /// draw ellipse arc or filled ellipse arc 762 void drawEllipseArcF(float centerX, float centerY, float xRadius, float yRadius, float startAngle, float endAngle, float lineWidth, uint lineColor, uint fillColor = COLOR_TRANSPARENT) { 763 import std.math : sin, cos, PI; 764 if (xRadius < 0) 765 xRadius = -xRadius; 766 if (yRadius < 0) 767 yRadius = -yRadius; 768 startAngle = startAngle * 2 * PI / 360; 769 endAngle = endAngle * 2 * PI / 360; 770 if (endAngle < startAngle) 771 endAngle += 2 * PI; 772 float angleDiff = endAngle - startAngle; 773 if (angleDiff > 2*PI) 774 angleDiff %= 2*PI; 775 int numLines = cast(int)((xRadius + yRadius) / angleDiff); 776 if (numLines < 3) 777 numLines = 4; 778 float step = angleDiff / numLines; 779 float angle = startAngle; 780 PointF[] points; 781 points.assumeSafeAppend; 782 points ~= PointF(centerX, centerY); 783 for (int i = 0; i < numLines; i++) { 784 float x = centerX + cos(angle) * xRadius; 785 float y = centerY + sin(angle) * yRadius; 786 angle += step; 787 points ~= PointF(x, y); 788 } 789 polyLineF(points, lineWidth, lineColor, true, fillColor); 790 } 791 792 /// draw poly line of width == 1px; when cycled is true, connect first and last point 793 void polyLine(Point[] points, uint colour, bool cycled) { 794 if (points.length < 2) 795 return; 796 for(int i = 0; i + 1 < points.length; i++) { 797 drawLine(points[i], points[i + 1], colour); 798 } 799 if (cycled && points.length > 2) 800 drawLine(points[$ - 1], points[0], colour); 801 } 802 803 /// draw line from point p1 to p2 with specified color 804 void drawLine(Point p1, Point p2, uint colour) { 805 if (!clipLine(_clipRect, p1, p2)) 806 return; 807 // from rosettacode.org 808 import std.math: abs; 809 immutable int dx = p2.x - p1.x; 810 immutable int ix = (dx > 0) - (dx < 0); 811 immutable int dx2 = abs(dx) * 2; 812 int dy = p2.y - p1.y; 813 immutable int iy = (dy > 0) - (dy < 0); 814 immutable int dy2 = abs(dy) * 2; 815 drawPixel(p1.x, p1.y, colour); 816 if (dx2 >= dy2) { 817 int error = dy2 - (dx2 / 2); 818 while (p1.x != p2.x) { 819 if (error >= 0 && (error || (ix > 0))) { 820 error -= dx2; 821 p1.y += iy; 822 } 823 error += dy2; 824 p1.x += ix; 825 drawPixel(p1.x, p1.y, colour); 826 } 827 } else { 828 int error = dx2 - (dy2 / 2); 829 while (p1.y != p2.y) { 830 if (error >= 0 && (error || (iy > 0))) { 831 error -= dy2; 832 p1.x += ix; 833 } 834 error += dx2; 835 p1.y += iy; 836 drawPixel(p1.x, p1.y, colour); 837 } 838 } 839 } 840 841 /// create drawbuf with copy of current buffer with changed colors (returns this if not supported) 842 DrawBuf transformColors(ref ColorTransform transform) { 843 return this; 844 } 845 846 /// draw custom OpenGL scene 847 void drawCustomOpenGLScene(Rect rc, OpenGLDrawableDelegate handler) { 848 // override it for OpenGL draw buffer 849 Log.w("drawCustomOpenGLScene is called for non-OpenGL DrawBuf"); 850 } 851 852 void clear() { 853 resetClipping(); 854 } 855 } 856 857 alias DrawBufRef = Ref!DrawBuf; 858 859 /// RAII setting/restoring of clip rectangle 860 struct ClipRectSaver { 861 private DrawBuf _buf; 862 private Rect _oldClipRect; 863 private uint _oldAlpha; 864 /// apply (intersect) new clip rectangle and alpha to draw buf; restore 865 this(DrawBuf buf, ref Rect newClipRect, uint newAlpha = 0) { 866 _buf = buf; 867 _oldClipRect = buf.clipRect; 868 _oldAlpha = buf.alpha; 869 buf.intersectClipRect(newClipRect); 870 if (newAlpha) 871 buf.addAlpha(newAlpha); 872 } 873 ~this() { 874 _buf.clipRect = _oldClipRect; 875 _buf.alpha = _oldAlpha; 876 } 877 } 878 879 class ColorDrawBufBase : DrawBuf { 880 int _dx; 881 int _dy; 882 /// returns buffer bits per pixel 883 override @property int bpp() { return 32; } 884 @property override int width() { return _dx; } 885 @property override int height() { return _dy; } 886 887 /// returns pointer to ARGB scanline, null if y is out of range or buffer doesn't provide access to its memory 888 uint * scanLine(int y) { return null; } 889 890 /// draw source buffer rectangle contents to destination buffer 891 override void drawFragment(int x, int y, DrawBuf src, Rect srcrect) { 892 Rect dstrect = Rect(x, y, x + srcrect.width, y + srcrect.height); 893 if (applyClipping(dstrect, srcrect)) { 894 if (src.applyClipping(srcrect, dstrect)) { 895 int dx = srcrect.width; 896 int dy = srcrect.height; 897 ColorDrawBufBase colorDrawBuf = cast(ColorDrawBufBase) src; 898 if (colorDrawBuf !is null) { 899 foreach(yy; 0 .. dy) { 900 uint * srcrow = colorDrawBuf.scanLine(srcrect.top + yy) + srcrect.left; 901 uint * dstrow = scanLine(dstrect.top + yy) + dstrect.left; 902 if (!_alpha) { 903 // simplified version - no alpha blending 904 foreach(i; 0 .. dx) { 905 uint pixel = srcrow[i]; 906 uint alpha = pixel >> 24; 907 if (!alpha) 908 dstrow[i] = pixel; 909 else if (alpha < 254) { 910 // apply blending 911 dstrow[i] = blendARGB(dstrow[i], pixel, alpha); 912 } 913 } 914 } else { 915 // combine two alphas 916 foreach(i; 0 .. dx) { 917 uint pixel = srcrow[i]; 918 uint alpha = blendAlpha(_alpha, pixel >> 24); 919 if (!alpha) 920 dstrow[i] = pixel; 921 else if (alpha < 254) { 922 // apply blending 923 dstrow[i] = blendARGB(dstrow[i], pixel, alpha); 924 } 925 } 926 } 927 928 } 929 } 930 } 931 } 932 } 933 934 import std.container.array; 935 936 /// Create mapping of source coordinates to destination coordinates, for resize. 937 private Array!int createMap(int dst0, int dst1, int src0, int src1, double k) { 938 int dd = dst1 - dst0; 939 //int sd = src1 - src0; 940 Array!int res; 941 res.length = dd; 942 foreach(int i; 0 .. dd) 943 res[i] = src0 + cast(int)(i * k);//sd / dd; 944 return res; 945 } 946 947 /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling 948 override void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect) { 949 //Log.d("drawRescaled ", dstrect, " <- ", srcrect); 950 if (_alpha >= 254) 951 return; // fully transparent - don't draw 952 double kx = cast(double)srcrect.width / dstrect.width; 953 double ky = cast(double)srcrect.height / dstrect.height; 954 if (applyClipping(dstrect, srcrect)) { 955 auto xmapArray = createMap(dstrect.left, dstrect.right, srcrect.left, srcrect.right, kx); 956 auto ymapArray = createMap(dstrect.top, dstrect.bottom, srcrect.top, srcrect.bottom, ky); 957 958 int * xmap = &xmapArray[0]; 959 int * ymap = &ymapArray[0]; 960 int dx = dstrect.width; 961 int dy = dstrect.height; 962 ColorDrawBufBase colorDrawBuf = cast(ColorDrawBufBase) src; 963 if (colorDrawBuf !is null) { 964 foreach(y; 0 .. dy) { 965 uint * srcrow = colorDrawBuf.scanLine(ymap[y]); 966 uint * dstrow = scanLine(dstrect.top + y) + dstrect.left; 967 if (!_alpha) { 968 // simplified alpha calculation 969 foreach(x; 0 .. dx) { 970 uint srcpixel = srcrow[xmap[x]]; 971 uint dstpixel = dstrow[x]; 972 uint alpha = srcpixel >> 24; 973 if (!alpha) 974 dstrow[x] = srcpixel; 975 else if (alpha < 255) { 976 // apply blending 977 dstrow[x] = blendARGB(dstpixel, srcpixel, alpha); 978 } 979 } 980 } else { 981 // blending two alphas 982 foreach(x; 0 .. dx) { 983 uint srcpixel = srcrow[xmap[x]]; 984 uint dstpixel = dstrow[x]; 985 uint srca = srcpixel >> 24; 986 uint alpha = !srca ? _alpha : blendAlpha(_alpha, srca); 987 if (!alpha) 988 dstrow[x] = srcpixel; 989 else if (alpha < 255) { 990 // apply blending 991 dstrow[x] = blendARGB(dstpixel, srcpixel, alpha); 992 } 993 } 994 } 995 } 996 } 997 } 998 } 999 1000 /// detect position of black pixels in row for 9-patch markup 1001 private bool detectHLine(int y, ref int x0, ref int x1) { 1002 uint * line = scanLine(y); 1003 bool foundUsed = false; 1004 x0 = 0; 1005 x1 = 0; 1006 foreach(int x; 1 .. _dx - 1) { 1007 if (isBlackPixel(line[x])) { // opaque black pixel 1008 if (!foundUsed) { 1009 x0 = x; 1010 foundUsed = true; 1011 } 1012 x1 = x + 1; 1013 } 1014 } 1015 return x1 > x0; 1016 } 1017 1018 static bool isBlackPixel(uint c) { 1019 if (((c >> 24) & 255) > 10) 1020 return false; 1021 if (((c >> 16) & 255) > 10) 1022 return false; 1023 if (((c >> 8) & 255) > 10) 1024 return false; 1025 if (((c >> 0) & 255) > 10) 1026 return false; 1027 return true; 1028 } 1029 1030 /// detect position of black pixels in column for 9-patch markup 1031 private bool detectVLine(int x, ref int y0, ref int y1) { 1032 bool foundUsed = false; 1033 y0 = 0; 1034 y1 = 0; 1035 foreach(int y; 1 .. _dy - 1) { 1036 uint * line = scanLine(y); 1037 if (isBlackPixel(line[x])) { // opaque black pixel 1038 if (!foundUsed) { 1039 y0 = y; 1040 foundUsed = true; 1041 } 1042 y1 = y + 1; 1043 } 1044 } 1045 return y1 > y0; 1046 } 1047 /// detect nine patch using image 1-pixel border (see Android documentation) 1048 override bool detectNinePatch() { 1049 if (_dx < 3 || _dy < 3) 1050 return false; // image is too small 1051 int x00, x01, x10, x11, y00, y01, y10, y11; 1052 bool found = true; 1053 found = found && detectHLine(0, x00, x01); 1054 found = found && detectHLine(_dy - 1, x10, x11); 1055 found = found && detectVLine(0, y00, y01); 1056 found = found && detectVLine(_dx - 1, y10, y11); 1057 if (!found) 1058 return false; // no black pixels on 1-pixel frame 1059 NinePatch * p = new NinePatch(); 1060 p.frame.left = x00 - 1; 1061 p.frame.right = _dx - x01 - 1; 1062 p.frame.top = y00 - 1; 1063 p.frame.bottom = _dy - y01 - 1; 1064 p.padding.left = x10 - 1; 1065 p.padding.right = _dx - x11 - 1; 1066 p.padding.top = y10 - 1; 1067 p.padding.bottom = _dy - y11 - 1; 1068 _ninePatch = p; 1069 //Log.d("NinePatch detected: frame=", p.frame, " padding=", p.padding, " left+right=", p.frame.left + p.frame.right, " dx=", _dx); 1070 return true; 1071 } 1072 1073 override void drawGlyph(int x, int y, Glyph * glyph, uint color) { 1074 ubyte[] src = glyph.glyph; 1075 int srcdx = glyph.blackBoxX; 1076 int srcdy = glyph.blackBoxY; 1077 bool clipping = true; //!_clipRect.empty(); 1078 color = applyAlpha(color); 1079 bool subpixel = glyph.subpixelMode != SubpixelRenderingMode.None; 1080 foreach(int yy; 0 .. srcdy) { 1081 int liney = y + yy; 1082 if (clipping && (liney < _clipRect.top || liney >= _clipRect.bottom)) 1083 continue; 1084 if (liney < 0 || liney >= _dy) 1085 continue; 1086 uint * row = scanLine(liney); 1087 ubyte * srcrow = src.ptr + yy * srcdx; 1088 foreach(int xx; 0 .. srcdx) { 1089 int colx = x + (subpixel ? xx / 3 : xx); 1090 if (clipping && (colx < _clipRect.left || colx >= _clipRect.right)) 1091 continue; 1092 if (colx < 0 || colx >= _dx) 1093 continue; 1094 uint alpha2 = (color >> 24); 1095 uint alpha1 = srcrow[xx] ^ 255; 1096 uint alpha = ((((alpha1 ^ 255) * (alpha2 ^ 255)) >> 8) ^ 255) & 255; 1097 if (subpixel) { 1098 int x0 = xx % 3; 1099 ubyte * dst = cast(ubyte*)(row + colx); 1100 ubyte * pcolor = cast(ubyte*)(&color); 1101 blendSubpixel(dst, pcolor, alpha, x0, glyph.subpixelMode); 1102 } else { 1103 uint pixel = row[colx]; 1104 if (alpha < 255) { 1105 if (!alpha) 1106 row[colx] = pixel; 1107 else { 1108 // apply blending 1109 row[colx] = blendARGB(pixel, color, alpha); 1110 } 1111 } 1112 } 1113 } 1114 } 1115 } 1116 1117 void drawGlyphToTexture(int x, int y, Glyph * glyph) { 1118 ubyte[] src = glyph.glyph; 1119 int srcdx = glyph.blackBoxX; 1120 int srcdy = glyph.blackBoxY; 1121 bool subpixel = glyph.subpixelMode != SubpixelRenderingMode.None; 1122 foreach(int yy; 0 .. srcdy) { 1123 int liney = y + yy; 1124 uint * row = scanLine(liney); 1125 ubyte * srcrow = src.ptr + yy * srcdx; 1126 int increment = subpixel ? 3 : 1; 1127 for (int xx = 0; xx <= srcdx - increment; xx += increment) { 1128 int colx = x + (subpixel ? xx / 3 : xx); 1129 if (subpixel) { 1130 uint t1 = srcrow[xx]; 1131 uint t2 = srcrow[xx + 1]; 1132 uint t3 = srcrow[xx + 2]; 1133 //uint pixel = ((t2 ^ 0x00) << 24) | ((t1 ^ 0xFF)<< 16) | ((t2 ^ 0xFF) << 8) | (t3 ^ 0xFF); 1134 uint pixel = ((t2 ^ 0x00) << 24) | 0xFFFFFF; 1135 row[colx] = pixel; 1136 } else { 1137 uint alpha1 = srcrow[xx] ^ 0xFF; 1138 //uint pixel = (alpha1 << 24) | 0xFFFFFF; //(alpha1 << 16) || (alpha1 << 8) || alpha1; 1139 //uint pixel = ((alpha1 ^ 0xFF) << 24) | (alpha1 << 16) | (alpha1 << 8) | alpha1; 1140 uint pixel = ((alpha1 ^ 0xFF) << 24) | 0xFFFFFF; 1141 row[colx] = pixel; 1142 } 1143 } 1144 } 1145 } 1146 1147 override void fillRect(Rect rc, uint color) { 1148 uint alpha = color >> 24; 1149 if (applyClipping(rc)) { 1150 foreach(y; rc.top .. rc.bottom) { 1151 uint * row = scanLine(y); 1152 if (!alpha) { 1153 row[rc.left .. rc.right] = color; 1154 } else if (alpha < 254) { 1155 foreach(x; rc.left .. rc.right) { 1156 // apply blending 1157 row[x] = blendARGB(row[x], color, alpha); 1158 } 1159 } 1160 } 1161 } 1162 } 1163 1164 /// fill rectangle with solid color and pattern (clipping is applied) 0=solid fill, 1 = dotted 1165 override void fillRectPattern(Rect rc, uint color, int pattern) { 1166 uint alpha = color >> 24; 1167 if (alpha == 255) // fully transparent 1168 return; 1169 if (applyClipping(rc)) { 1170 foreach(y; rc.top .. rc.bottom) { 1171 uint * row = scanLine(y); 1172 if (!alpha) { 1173 if (pattern == 1) { 1174 foreach(x; rc.left .. rc.right) { 1175 if ((x ^ y) & 1) 1176 row[x] = color; 1177 } 1178 } else { 1179 row[rc.left .. rc.right] = color; 1180 } 1181 } else if (alpha < 254) { 1182 foreach(x; rc.left .. rc.right) { 1183 // apply blending 1184 if (pattern != 1 || ((x ^ y) & 1) != 0) 1185 row[x] = blendARGB(row[x], color, alpha); 1186 } 1187 } 1188 } 1189 } 1190 } 1191 1192 /// draw pixel at (x, y) with specified color 1193 override void drawPixel(int x, int y, uint color) { 1194 if (!_clipRect.isPointInside(x, y)) 1195 return; 1196 color = applyAlpha(color); 1197 uint * row = scanLine(y); 1198 uint alpha = color >> 24; 1199 if (!alpha) { 1200 row[x] = color; 1201 } else if (alpha < 254) { 1202 // apply blending 1203 row[x] = blendARGB(row[x], color, alpha); 1204 } 1205 } 1206 1207 } 1208 1209 class GrayDrawBuf : DrawBuf { 1210 int _dx; 1211 int _dy; 1212 /// returns buffer bits per pixel 1213 override @property int bpp() { return 8; } 1214 @property override int width() { return _dx; } 1215 @property override int height() { return _dy; } 1216 1217 ubyte[] _buf; 1218 this(int width, int height) { 1219 resize(width, height); 1220 } 1221 ubyte * scanLine(int y) { 1222 if (y >= 0 && y < _dy) 1223 return _buf.ptr + _dx * y; 1224 return null; 1225 } 1226 override void resize(int width, int height) { 1227 if (_dx == width && _dy == height) 1228 return; 1229 _dx = width; 1230 _dy = height; 1231 _buf.length = _dx * _dy; 1232 resetClipping(); 1233 } 1234 override void fill(uint color) { 1235 if (hasClipping) { 1236 fillRect(_clipRect, color); 1237 return; 1238 } 1239 int len = _dx * _dy; 1240 ubyte * p = _buf.ptr; 1241 ubyte cl = rgbToGray(color); 1242 foreach(i; 0 .. len) 1243 p[i] = cl; 1244 } 1245 1246 /// draw source buffer rectangle contents to destination buffer 1247 override void drawFragment(int x, int y, DrawBuf src, Rect srcrect) { 1248 Rect dstrect = Rect(x, y, x + srcrect.width, y + srcrect.height); 1249 if (applyClipping(dstrect, srcrect)) { 1250 if (src.applyClipping(srcrect, dstrect)) { 1251 int dx = srcrect.width; 1252 int dy = srcrect.height; 1253 GrayDrawBuf grayDrawBuf = cast (GrayDrawBuf) src; 1254 if (grayDrawBuf !is null) { 1255 foreach(yy; 0 .. dy) { 1256 ubyte * srcrow = grayDrawBuf.scanLine(srcrect.top + yy) + srcrect.left; 1257 ubyte * dstrow = scanLine(dstrect.top + yy) + dstrect.left; 1258 foreach(i; 0 .. dx) { 1259 ubyte pixel = srcrow[i]; 1260 dstrow[i] = pixel; 1261 } 1262 } 1263 } 1264 } 1265 } 1266 } 1267 1268 /// Create mapping of source coordinates to destination coordinates, for resize. 1269 private int[] createMap(int dst0, int dst1, int src0, int src1) { 1270 int dd = dst1 - dst0; 1271 int sd = src1 - src0; 1272 int[] res = new int[dd]; 1273 foreach(int i; 0 .. dd) 1274 res[i] = src0 + i * sd / dd; 1275 return res; 1276 } 1277 /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling 1278 override void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect) { 1279 //Log.d("drawRescaled ", dstrect, " <- ", srcrect); 1280 if (applyClipping(dstrect, srcrect)) { 1281 int[] xmap = createMap(dstrect.left, dstrect.right, srcrect.left, srcrect.right); 1282 int[] ymap = createMap(dstrect.top, dstrect.bottom, srcrect.top, srcrect.bottom); 1283 int dx = dstrect.width; 1284 int dy = dstrect.height; 1285 GrayDrawBuf grayDrawBuf = cast (GrayDrawBuf) src; 1286 if (grayDrawBuf !is null) { 1287 foreach(y; 0 .. dy) { 1288 ubyte * srcrow = grayDrawBuf.scanLine(ymap[y]); 1289 ubyte * dstrow = scanLine(dstrect.top + y) + dstrect.left; 1290 foreach(x; 0 .. dx) { 1291 ubyte srcpixel = srcrow[xmap[x]]; 1292 ubyte dstpixel = dstrow[x]; 1293 dstrow[x] = srcpixel; 1294 } 1295 } 1296 } 1297 } 1298 } 1299 1300 /// detect position of black pixels in row for 9-patch markup 1301 private bool detectHLine(int y, ref int x0, ref int x1) { 1302 ubyte * line = scanLine(y); 1303 bool foundUsed = false; 1304 x0 = 0; 1305 x1 = 0; 1306 foreach(int x; 1 .. _dx - 1) { 1307 if (line[x] == 0x00000000) { // opaque black pixel 1308 if (!foundUsed) { 1309 x0 = x; 1310 foundUsed = true; 1311 } 1312 x1 = x + 1; 1313 } 1314 } 1315 return x1 > x0; 1316 } 1317 1318 /// detect position of black pixels in column for 9-patch markup 1319 private bool detectVLine(int x, ref int y0, ref int y1) { 1320 bool foundUsed = false; 1321 y0 = 0; 1322 y1 = 0; 1323 foreach(int y; 1 .. _dy - 1) { 1324 ubyte * line = scanLine(y); 1325 if (line[x] == 0x00000000) { // opaque black pixel 1326 if (!foundUsed) { 1327 y0 = y; 1328 foundUsed = true; 1329 } 1330 y1 = y + 1; 1331 } 1332 } 1333 return y1 > y0; 1334 } 1335 /// detect nine patch using image 1-pixel border (see Android documentation) 1336 override bool detectNinePatch() { 1337 if (_dx < 3 || _dy < 3) 1338 return false; // image is too small 1339 int x00, x01, x10, x11, y00, y01, y10, y11; 1340 bool found = true; 1341 found = found && detectHLine(0, x00, x01); 1342 found = found && detectHLine(_dy - 1, x10, x11); 1343 found = found && detectVLine(0, y00, y01); 1344 found = found && detectVLine(_dx - 1, y10, y11); 1345 if (!found) 1346 return false; // no black pixels on 1-pixel frame 1347 NinePatch * p = new NinePatch(); 1348 p.frame.left = x00 - 1; 1349 p.frame.right = _dy - y01 - 1; 1350 p.frame.top = y00 - 1; 1351 p.frame.bottom = _dy - y01 - 1; 1352 p.padding.left = x10 - 1; 1353 p.padding.right = _dy - y11 - 1; 1354 p.padding.top = y10 - 1; 1355 p.padding.bottom = _dy - y11 - 1; 1356 _ninePatch = p; 1357 return true; 1358 } 1359 override void drawGlyph(int x, int y, Glyph * glyph, uint color) { 1360 ubyte[] src = glyph.glyph; 1361 int srcdx = glyph.blackBoxX; 1362 int srcdy = glyph.blackBoxY; 1363 bool clipping = true; //!_clipRect.empty(); 1364 foreach(int yy; 0 .. srcdy) { 1365 int liney = y + yy; 1366 if (clipping && (liney < _clipRect.top || liney >= _clipRect.bottom)) 1367 continue; 1368 if (liney < 0 || liney >= _dy) 1369 continue; 1370 ubyte * row = scanLine(liney); 1371 ubyte * srcrow = src.ptr + yy * srcdx; 1372 foreach(int xx; 0 .. srcdx) { 1373 int colx = xx + x; 1374 if (clipping && (colx < _clipRect.left || colx >= _clipRect.right)) 1375 continue; 1376 if (colx < 0 || colx >= _dx) 1377 continue; 1378 uint alpha1 = srcrow[xx] ^ 255; 1379 uint alpha2 = (color >> 24); 1380 uint alpha = ((((alpha1 ^ 255) * (alpha2 ^ 255)) >> 8) ^ 255) & 255; 1381 uint pixel = row[colx]; 1382 if (!alpha) 1383 row[colx] = cast(ubyte)pixel; 1384 else if (alpha < 255) { 1385 // apply blending 1386 row[colx] = cast(ubyte)blendARGB(pixel, color, alpha); 1387 } 1388 } 1389 } 1390 } 1391 override void fillRect(Rect rc, uint color) { 1392 if (applyClipping(rc)) { 1393 uint alpha = color >> 24; 1394 ubyte cl = rgbToGray(color); 1395 foreach(y; rc.top .. rc.bottom) { 1396 ubyte * row = scanLine(y); 1397 foreach(x; rc.left .. rc.right) { 1398 if (!alpha) 1399 row[x] = cl; 1400 else if (alpha < 255) { 1401 // apply blending 1402 row[x] = blendGray(row[x], cl, alpha); 1403 } 1404 } 1405 } 1406 } 1407 } 1408 1409 /// draw pixel at (x, y) with specified color 1410 override void drawPixel(int x, int y, uint color) { 1411 if (!_clipRect.isPointInside(x, y)) 1412 return; 1413 color = applyAlpha(color); 1414 ubyte cl = rgbToGray(color); 1415 ubyte * row = scanLine(y); 1416 uint alpha = color >> 24; 1417 if (!alpha) { 1418 row[x] = cl; 1419 } else if (alpha < 254) { 1420 // apply blending 1421 row[x] = blendGray(row[x], cl, alpha); 1422 } 1423 } 1424 } 1425 1426 class ColorDrawBuf : ColorDrawBufBase { 1427 uint[] _buf; 1428 /// create ARGB8888 draw buf of specified width and height 1429 this(int width, int height) { 1430 resize(width, height); 1431 } 1432 /// create copy of ColorDrawBuf 1433 this(ColorDrawBuf v) { 1434 this(v.width, v.height); 1435 //_buf.length = v._buf.length; 1436 foreach(i; 0 .. _buf.length) 1437 _buf[i] = v._buf[i]; 1438 } 1439 /// create resized copy of ColorDrawBuf 1440 this(ColorDrawBuf v, int dx, int dy) { 1441 this(dx, dy); 1442 fill(0xFFFFFFFF); 1443 drawRescaled(Rect(0, 0, dx, dy), v, Rect(0, 0, v.width, v.height)); 1444 } 1445 1446 void invertAndPreMultiplyAlpha() { 1447 foreach(ref pixel; _buf) { 1448 uint a = (pixel >> 24) & 0xFF; 1449 uint r = (pixel >> 16) & 0xFF; 1450 uint g = (pixel >> 8) & 0xFF; 1451 uint b = (pixel >> 0) & 0xFF; 1452 a ^= 0xFF; 1453 if (a > 0xFC) { 1454 r = ((r * a) >> 8) & 0xFF; 1455 g = ((g * a) >> 8) & 0xFF; 1456 b = ((b * a) >> 8) & 0xFF; 1457 } 1458 pixel = (a << 24) | (r << 16) | (g << 8) | (b << 0); 1459 } 1460 } 1461 1462 void invertAlpha() { 1463 foreach(ref pixel; _buf) 1464 pixel ^= 0xFF000000; 1465 } 1466 1467 void invertByteOrder() { 1468 foreach(ref pixel; _buf) { 1469 pixel = (pixel & 0xFF00FF00) | 1470 ((pixel & 0xFF0000) >> 16) | 1471 ((pixel & 0xFF) << 16); 1472 } 1473 } 1474 1475 // for passing of image to OpenGL texture 1476 void invertAlphaAndByteOrder() { 1477 foreach(ref pixel; _buf) { 1478 pixel = ((pixel & 0xFF00FF00) | 1479 ((pixel & 0xFF0000) >> 16) | 1480 ((pixel & 0xFF) << 16)); 1481 pixel ^= 0xFF000000; 1482 } 1483 } 1484 override uint * scanLine(int y) { 1485 if (y >= 0 && y < _dy) 1486 return _buf.ptr + _dx * y; 1487 return null; 1488 } 1489 override void resize(int width, int height) { 1490 if (_dx == width && _dy == height) 1491 return; 1492 _dx = width; 1493 _dy = height; 1494 _buf.length = _dx * _dy; 1495 resetClipping(); 1496 } 1497 override void fill(uint color) { 1498 if (hasClipping) { 1499 fillRect(_clipRect, color); 1500 return; 1501 } 1502 int len = _dx * _dy; 1503 uint * p = _buf.ptr; 1504 foreach(i; 0 .. len) 1505 p[i] = color; 1506 } 1507 override DrawBuf transformColors(ref ColorTransform transform) { 1508 if (transform.empty) 1509 return this; 1510 bool skipFrame = hasNinePatch; 1511 ColorDrawBuf res = new ColorDrawBuf(_dx, _dy); 1512 if (hasNinePatch) { 1513 NinePatch * p = new NinePatch; 1514 *p = *_ninePatch; 1515 res.ninePatch = p; 1516 } 1517 foreach(int y; 0 .. _dy) { 1518 uint * srcline = scanLine(y); 1519 uint * dstline = res.scanLine(y); 1520 bool allowTransformY = !skipFrame || (y !=0 && y != _dy - 1); 1521 foreach(int x; 0 .. _dx) { 1522 bool allowTransformX = !skipFrame || (x !=0 && x != _dx - 1); 1523 if (!allowTransformX || !allowTransformY) 1524 dstline[x] = srcline[x]; 1525 else 1526 dstline[x] = transform.transform(srcline[x]); 1527 } 1528 } 1529 return res; 1530 } 1531 } 1532 1533 1534 // line clipping algorithm from https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm 1535 private alias OutCode = int; 1536 1537 private const int INSIDE = 0; // 0000 1538 private const int LEFT = 1; // 0001 1539 private const int RIGHT = 2; // 0010 1540 private const int BOTTOM = 4; // 0100 1541 private const int TOP = 8; // 1000 1542 1543 // Compute the bit code for a point (x, y) using the clip rectangle 1544 // bounded diagonally by (xmin, ymin), and (xmax, ymax) 1545 1546 // ASSUME THAT xmax, xmin, ymax and ymin are global constants. 1547 1548 private OutCode ComputeOutCode(Rect clipRect, double x, double y) 1549 { 1550 OutCode code; 1551 1552 code = INSIDE; // initialised as being inside of clip window 1553 1554 if (x < clipRect.left) // to the left of clip window 1555 code |= LEFT; 1556 else if (x > clipRect.right) // to the right of clip window 1557 code |= RIGHT; 1558 if (y < clipRect.top) // below the clip window 1559 code |= BOTTOM; 1560 else if (y > clipRect.bottom) // above the clip window 1561 code |= TOP; 1562 1563 return code; 1564 } 1565 1566 package bool clipLine(ref Rect clipRect, ref Point p1, ref Point p2) { 1567 double x0 = p1.x; 1568 double y0 = p1.y; 1569 double x1 = p2.x; 1570 double y1 = p2.y; 1571 bool res = CohenSutherlandLineClipAndDraw(clipRect, x0, y0, x1, y1); 1572 if (res) { 1573 p1.x = cast(int)x0; 1574 p1.y = cast(int)y0; 1575 p2.x = cast(int)x1; 1576 p2.y = cast(int)y1; 1577 } 1578 return res; 1579 } 1580 1581 // Cohen–Sutherland clipping algorithm clips a line from 1582 // P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with 1583 // diagonal from (xmin, ymin) to (xmax, ymax). 1584 private bool CohenSutherlandLineClipAndDraw(ref Rect clipRect, ref double x0, ref double y0, ref double x1, ref double y1) 1585 { 1586 // compute outcodes for P0, P1, and whatever point lies outside the clip rectangle 1587 OutCode outcode0 = ComputeOutCode(clipRect, x0, y0); 1588 OutCode outcode1 = ComputeOutCode(clipRect, x1, y1); 1589 bool accept = false; 1590 1591 while (true) { 1592 if (!(outcode0 | outcode1)) { // Bitwise OR is 0. Trivially accept and get out of loop 1593 accept = true; 1594 break; 1595 } else if (outcode0 & outcode1) { // Bitwise AND is not 0. Trivially reject and get out of loop 1596 break; 1597 } else { 1598 // failed both tests, so calculate the line segment to clip 1599 // from an outside point to an intersection with clip edge 1600 double x, y; 1601 1602 // At least one endpoint is outside the clip rectangle; pick it. 1603 OutCode outcodeOut = outcode0 ? outcode0 : outcode1; 1604 1605 // Now find the intersection point; 1606 // use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0) 1607 if (outcodeOut & TOP) { // point is above the clip rectangle 1608 x = x0 + (x1 - x0) * (clipRect.bottom - y0) / (y1 - y0); 1609 y = clipRect.bottom; 1610 } else if (outcodeOut & BOTTOM) { // point is below the clip rectangle 1611 x = x0 + (x1 - x0) * (clipRect.top - y0) / (y1 - y0); 1612 y = clipRect.top; 1613 } else if (outcodeOut & RIGHT) { // point is to the right of clip rectangle 1614 y = y0 + (y1 - y0) * (clipRect.right - x0) / (x1 - x0); 1615 x = clipRect.right; 1616 } else if (outcodeOut & LEFT) { // point is to the left of clip rectangle 1617 y = y0 + (y1 - y0) * (clipRect.left - x0) / (x1 - x0); 1618 x = clipRect.left; 1619 } 1620 1621 // Now we move outside point to intersection point to clip 1622 // and get ready for next pass. 1623 if (outcodeOut == outcode0) { 1624 x0 = x; 1625 y0 = y; 1626 outcode0 = ComputeOutCode(clipRect, x0, y0); 1627 } else { 1628 x1 = x; 1629 y1 = y; 1630 outcode1 = ComputeOutCode(clipRect, x1, y1); 1631 } 1632 } 1633 } 1634 return accept; 1635 }