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 a gradient (clipping is applied) 292 abstract void fillGradientRect(Rect rc, uint color1, uint color2, uint color3, uint color4); 293 /// fill rectangle with solid color and pattern (clipping is applied) 0=solid fill, 1 = dotted 294 void fillRectPattern(Rect rc, uint color, int pattern) { 295 // default implementation: does not support patterns 296 fillRect(rc, color); 297 } 298 /// draw pixel at (x, y) with specified color 299 abstract void drawPixel(int x, int y, uint color); 300 /// draw 8bit alpha image - usually font glyph using specified color (clipping is applied) 301 abstract void drawGlyph(int x, int y, Glyph * glyph, uint color); 302 /// draw source buffer rectangle contents to destination buffer 303 abstract void drawFragment(int x, int y, DrawBuf src, Rect srcrect); 304 /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling 305 abstract void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect); 306 /// draw unscaled image at specified coordinates 307 void drawImage(int x, int y, DrawBuf src) { 308 drawFragment(x, y, src, Rect(0, 0, src.width, src.height)); 309 } 310 /// draws rectangle frame of specified color and widths (per side), and optinally fills inner area 311 void drawFrame(Rect rc, uint frameColor, Rect frameSideWidths, uint innerAreaColor = 0xFFFFFFFF) { 312 // draw frame 313 if (!isFullyTransparentColor(frameColor)) { 314 Rect r; 315 // left side 316 r = rc; 317 r.right = r.left + frameSideWidths.left; 318 if (!r.empty) 319 fillRect(r, frameColor); 320 // right side 321 r = rc; 322 r.left = r.right - frameSideWidths.right; 323 if (!r.empty) 324 fillRect(r, frameColor); 325 // top side 326 r = rc; 327 r.left += frameSideWidths.left; 328 r.right -= frameSideWidths.right; 329 Rect rc2 = r; 330 rc2.bottom = r.top + frameSideWidths.top; 331 if (!rc2.empty) 332 fillRect(rc2, frameColor); 333 // bottom side 334 rc2 = r; 335 rc2.top = r.bottom - frameSideWidths.bottom; 336 if (!rc2.empty) 337 fillRect(rc2, frameColor); 338 } 339 // draw internal area 340 if (!isFullyTransparentColor(innerAreaColor)) { 341 rc.left += frameSideWidths.left; 342 rc.top += frameSideWidths.top; 343 rc.right -= frameSideWidths.right; 344 rc.bottom -= frameSideWidths.bottom; 345 if (!rc.empty) 346 fillRect(rc, innerAreaColor); 347 } 348 } 349 350 /// draw focus rectangle; vertical gradient supported - colors[0] is top color, colors[1] is bottom color 351 void drawFocusRect(Rect rc, const uint[] colors) { 352 // override for faster performance when using OpenGL 353 if (colors.length < 1) 354 return; 355 uint color1 = colors[0]; 356 uint color2 = colors.length > 1 ? colors[1] : color1; 357 if (isFullyTransparentColor(color1) && isFullyTransparentColor(color2)) 358 return; 359 // draw horizontal lines 360 foreach(int x; rc.left .. rc.right) { 361 if ((x ^ rc.top) & 1) 362 fillRect(Rect(x, rc.top, x + 1, rc.top + 1), color1); 363 if ((x ^ (rc.bottom - 1)) & 1) 364 fillRect(Rect(x, rc.bottom - 1, x + 1, rc.bottom), color2); 365 } 366 // draw vertical lines 367 foreach(int y; rc.top + 1 .. rc.bottom - 1) { 368 uint color = color1 == color2 ? color1 : blendARGB(color2, color1, 255 / (rc.bottom - rc.top)); 369 if ((y ^ rc.left) & 1) 370 fillRect(Rect(rc.left, y, rc.left + 1, y + 1), color); 371 if ((y ^ (rc.right - 1)) & 1) 372 fillRect(Rect(rc.right - 1, y, rc.right, y + 1), color); 373 } 374 } 375 376 /// draw filled triangle in float coordinates; clipping is already applied 377 protected void fillTriangleFClipped(PointF p1, PointF p2, PointF p3, uint colour) { 378 // override and implement it 379 } 380 381 /// find intersection of line p1..p2 with clip rectangle 382 protected bool intersectClipF(ref PointF p1, ref PointF p2, ref bool p1moved, ref bool p2moved) { 383 if (p1.x < _clipRect.left && p2.x < _clipRect.left) 384 return true; 385 if (p1.x >= _clipRect.right && p2.x >= _clipRect.right) 386 return true; 387 if (p1.y < _clipRect.top && p2.y < _clipRect.top) 388 return true; 389 if (p1.y >= _clipRect.bottom && p2.y >= _clipRect.bottom) 390 return true; 391 // horizontal clip 392 if (p1.x < _clipRect.left && p2.x >= _clipRect.left) { 393 // move p1 to clip left 394 p1 += (p2 - p1) * ((_clipRect.left - p1.x) / (p2.x - p1.x)); 395 p1moved = true; 396 } 397 if (p2.x < _clipRect.left && p1.x >= _clipRect.left) { 398 // move p2 to clip left 399 p2 += (p1 - p2) * ((_clipRect.left - p2.x) / (p1.x - p2.x)); 400 p2moved = true; 401 } 402 if (p1.x > _clipRect.right && p2.x < _clipRect.right) { 403 // move p1 to clip right 404 p1 += (p2 - p1) * ((_clipRect.right - p1.x) / (p2.x - p1.x)); 405 p1moved = true; 406 } 407 if (p2.x > _clipRect.right && p1.x < _clipRect.right) { 408 // move p1 to clip right 409 p2 += (p1 - p2) * ((_clipRect.right - p2.x) / (p1.x - p2.x)); 410 p2moved = true; 411 } 412 // vertical clip 413 if (p1.y < _clipRect.top && p2.y >= _clipRect.top) { 414 // move p1 to clip left 415 p1 += (p2 - p1) * ((_clipRect.top - p1.y) / (p2.y - p1.y)); 416 p1moved = true; 417 } 418 if (p2.y < _clipRect.top && p1.y >= _clipRect.top) { 419 // move p2 to clip left 420 p2 += (p1 - p2) * ((_clipRect.top - p2.y) / (p1.y - p2.y)); 421 p2moved = true; 422 } 423 if (p1.y > _clipRect.bottom && p2.y < _clipRect.bottom) { 424 // move p1 to clip right <0 <0 425 p1 += (p2 - p1) * ((_clipRect.bottom - p1.y) / (p2.y - p1.y)); 426 p1moved = true; 427 } 428 if (p2.y > _clipRect.bottom && p1.y < _clipRect.bottom) { 429 // move p1 to clip right 430 p2 += (p1 - p2) * ((_clipRect.bottom - p2.y) / (p1.y - p2.y)); 431 p2moved = true; 432 } 433 return false; 434 } 435 436 /// draw filled triangle in float coordinates 437 void fillTriangleF(PointF p1, PointF p2, PointF p3, uint colour) { 438 if (_clipRect.empty) // clip rectangle is empty - all drawables are clipped out 439 return; 440 // apply clipping 441 bool p1insideClip = (p1.x >= _clipRect.left && p1.x < _clipRect.right && p1.y >= _clipRect.top && p1.y < _clipRect.bottom); 442 bool p2insideClip = (p2.x >= _clipRect.left && p2.x < _clipRect.right && p2.y >= _clipRect.top && p2.y < _clipRect.bottom); 443 bool p3insideClip = (p3.x >= _clipRect.left && p3.x < _clipRect.right && p3.y >= _clipRect.top && p3.y < _clipRect.bottom); 444 if (p1insideClip && p2insideClip && p3insideClip) { 445 // all points inside clipping area - no clipping required 446 fillTriangleFClipped(p1, p2, p3, colour); 447 return; 448 } 449 // do triangle clipping 450 // check if all points outside the same bound 451 if ((p1.x < _clipRect.left && p2.x < _clipRect.left && p3.x < _clipRect.left) 452 || (p1.x >= _clipRect.right && p2.x >= _clipRect.right && p3.x >= _clipRect.bottom) 453 || (p1.y < _clipRect.top && p2.y < _clipRect.top && p3.y < _clipRect.top) 454 || (p1.y >= _clipRect.bottom && p2.y >= _clipRect.bottom && p3.y >= _clipRect.bottom)) 455 return; 456 /++ 457 + side 1 458 + p1-------p11------------p21--------------p2 459 + \ / 460 + \ / 461 + \ / 462 + \ / 463 + p13\ /p22 464 + \ / 465 + \ / 466 + \ / 467 + \ / side 2 468 + side 3 \ / 469 + \ / 470 + \ / 471 + \ /p32 472 + p33\ / 473 + \ / 474 + \ / 475 + \ / 476 + \ / 477 + \ / 478 + \ / 479 + p3 480 +/ 481 PointF p11 = p1; 482 PointF p13 = p1; 483 PointF p21 = p2; 484 PointF p22 = p2; 485 PointF p32 = p3; 486 PointF p33 = p3; 487 bool p1moved = false; 488 bool p2moved = false; 489 bool p3moved = false; 490 bool side1clipped = intersectClipF(p11, p21, p1moved, p2moved); 491 bool side2clipped = intersectClipF(p22, p32, p2moved, p3moved); 492 bool side3clipped = intersectClipF(p33, p13, p3moved, p1moved); 493 if (!p1moved && !p2moved && !p3moved) { 494 // no moved - no clipping 495 fillTriangleFClipped(p1, p2, p3, colour); 496 } else if (p1moved && !p2moved && !p3moved) { 497 fillTriangleFClipped(p11, p2, p3, colour); 498 fillTriangleFClipped(p3, p13, p11, colour); 499 } else if (!p1moved && p2moved && !p3moved) { 500 fillTriangleFClipped(p22, p3, p1, colour); 501 fillTriangleFClipped(p1, p21, p22, colour); 502 } else if (!p1moved && !p2moved && p3moved) { 503 fillTriangleFClipped(p33, p1, p2, colour); 504 fillTriangleFClipped(p2, p32, p33, colour); 505 } else if (p1moved && p2moved && !p3moved) { 506 if (!side1clipped) { 507 fillTriangleFClipped(p13, p11, p21, colour); 508 fillTriangleFClipped(p21, p22, p13, colour); 509 } 510 fillTriangleFClipped(p22, p3, p13, colour); 511 } else if (!p1moved && p2moved && p3moved) { 512 if (!side2clipped) { 513 fillTriangleFClipped(p21, p22, p32, colour); 514 fillTriangleFClipped(p32, p33, p21, colour); 515 } 516 fillTriangleFClipped(p21, p33, p1, colour); 517 } else if (p1moved && !p2moved && p3moved) { 518 if (!side3clipped) { 519 fillTriangleFClipped(p13, p11, p32, colour); 520 fillTriangleFClipped(p32, p33, p13, colour); 521 } 522 fillTriangleFClipped(p11, p2, p32, colour); 523 } else if (p1moved && p2moved && p3moved) { 524 if (side1clipped) { 525 fillTriangleFClipped(p13, p22, p32, colour); 526 fillTriangleFClipped(p32, p33, p13, colour); 527 } else if (side2clipped) { 528 fillTriangleFClipped(p11, p21, p33, colour); 529 fillTriangleFClipped(p33, p13, p11, colour); 530 } else if (side3clipped) { 531 fillTriangleFClipped(p11, p21, p22, colour); 532 fillTriangleFClipped(p22, p32, p11, colour); 533 } else { 534 fillTriangleFClipped(p13, p11, p21, colour); 535 fillTriangleFClipped(p21, p22, p13, colour); 536 fillTriangleFClipped(p22, p32, p33, colour); 537 fillTriangleFClipped(p33, p13, p22, colour); 538 } 539 } 540 } 541 542 /// draw filled quad in float coordinates 543 void fillQuadF(PointF p1, PointF p2, PointF p3, PointF p4, uint colour) { 544 fillTriangleF(p1, p2, p3, colour); 545 fillTriangleF(p3, p4, p1, colour); 546 } 547 548 /// draw line of arbitrary width in float coordinates 549 void drawLineF(PointF p1, PointF p2, float width, uint colour) { 550 // direction vector 551 PointF v = (p2 - p1).normalized; 552 // calculate normal vector 553 // calculate normal vector : rotate CCW 90 degrees 554 PointF n = v.rotated90ccw(); 555 // rotate CCW 90 degrees 556 n.y = v.x; 557 n.x = -v.y; 558 // offset by normal * half_width 559 n *= width / 2; 560 // draw line using quad 561 fillQuadF(p1 - n, p2 - n, p2 + n, p1 + n, colour); 562 } 563 564 // find intersection point for two vectors with start points p1, p2 and normalized directions dir1, dir2 565 protected static PointF intersectVectors(PointF p1, PointF dir1, PointF p2, PointF dir2) { 566 /* 567 L1 = P1 + a * V1 568 L2 = P2 + b * V2 569 P1 + a * V1 = P2 + b * V2 570 a * V1 = (P2 - P1) + b * V2 571 a * (V1 X V2) = (P2 - P1) X V2 572 a = (P2 - P1) * V2 / (V1*V2) 573 return P1 + a * V1 574 */ 575 // just return middle point 576 PointF p2p1 = (p2 - p1); //.normalized; 577 float d1 = p2p1.crossProduct(dir2); 578 float d2 = dir1.crossProduct(dir2); 579 // a * d1 = d2 580 if (d2 >= -0.1f && d2 <= 0.1f) { 581 return p1; //PointF((p1.x + p2.x)/2, (p1.y + p2.y)/2); 582 } 583 float a = d1 / d2; 584 return p1 + dir1 * a; 585 } 586 587 protected void calcLineSegmentQuad(PointF p0, PointF p1, PointF p2, PointF p3, float width, ref PointF[4] quad) { 588 // direction vector 589 PointF v = (p2 - p1).normalized; 590 // calculate normal vector : rotate CCW 90 degrees 591 PointF n = v.rotated90ccw(); 592 // offset by normal * half_width 593 n *= width / 2; 594 // draw line using quad 595 PointF pp10 = p1 - n; 596 PointF pp20 = p2 - n; 597 PointF pp11 = p1 + n; 598 PointF pp21 = p2 + n; 599 if ((p1 - p0).length > 0.1f) { 600 // has prev segment 601 PointF prevv = (p1 - p0).normalized; 602 PointF prevn = prevv.rotated90ccw(); 603 PointF prev10 = p1 - prevn * width / 2; 604 PointF prev11 = p1 + prevn * width / 2; 605 PointF intersect0 = intersectVectors(pp10, -v, prev10, prevv); 606 PointF intersect1 = intersectVectors(pp11, -v, prev11, prevv); 607 pp10 = intersect0; 608 pp11 = intersect1; 609 } 610 if ((p3 - p2).length > 0.1f) { 611 // has next segment 612 PointF nextv = (p3 - p2).normalized; 613 PointF nextn = nextv.rotated90ccw(); 614 PointF next20 = p2 - nextn * width / 2; 615 PointF next21 = p2 + nextn * width / 2; 616 PointF intersect0 = intersectVectors(pp20, v, next20, -nextv); 617 PointF intersect1 = intersectVectors(pp21, v, next21, -nextv); 618 pp20 = intersect0; 619 pp21 = intersect1; 620 } 621 quad[0] = pp10; 622 quad[1] = pp20; 623 quad[2] = pp21; 624 quad[3] = pp11; 625 } 626 /// draw line of arbitrary width in float coordinates p1..p2 with angle based on previous (p0..p1) and next (p2..p3) segments 627 void drawLineSegmentF(PointF p0, PointF p1, PointF p2, PointF p3, float width, uint colour) { 628 PointF[4] quad; 629 calcLineSegmentQuad(p0, p1, p2, p3, width, quad); 630 fillQuadF(quad[0], quad[1], quad[2], quad[3], colour); 631 } 632 633 /// draw poly line of arbitrary width in float coordinates; when cycled is true, connect first and last point (optionally fill inner area) 634 void polyLineF(PointF[] points, float width, uint colour, bool cycled = false, uint innerAreaColour = COLOR_TRANSPARENT) { 635 if (points.length < 2) 636 return; 637 bool hasInnerArea = !isFullyTransparentColor(innerAreaColour); 638 if (isFullyTransparentColor(colour)) { 639 if (hasInnerArea) 640 fillPolyF(points, innerAreaColour); 641 return; 642 } 643 int len = cast(int)points.length; 644 if (hasInnerArea) { 645 PointF[] innerArea; 646 innerArea.assumeSafeAppend; 647 //Log.d("fill poly inner: ", points); 648 for(int i = 0; i < len; i++) { 649 PointF[4] quad; 650 int index0 = i - 1; 651 int index1 = i; 652 int index2 = i + 1; 653 int index3 = i + 2; 654 if (index0 < 0) 655 index0 = cycled ? len - 1 : 0; 656 index2 %= len; // only can be if cycled 657 index3 %= len; // only can be if cycled 658 if (!cycled) { 659 if (index1 == len - 1) { 660 index0 = index1; 661 index2 = 0; 662 index3 = 0; 663 } else if (index1 == len - 2) { 664 index2 = len - 1; 665 index3 = len - 1; 666 } 667 } 668 //Log.d("lineSegment - inner ", index0, ", ", index1, ", ", index2, ", ", index3); 669 calcLineSegmentQuad(points[index0], points[index1], points[index2], points[index3], width, quad); 670 innerArea ~= quad[3]; 671 } 672 fillPolyF(innerArea, innerAreaColour); 673 } 674 if (!isFullyTransparentColor(colour)) { 675 for(int i = 0; i < len; i++) { 676 int index0 = i - 1; 677 int index1 = i; 678 int index2 = i + 1; 679 int index3 = i + 2; 680 if (index0 < 0) 681 index0 = cycled ? len - 1 : 0; 682 index2 %= len; // only can be if cycled 683 index3 %= len; // only can be if cycled 684 if (!cycled) { 685 if (index1 == len - 1) { 686 index0 = index1; 687 index2 = 0; 688 index3 = 0; 689 } else if (index1 == len - 2) { 690 index2 = len - 1; 691 index3 = len - 1; 692 } 693 } 694 //Log.d("lineSegment - outer ", index0, ", ", index1, ", ", index2, ", ", index3); 695 if (cycled || i + 1 < len) 696 drawLineSegmentF(points[index0], points[index1], points[index2], points[index3], width, colour); 697 } 698 } 699 } 700 701 /// draw filled polyline (vertexes must be in clockwise order) 702 void fillPolyF(PointF[] points, uint colour) { 703 if (points.length < 3) { 704 return; 705 } 706 if (points.length == 3) { 707 fillTriangleF(points[0], points[1], points[2], colour); 708 return; 709 } 710 PointF[] list = points.dup; 711 bool moved; 712 while (list.length > 3) { 713 moved = false; 714 for (int i = 0; i < list.length; i++) { 715 PointF p1 = list[i + 0]; 716 PointF p2 = list[(i + 1) % list.length]; 717 PointF p3 = list[(i + 2) % list.length]; 718 float cross = (p2 - p1).crossProduct(p3 - p2); 719 if (cross > 0) { 720 // draw triangle 721 fillTriangleF(p1, p2, p3, colour); 722 int indexToRemove = (i + 1) % (cast(int)list.length); 723 // remove triangle from poly 724 for (int j = indexToRemove; j + 1 < list.length; j++) 725 list[j] = list[j + 1]; 726 list.length = list.length - 1; 727 i += 2; 728 moved = true; 729 } 730 } 731 if (list.length == 3) { 732 fillTriangleF(list[0], list[1], list[2], colour); 733 break; 734 } 735 if (!moved) 736 break; 737 } 738 } 739 740 /// draw ellipse or filled ellipse 741 void drawEllipseF(float centerX, float centerY, float xRadius, float yRadius, float lineWidth, uint lineColor, uint fillColor = COLOR_TRANSPARENT) { 742 import std.math : sin, cos, PI; 743 if (xRadius < 0) 744 xRadius = -xRadius; 745 if (yRadius < 0) 746 yRadius = -yRadius; 747 int numLines = cast(int)((xRadius + yRadius) / 5); 748 if (numLines < 4) 749 numLines = 4; 750 float step = PI * 2 / numLines; 751 float angle = 0; 752 PointF[] points; 753 points.assumeSafeAppend; 754 for (int i = 0; i < numLines; i++) { 755 float x = centerX + cos(angle) * xRadius; 756 float y = centerY + sin(angle) * yRadius; 757 angle += step; 758 points ~= PointF(x, y); 759 } 760 polyLineF(points, lineWidth, lineColor, true, fillColor); 761 } 762 763 /// draw ellipse arc or filled ellipse arc 764 void drawEllipseArcF(float centerX, float centerY, float xRadius, float yRadius, float startAngle, float endAngle, float lineWidth, uint lineColor, uint fillColor = COLOR_TRANSPARENT) { 765 import std.math : sin, cos, PI; 766 if (xRadius < 0) 767 xRadius = -xRadius; 768 if (yRadius < 0) 769 yRadius = -yRadius; 770 startAngle = startAngle * 2 * PI / 360; 771 endAngle = endAngle * 2 * PI / 360; 772 if (endAngle < startAngle) 773 endAngle += 2 * PI; 774 float angleDiff = endAngle - startAngle; 775 if (angleDiff > 2*PI) 776 angleDiff %= 2*PI; 777 int numLines = cast(int)((xRadius + yRadius) / angleDiff); 778 if (numLines < 3) 779 numLines = 4; 780 float step = angleDiff / numLines; 781 float angle = startAngle; 782 PointF[] points; 783 points.assumeSafeAppend; 784 points ~= PointF(centerX, centerY); 785 for (int i = 0; i < numLines; i++) { 786 float x = centerX + cos(angle) * xRadius; 787 float y = centerY + sin(angle) * yRadius; 788 angle += step; 789 points ~= PointF(x, y); 790 } 791 polyLineF(points, lineWidth, lineColor, true, fillColor); 792 } 793 794 /// draw poly line of width == 1px; when cycled is true, connect first and last point 795 void polyLine(Point[] points, uint colour, bool cycled) { 796 if (points.length < 2) 797 return; 798 for(int i = 0; i + 1 < points.length; i++) { 799 drawLine(points[i], points[i + 1], colour); 800 } 801 if (cycled && points.length > 2) 802 drawLine(points[$ - 1], points[0], colour); 803 } 804 805 /// draw line from point p1 to p2 with specified color 806 void drawLine(Point p1, Point p2, uint colour) { 807 if (!clipLine(_clipRect, p1, p2)) 808 return; 809 // from rosettacode.org 810 import std.math: abs; 811 immutable int dx = p2.x - p1.x; 812 immutable int ix = (dx > 0) - (dx < 0); 813 immutable int dx2 = abs(dx) * 2; 814 int dy = p2.y - p1.y; 815 immutable int iy = (dy > 0) - (dy < 0); 816 immutable int dy2 = abs(dy) * 2; 817 drawPixel(p1.x, p1.y, colour); 818 if (dx2 >= dy2) { 819 int error = dy2 - (dx2 / 2); 820 while (p1.x != p2.x) { 821 if (error >= 0 && (error || (ix > 0))) { 822 error -= dx2; 823 p1.y += iy; 824 } 825 error += dy2; 826 p1.x += ix; 827 drawPixel(p1.x, p1.y, colour); 828 } 829 } else { 830 int error = dx2 - (dy2 / 2); 831 while (p1.y != p2.y) { 832 if (error >= 0 && (error || (iy > 0))) { 833 error -= dy2; 834 p1.x += ix; 835 } 836 error += dx2; 837 p1.y += iy; 838 drawPixel(p1.x, p1.y, colour); 839 } 840 } 841 } 842 843 // basically a modified drawEllipseArcF that doesn't draw but gives you the lines instead! 844 static PointF[] makeArcPath(PointF center, float radiusX, float radiusY, float startAngle, float endAngle) { 845 import std.math : sin, cos, PI, abs, sqrt; 846 radiusX = abs(radiusX); 847 radiusY = abs(radiusY); 848 startAngle = startAngle * 2 * PI / 360; 849 endAngle = endAngle * 2 * PI / 360; 850 if (endAngle < startAngle) 851 endAngle += 2 * PI; 852 float angleDiff = endAngle - startAngle; 853 if (angleDiff > 2*PI) 854 angleDiff %= 2*PI; 855 int numLines = cast(int)(sqrt(radiusX*radiusX + radiusY*radiusY) / angleDiff); 856 if (numLines < 3) 857 numLines = 4; 858 float step = angleDiff / numLines; 859 float angle = startAngle; 860 PointF[] points; 861 points.assumeSafeAppend; 862 if ( radiusX == 0 ) { 863 points ~= PointF(center.x, center.y); 864 } 865 else for (int i; i < numLines+1; i++) { 866 float x = center.x + cos(angle) * radiusX; 867 float y = center.y + sin(angle) * radiusY; 868 angle += step; 869 points ~= PointF(x, y); 870 } 871 return points; 872 } 873 874 // calculates inwards XY offsets from rect corners 875 static PointF[4] calcRectRoundedCornerRadius(vec4 corners, float w, float h, bool keepSquareXY) { 876 import std.algorithm.comparison : min; 877 // clamps radius to corner 878 static float clampRadius(float r, float len) pure @safe @nogc { 879 if (len - 2*r < 0) return len/2; 880 return r; 881 } 882 if (keepSquareXY) { 883 auto minSize = min(w, h); 884 w = h = minSize; 885 } 886 PointF[4] cornerRad; 887 cornerRad[0] = PointF( clampRadius(corners.x, w), clampRadius(corners.x, h) ); 888 cornerRad[1] = PointF( -clampRadius(corners.y, w), clampRadius(corners.y, h) ); 889 cornerRad[2] = PointF( clampRadius(corners.z, w), -clampRadius(corners.z, h) ); 890 cornerRad[3] = PointF( -clampRadius(corners.w, w), -clampRadius(corners.w, h) ); 891 return cornerRad; 892 } 893 894 /// builds outer rounded rect path 895 /// the last parameter optionally writes out indices of first segment of corners excluding top-left 896 static PointF[] makeRoundedRectPath(Rect rect, vec4 corners, bool keepSquareXY, size_t[] outCornerSegmentsStart = null) { 897 import std.range : chain; 898 import std.array : array; 899 PointF[4] cornerRad = calcRectRoundedCornerRadius(corners, rect.width, rect.height, keepSquareXY); 900 PointF topLeftC = PointF(rect.left, rect.top) + cornerRad[0]; 901 PointF topRightC = PointF(rect.left, rect.top) + cornerRad[1] + PointF(rect.width, 0); 902 PointF botLeftC = PointF(rect.left, rect.top) + cornerRad[2] + PointF(0, rect.height); 903 PointF botRightC = PointF(rect.left, rect.top) + cornerRad[3] + PointF(rect.width, rect.height); 904 auto lt = makeArcPath(topLeftC, cornerRad[0].x, cornerRad[0].y, 180, 270); 905 auto rt = makeArcPath(topRightC, cornerRad[1].x, cornerRad[1].y, 270, 0); 906 auto lb = makeArcPath(botLeftC, cornerRad[2].x, cornerRad[2].y, 90, 180); 907 auto rb = makeArcPath(botRightC, cornerRad[3].x, cornerRad[3].y, 0, 90); 908 if ( outCornerSegmentsStart.length ) { 909 outCornerSegmentsStart[0] = lt.length; 910 outCornerSegmentsStart[1] = lt.length + rt.length; 911 outCornerSegmentsStart[2] = lt.length + rt.length + lb.length; 912 } 913 auto outerPath = chain( lt, rt, rb, lb, lt[0..1] ); 914 return outerPath.array(); 915 } 916 917 /// draws rect with rounded corners 918 void drawRoundedRectF(Rect rect, vec4 corners, bool keepSquareXY, float frameWidth, uint frameColor, uint fillColor = COLOR_TRANSPARENT) { 919 auto fullPath = makeRoundedRectPath(rect, corners, keepSquareXY); 920 // fill inner area, doing this manually by sectors to reduce flickering artifacts 921 if ( fillColor != COLOR_TRANSPARENT ) { 922 PointF center = PointF(rect.middlex, rect.middley); 923 for( int i = 0; i < fullPath.length-1; i++ ) { 924 fillTriangleF(center, fullPath[i], fullPath[i+1], fillColor); 925 } 926 } 927 if (frameColor != COLOR_TRANSPARENT && frameWidth > 0) { 928 for( int i = 0; i < fullPath.length-1; i++ ) { 929 drawLineF(fullPath[i], fullPath[i+1], frameWidth, frameColor); 930 } 931 } 932 } 933 934 /// create drawbuf with copy of current buffer with changed colors (returns this if not supported) 935 DrawBuf transformColors(ref ColorTransform transform) { 936 return this; 937 } 938 939 /// draw custom OpenGL scene 940 void drawCustomOpenGLScene(Rect rc, OpenGLDrawableDelegate handler) { 941 // override it for OpenGL draw buffer 942 Log.w("drawCustomOpenGLScene is called for non-OpenGL DrawBuf"); 943 } 944 945 void clear() { 946 resetClipping(); 947 } 948 } 949 950 alias DrawBufRef = Ref!DrawBuf; 951 952 /// RAII setting/restoring of a DrawBuf clip rectangle 953 struct ClipRectSaver { 954 private DrawBuf _buf; 955 private Rect _oldClipRect; 956 private uint _oldAlpha; 957 958 /// apply (intersect) new clip rectangle and alpha to draw buf 959 /// set `intersect` parameter to `false`, if you want to draw something outside of the widget 960 this(DrawBuf buf, ref Rect newClipRect, uint newAlpha = 0, bool intersect = true) { 961 _buf = buf; 962 _oldClipRect = buf.clipRect; 963 _oldAlpha = buf.alpha; 964 if (intersect) 965 buf.intersectClipRect(newClipRect); 966 else 967 buf.clipRect = newClipRect; 968 if (newAlpha) 969 buf.addAlpha(newAlpha); 970 } 971 /// restore previous clip rectangle 972 ~this() { 973 _buf.clipRect = _oldClipRect; 974 _buf.alpha = _oldAlpha; 975 } 976 } 977 978 class ColorDrawBufBase : DrawBuf { 979 int _dx; 980 int _dy; 981 /// returns buffer bits per pixel 982 override @property int bpp() { return 32; } 983 @property override int width() { return _dx; } 984 @property override int height() { return _dy; } 985 986 /// returns pointer to ARGB scanline, null if y is out of range or buffer doesn't provide access to its memory 987 uint * scanLine(int y) { return null; } 988 989 /// draw source buffer rectangle contents to destination buffer 990 override void drawFragment(int x, int y, DrawBuf src, Rect srcrect) { 991 Rect dstrect = Rect(x, y, x + srcrect.width, y + srcrect.height); 992 if (applyClipping(dstrect, srcrect)) { 993 if (src.applyClipping(srcrect, dstrect)) { 994 int dx = srcrect.width; 995 int dy = srcrect.height; 996 ColorDrawBufBase colorDrawBuf = cast(ColorDrawBufBase) src; 997 if (colorDrawBuf !is null) { 998 foreach(yy; 0 .. dy) { 999 uint * srcrow = colorDrawBuf.scanLine(srcrect.top + yy) + srcrect.left; 1000 uint * dstrow = scanLine(dstrect.top + yy) + dstrect.left; 1001 if (!_alpha) { 1002 // simplified version - no alpha blending 1003 foreach(i; 0 .. dx) { 1004 uint pixel = srcrow[i]; 1005 uint alpha = pixel >> 24; 1006 if (!alpha) 1007 dstrow[i] = pixel; 1008 else if (alpha < 254) { 1009 // apply blending 1010 dstrow[i] = blendARGB(dstrow[i], pixel, alpha); 1011 } 1012 } 1013 } else { 1014 // combine two alphas 1015 foreach(i; 0 .. dx) { 1016 uint pixel = srcrow[i]; 1017 uint alpha = blendAlpha(_alpha, pixel >> 24); 1018 if (!alpha) 1019 dstrow[i] = pixel; 1020 else if (alpha < 254) { 1021 // apply blending 1022 dstrow[i] = blendARGB(dstrow[i], pixel, alpha); 1023 } 1024 } 1025 } 1026 1027 } 1028 } 1029 } 1030 } 1031 } 1032 1033 import std.container.array; 1034 1035 /// Create mapping of source coordinates to destination coordinates, for resize. 1036 private Array!int createMap(int dst0, int dst1, int src0, int src1, double k) { 1037 int dd = dst1 - dst0; 1038 //int sd = src1 - src0; 1039 Array!int res; 1040 res.length = dd; 1041 foreach(int i; 0 .. dd) 1042 res[i] = src0 + cast(int)(i * k);//sd / dd; 1043 return res; 1044 } 1045 1046 /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling 1047 override void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect) { 1048 //Log.d("drawRescaled ", dstrect, " <- ", srcrect); 1049 if (_alpha >= 254) 1050 return; // fully transparent - don't draw 1051 double kx = cast(double)srcrect.width / dstrect.width; 1052 double ky = cast(double)srcrect.height / dstrect.height; 1053 if (applyClipping(dstrect, srcrect)) { 1054 auto xmapArray = createMap(dstrect.left, dstrect.right, srcrect.left, srcrect.right, kx); 1055 auto ymapArray = createMap(dstrect.top, dstrect.bottom, srcrect.top, srcrect.bottom, ky); 1056 1057 int * xmap = &xmapArray[0]; 1058 int * ymap = &ymapArray[0]; 1059 int dx = dstrect.width; 1060 int dy = dstrect.height; 1061 ColorDrawBufBase colorDrawBuf = cast(ColorDrawBufBase) src; 1062 if (colorDrawBuf !is null) { 1063 foreach(y; 0 .. dy) { 1064 uint * srcrow = colorDrawBuf.scanLine(ymap[y]); 1065 uint * dstrow = scanLine(dstrect.top + y) + dstrect.left; 1066 if (!_alpha) { 1067 // simplified alpha calculation 1068 foreach(x; 0 .. dx) { 1069 uint srcpixel = srcrow[xmap[x]]; 1070 uint dstpixel = dstrow[x]; 1071 uint alpha = srcpixel >> 24; 1072 if (!alpha) 1073 dstrow[x] = srcpixel; 1074 else if (alpha < 255) { 1075 // apply blending 1076 dstrow[x] = blendARGB(dstpixel, srcpixel, alpha); 1077 } 1078 } 1079 } else { 1080 // blending two alphas 1081 foreach(x; 0 .. dx) { 1082 uint srcpixel = srcrow[xmap[x]]; 1083 uint dstpixel = dstrow[x]; 1084 uint srca = srcpixel >> 24; 1085 uint alpha = !srca ? _alpha : blendAlpha(_alpha, srca); 1086 if (!alpha) 1087 dstrow[x] = srcpixel; 1088 else if (alpha < 255) { 1089 // apply blending 1090 dstrow[x] = blendARGB(dstpixel, srcpixel, alpha); 1091 } 1092 } 1093 } 1094 } 1095 } 1096 } 1097 } 1098 1099 /// detect position of black pixels in row for 9-patch markup 1100 private bool detectHLine(int y, ref int x0, ref int x1) { 1101 uint * line = scanLine(y); 1102 bool foundUsed = false; 1103 x0 = 0; 1104 x1 = 0; 1105 foreach(int x; 1 .. _dx - 1) { 1106 if (isBlackPixel(line[x])) { // opaque black pixel 1107 if (!foundUsed) { 1108 x0 = x; 1109 foundUsed = true; 1110 } 1111 x1 = x + 1; 1112 } 1113 } 1114 return x1 > x0; 1115 } 1116 1117 static bool isBlackPixel(uint c) { 1118 if (((c >> 24) & 255) > 10) 1119 return false; 1120 if (((c >> 16) & 255) > 10) 1121 return false; 1122 if (((c >> 8) & 255) > 10) 1123 return false; 1124 if (((c >> 0) & 255) > 10) 1125 return false; 1126 return true; 1127 } 1128 1129 /// detect position of black pixels in column for 9-patch markup 1130 private bool detectVLine(int x, ref int y0, ref int y1) { 1131 bool foundUsed = false; 1132 y0 = 0; 1133 y1 = 0; 1134 foreach(int y; 1 .. _dy - 1) { 1135 uint * line = scanLine(y); 1136 if (isBlackPixel(line[x])) { // opaque black pixel 1137 if (!foundUsed) { 1138 y0 = y; 1139 foundUsed = true; 1140 } 1141 y1 = y + 1; 1142 } 1143 } 1144 return y1 > y0; 1145 } 1146 /// detect nine patch using image 1-pixel border (see Android documentation) 1147 override bool detectNinePatch() { 1148 if (_dx < 3 || _dy < 3) 1149 return false; // image is too small 1150 int x00, x01, x10, x11, y00, y01, y10, y11; 1151 bool found = true; 1152 found = found && detectHLine(0, x00, x01); 1153 found = found && detectHLine(_dy - 1, x10, x11); 1154 found = found && detectVLine(0, y00, y01); 1155 found = found && detectVLine(_dx - 1, y10, y11); 1156 if (!found) 1157 return false; // no black pixels on 1-pixel frame 1158 NinePatch * p = new NinePatch(); 1159 p.frame.left = x00 - 1; 1160 p.frame.right = _dx - x01 - 1; 1161 p.frame.top = y00 - 1; 1162 p.frame.bottom = _dy - y01 - 1; 1163 p.padding.left = x10 - 1; 1164 p.padding.right = _dx - x11 - 1; 1165 p.padding.top = y10 - 1; 1166 p.padding.bottom = _dy - y11 - 1; 1167 _ninePatch = p; 1168 //Log.d("NinePatch detected: frame=", p.frame, " padding=", p.padding, " left+right=", p.frame.left + p.frame.right, " dx=", _dx); 1169 return true; 1170 } 1171 1172 override void drawGlyph(int x, int y, Glyph * glyph, uint color) { 1173 ubyte[] src = glyph.glyph; 1174 int srcdx = glyph.blackBoxX; 1175 int srcdy = glyph.blackBoxY; 1176 bool clipping = true; //!_clipRect.empty(); 1177 color = applyAlpha(color); 1178 bool subpixel = glyph.subpixelMode != SubpixelRenderingMode.None; 1179 foreach(int yy; 0 .. srcdy) { 1180 int liney = y + yy; 1181 if (clipping && (liney < _clipRect.top || liney >= _clipRect.bottom)) 1182 continue; 1183 if (liney < 0 || liney >= _dy) 1184 continue; 1185 uint * row = scanLine(liney); 1186 ubyte * srcrow = src.ptr + yy * srcdx; 1187 foreach(int xx; 0 .. srcdx) { 1188 int colx = x + (subpixel ? xx / 3 : xx); 1189 if (clipping && (colx < _clipRect.left || colx >= _clipRect.right)) 1190 continue; 1191 if (colx < 0 || colx >= _dx) 1192 continue; 1193 uint alpha2 = (color >> 24); 1194 uint alpha1 = srcrow[xx] ^ 255; 1195 uint alpha = ((((alpha1 ^ 255) * (alpha2 ^ 255)) >> 8) ^ 255) & 255; 1196 if (subpixel) { 1197 int x0 = xx % 3; 1198 ubyte * dst = cast(ubyte*)(row + colx); 1199 ubyte * pcolor = cast(ubyte*)(&color); 1200 blendSubpixel(dst, pcolor, alpha, x0, glyph.subpixelMode); 1201 } else { 1202 uint pixel = row[colx]; 1203 if (alpha < 255) { 1204 if (!alpha) 1205 row[colx] = pixel; 1206 else { 1207 // apply blending 1208 row[colx] = blendARGB(pixel, color, alpha); 1209 } 1210 } 1211 } 1212 } 1213 } 1214 } 1215 1216 void drawGlyphToTexture(int x, int y, Glyph * glyph) { 1217 ubyte[] src = glyph.glyph; 1218 int srcdx = glyph.blackBoxX; 1219 int srcdy = glyph.blackBoxY; 1220 bool subpixel = glyph.subpixelMode != SubpixelRenderingMode.None; 1221 foreach(int yy; 0 .. srcdy) { 1222 int liney = y + yy; 1223 uint * row = scanLine(liney); 1224 ubyte * srcrow = src.ptr + yy * srcdx; 1225 int increment = subpixel ? 3 : 1; 1226 for (int xx = 0; xx <= srcdx - increment; xx += increment) { 1227 int colx = x + (subpixel ? xx / 3 : xx); 1228 if (subpixel) { 1229 uint t1 = srcrow[xx]; 1230 uint t2 = srcrow[xx + 1]; 1231 uint t3 = srcrow[xx + 2]; 1232 //uint pixel = ((t2 ^ 0x00) << 24) | ((t1 ^ 0xFF)<< 16) | ((t2 ^ 0xFF) << 8) | (t3 ^ 0xFF); 1233 uint pixel = ((t2 ^ 0x00) << 24) | 0xFFFFFF; 1234 row[colx] = pixel; 1235 } else { 1236 uint alpha1 = srcrow[xx] ^ 0xFF; 1237 //uint pixel = (alpha1 << 24) | 0xFFFFFF; //(alpha1 << 16) || (alpha1 << 8) || alpha1; 1238 //uint pixel = ((alpha1 ^ 0xFF) << 24) | (alpha1 << 16) | (alpha1 << 8) | alpha1; 1239 uint pixel = ((alpha1 ^ 0xFF) << 24) | 0xFFFFFF; 1240 row[colx] = pixel; 1241 } 1242 } 1243 } 1244 } 1245 1246 override void fillRect(Rect rc, uint color) { 1247 uint alpha = color >> 24; 1248 if (applyClipping(rc)) { 1249 foreach(y; rc.top .. rc.bottom) { 1250 uint * row = scanLine(y); 1251 if (!alpha) { 1252 row[rc.left .. rc.right] = color; 1253 } else if (alpha < 254) { 1254 foreach(x; rc.left .. rc.right) { 1255 // apply blending 1256 row[x] = blendARGB(row[x], color, alpha); 1257 } 1258 } 1259 } 1260 } 1261 } 1262 1263 /// fill rectangle with a gradient (clipping is applied) 1264 override void fillGradientRect(Rect rc, uint color1, uint color2, uint color3, uint color4) { 1265 if (applyClipping(rc)) { 1266 foreach (y; rc.top .. rc.bottom) { 1267 // interpolate vertically at the side edges 1268 uint ay = (255 * (y - rc.top)) / (rc.bottom - rc.top); 1269 uint cl = blendARGB(color2, color1, ay); 1270 uint cr = blendARGB(color4, color3, ay); 1271 1272 uint * row = scanLine(y); 1273 foreach (x; rc.left .. rc.right) { 1274 // interpolate horizontally 1275 uint ax = (255 * (x - rc.left)) / (rc.right - rc.left); 1276 row[x] = blendARGB(cr, cl, ax); 1277 } 1278 } 1279 } 1280 } 1281 1282 /// fill rectangle with solid color and pattern (clipping is applied) 0=solid fill, 1 = dotted 1283 override void fillRectPattern(Rect rc, uint color, int pattern) { 1284 uint alpha = color >> 24; 1285 if (alpha == 255) // fully transparent 1286 return; 1287 if (applyClipping(rc)) { 1288 foreach(y; rc.top .. rc.bottom) { 1289 uint * row = scanLine(y); 1290 if (!alpha) { 1291 if (pattern == 1) { 1292 foreach(x; rc.left .. rc.right) { 1293 if ((x ^ y) & 1) 1294 row[x] = color; 1295 } 1296 } else { 1297 row[rc.left .. rc.right] = color; 1298 } 1299 } else if (alpha < 254) { 1300 foreach(x; rc.left .. rc.right) { 1301 // apply blending 1302 if (pattern != 1 || ((x ^ y) & 1) != 0) 1303 row[x] = blendARGB(row[x], color, alpha); 1304 } 1305 } 1306 } 1307 } 1308 } 1309 1310 /// draw pixel at (x, y) with specified color 1311 override void drawPixel(int x, int y, uint color) { 1312 if (!_clipRect.isPointInside(x, y)) 1313 return; 1314 color = applyAlpha(color); 1315 uint * row = scanLine(y); 1316 uint alpha = color >> 24; 1317 if (!alpha) { 1318 row[x] = color; 1319 } else if (alpha < 254) { 1320 // apply blending 1321 row[x] = blendARGB(row[x], color, alpha); 1322 } 1323 } 1324 } 1325 1326 class GrayDrawBuf : DrawBuf { 1327 protected int _dx; 1328 protected int _dy; 1329 /// returns buffer bits per pixel 1330 override @property int bpp() { return 8; } 1331 @property override int width() { return _dx; } 1332 @property override int height() { return _dy; } 1333 1334 protected MallocBuf!ubyte _buf; 1335 1336 this(int width, int height) { 1337 resize(width, height); 1338 } 1339 ubyte * scanLine(int y) { 1340 if (y >= 0 && y < _dy) 1341 return _buf.ptr + _dx * y; 1342 return null; 1343 } 1344 override void resize(int width, int height) { 1345 if (_dx == width && _dy == height) 1346 return; 1347 _dx = width; 1348 _dy = height; 1349 _buf.length = _dx * _dy; 1350 resetClipping(); 1351 } 1352 override void fill(uint color) { 1353 if (hasClipping) { 1354 fillRect(_clipRect, color); 1355 return; 1356 } 1357 int len = _dx * _dy; 1358 ubyte * p = _buf.ptr; 1359 ubyte cl = rgbToGray(color); 1360 foreach(i; 0 .. len) 1361 p[i] = cl; 1362 } 1363 1364 /// draw source buffer rectangle contents to destination buffer 1365 override void drawFragment(int x, int y, DrawBuf src, Rect srcrect) { 1366 Rect dstrect = Rect(x, y, x + srcrect.width, y + srcrect.height); 1367 if (applyClipping(dstrect, srcrect)) { 1368 if (src.applyClipping(srcrect, dstrect)) { 1369 int dx = srcrect.width; 1370 int dy = srcrect.height; 1371 GrayDrawBuf grayDrawBuf = cast (GrayDrawBuf) src; 1372 if (grayDrawBuf !is null) { 1373 foreach(yy; 0 .. dy) { 1374 ubyte * srcrow = grayDrawBuf.scanLine(srcrect.top + yy) + srcrect.left; 1375 ubyte * dstrow = scanLine(dstrect.top + yy) + dstrect.left; 1376 foreach(i; 0 .. dx) { 1377 ubyte pixel = srcrow[i]; 1378 dstrow[i] = pixel; 1379 } 1380 } 1381 } 1382 } 1383 } 1384 } 1385 1386 /// Create mapping of source coordinates to destination coordinates, for resize. 1387 private int[] createMap(int dst0, int dst1, int src0, int src1) { 1388 int dd = dst1 - dst0; 1389 int sd = src1 - src0; 1390 int[] res = new int[dd]; 1391 foreach(int i; 0 .. dd) 1392 res[i] = src0 + i * sd / dd; 1393 return res; 1394 } 1395 /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling 1396 override void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect) { 1397 //Log.d("drawRescaled ", dstrect, " <- ", srcrect); 1398 if (applyClipping(dstrect, srcrect)) { 1399 int[] xmap = createMap(dstrect.left, dstrect.right, srcrect.left, srcrect.right); 1400 int[] ymap = createMap(dstrect.top, dstrect.bottom, srcrect.top, srcrect.bottom); 1401 int dx = dstrect.width; 1402 int dy = dstrect.height; 1403 GrayDrawBuf grayDrawBuf = cast (GrayDrawBuf) src; 1404 if (grayDrawBuf !is null) { 1405 foreach(y; 0 .. dy) { 1406 ubyte * srcrow = grayDrawBuf.scanLine(ymap[y]); 1407 ubyte * dstrow = scanLine(dstrect.top + y) + dstrect.left; 1408 foreach(x; 0 .. dx) { 1409 ubyte srcpixel = srcrow[xmap[x]]; 1410 ubyte dstpixel = dstrow[x]; 1411 dstrow[x] = srcpixel; 1412 } 1413 } 1414 } 1415 } 1416 } 1417 1418 /// detect position of black pixels in row for 9-patch markup 1419 private bool detectHLine(int y, ref int x0, ref int x1) { 1420 ubyte * line = scanLine(y); 1421 bool foundUsed = false; 1422 x0 = 0; 1423 x1 = 0; 1424 foreach(int x; 1 .. _dx - 1) { 1425 if (line[x] == 0x00000000) { // opaque black pixel 1426 if (!foundUsed) { 1427 x0 = x; 1428 foundUsed = true; 1429 } 1430 x1 = x + 1; 1431 } 1432 } 1433 return x1 > x0; 1434 } 1435 1436 /// detect position of black pixels in column for 9-patch markup 1437 private bool detectVLine(int x, ref int y0, ref int y1) { 1438 bool foundUsed = false; 1439 y0 = 0; 1440 y1 = 0; 1441 foreach(int y; 1 .. _dy - 1) { 1442 ubyte * line = scanLine(y); 1443 if (line[x] == 0x00000000) { // opaque black pixel 1444 if (!foundUsed) { 1445 y0 = y; 1446 foundUsed = true; 1447 } 1448 y1 = y + 1; 1449 } 1450 } 1451 return y1 > y0; 1452 } 1453 /// detect nine patch using image 1-pixel border (see Android documentation) 1454 override bool detectNinePatch() { 1455 if (_dx < 3 || _dy < 3) 1456 return false; // image is too small 1457 int x00, x01, x10, x11, y00, y01, y10, y11; 1458 bool found = true; 1459 found = found && detectHLine(0, x00, x01); 1460 found = found && detectHLine(_dy - 1, x10, x11); 1461 found = found && detectVLine(0, y00, y01); 1462 found = found && detectVLine(_dx - 1, y10, y11); 1463 if (!found) 1464 return false; // no black pixels on 1-pixel frame 1465 NinePatch * p = new NinePatch(); 1466 p.frame.left = x00 - 1; 1467 p.frame.right = _dy - y01 - 1; 1468 p.frame.top = y00 - 1; 1469 p.frame.bottom = _dy - y01 - 1; 1470 p.padding.left = x10 - 1; 1471 p.padding.right = _dy - y11 - 1; 1472 p.padding.top = y10 - 1; 1473 p.padding.bottom = _dy - y11 - 1; 1474 _ninePatch = p; 1475 return true; 1476 } 1477 override void drawGlyph(int x, int y, Glyph * glyph, uint color) { 1478 ubyte[] src = glyph.glyph; 1479 int srcdx = glyph.blackBoxX; 1480 int srcdy = glyph.blackBoxY; 1481 bool clipping = true; //!_clipRect.empty(); 1482 foreach(int yy; 0 .. srcdy) { 1483 int liney = y + yy; 1484 if (clipping && (liney < _clipRect.top || liney >= _clipRect.bottom)) 1485 continue; 1486 if (liney < 0 || liney >= _dy) 1487 continue; 1488 ubyte * row = scanLine(liney); 1489 ubyte * srcrow = src.ptr + yy * srcdx; 1490 foreach(int xx; 0 .. srcdx) { 1491 int colx = xx + x; 1492 if (clipping && (colx < _clipRect.left || colx >= _clipRect.right)) 1493 continue; 1494 if (colx < 0 || colx >= _dx) 1495 continue; 1496 uint alpha1 = srcrow[xx] ^ 255; 1497 uint alpha2 = (color >> 24); 1498 uint alpha = ((((alpha1 ^ 255) * (alpha2 ^ 255)) >> 8) ^ 255) & 255; 1499 uint pixel = row[colx]; 1500 if (!alpha) 1501 row[colx] = cast(ubyte)pixel; 1502 else if (alpha < 255) { 1503 // apply blending 1504 row[colx] = cast(ubyte)blendARGB(pixel, color, alpha); 1505 } 1506 } 1507 } 1508 } 1509 override void fillRect(Rect rc, uint color) { 1510 if (applyClipping(rc)) { 1511 uint alpha = color >> 24; 1512 ubyte cl = rgbToGray(color); 1513 foreach(y; rc.top .. rc.bottom) { 1514 ubyte * row = scanLine(y); 1515 foreach(x; rc.left .. rc.right) { 1516 if (!alpha) 1517 row[x] = cl; 1518 else if (alpha < 255) { 1519 // apply blending 1520 row[x] = blendGray(row[x], cl, alpha); 1521 } 1522 } 1523 } 1524 } 1525 } 1526 1527 /// fill rectangle with a gradient (clipping is applied) 1528 override void fillGradientRect(Rect rc, uint color1, uint color2, uint color3, uint color4) { 1529 if (applyClipping(rc)) { 1530 ubyte c1 = rgbToGray(color1); 1531 ubyte c2 = rgbToGray(color2); 1532 ubyte c3 = rgbToGray(color3); 1533 ubyte c4 = rgbToGray(color4); 1534 foreach (y; rc.top .. rc.bottom) { 1535 // interpolate vertically at the side edges 1536 uint ay = (255 * (y - rc.top)) / (rc.bottom - rc.top); 1537 ubyte cl = blendGray(c2, c1, ay); 1538 ubyte cr = blendGray(c4, c3, ay); 1539 1540 ubyte * row = scanLine(y); 1541 foreach (x; rc.left .. rc.right) { 1542 // interpolate horizontally 1543 uint ax = (255 * (x - rc.left)) / (rc.right - rc.left); 1544 row[x] = blendGray(cr, cl, ax); 1545 } 1546 } 1547 } 1548 } 1549 1550 /// draw pixel at (x, y) with specified color 1551 override void drawPixel(int x, int y, uint color) { 1552 if (!_clipRect.isPointInside(x, y)) 1553 return; 1554 color = applyAlpha(color); 1555 ubyte cl = rgbToGray(color); 1556 ubyte * row = scanLine(y); 1557 uint alpha = color >> 24; 1558 if (!alpha) { 1559 row[x] = cl; 1560 } else if (alpha < 254) { 1561 // apply blending 1562 row[x] = blendGray(row[x], cl, alpha); 1563 } 1564 } 1565 } 1566 1567 class ColorDrawBuf : ColorDrawBufBase { 1568 protected MallocBuf!uint _buf; 1569 1570 /// create ARGB8888 draw buf of specified width and height 1571 this(int width, int height) { 1572 resize(width, height); 1573 } 1574 /// create copy of ColorDrawBuf 1575 this(ColorDrawBuf v) { 1576 this(v.width, v.height); 1577 if (auto len = _buf.length) 1578 _buf.ptr[0 .. len] = v._buf.ptr[0 .. len]; 1579 } 1580 /// create resized copy of ColorDrawBuf 1581 this(ColorDrawBuf v, int dx, int dy) { 1582 this(dx, dy); 1583 fill(0xFFFFFFFF); 1584 drawRescaled(Rect(0, 0, dx, dy), v, Rect(0, 0, v.width, v.height)); 1585 } 1586 1587 void invertAndPreMultiplyAlpha() { 1588 foreach(ref pixel; _buf[]) { 1589 uint a = (pixel >> 24) & 0xFF; 1590 uint r = (pixel >> 16) & 0xFF; 1591 uint g = (pixel >> 8) & 0xFF; 1592 uint b = (pixel >> 0) & 0xFF; 1593 a ^= 0xFF; 1594 if (a > 0xFC) { 1595 r = ((r * a) >> 8) & 0xFF; 1596 g = ((g * a) >> 8) & 0xFF; 1597 b = ((b * a) >> 8) & 0xFF; 1598 } 1599 pixel = (a << 24) | (r << 16) | (g << 8) | (b << 0); 1600 } 1601 } 1602 1603 void invertAlpha() { 1604 foreach(ref pixel; _buf[]) 1605 pixel ^= 0xFF000000; 1606 } 1607 1608 void invertByteOrder() { 1609 foreach(ref pixel; _buf[]) { 1610 pixel = (pixel & 0xFF00FF00) | 1611 ((pixel & 0xFF0000) >> 16) | 1612 ((pixel & 0xFF) << 16); 1613 } 1614 } 1615 1616 // for passing of image to OpenGL texture 1617 void invertAlphaAndByteOrder() { 1618 foreach(ref pixel; _buf[]) { 1619 pixel = ((pixel & 0xFF00FF00) | 1620 ((pixel & 0xFF0000) >> 16) | 1621 ((pixel & 0xFF) << 16)); 1622 pixel ^= 0xFF000000; 1623 } 1624 } 1625 1626 override uint * scanLine(int y) { 1627 if (y >= 0 && y < _dy) 1628 return _buf.ptr + _dx * y; 1629 return null; 1630 } 1631 1632 override void resize(int width, int height) { 1633 if (_dx == width && _dy == height) 1634 return; 1635 _dx = width; 1636 _dy = height; 1637 _buf.length = _dx * _dy; 1638 resetClipping(); 1639 } 1640 1641 override void fill(uint color) { 1642 if (hasClipping) { 1643 fillRect(_clipRect, color); 1644 return; 1645 } 1646 int len = _dx * _dy; 1647 uint * p = _buf.ptr; 1648 foreach(i; 0 .. len) 1649 p[i] = color; 1650 } 1651 1652 override DrawBuf transformColors(ref ColorTransform transform) { 1653 if (transform.empty) 1654 return this; 1655 bool skipFrame = hasNinePatch; 1656 ColorDrawBuf res = new ColorDrawBuf(_dx, _dy); 1657 if (hasNinePatch) { 1658 NinePatch * p = new NinePatch; 1659 *p = *_ninePatch; 1660 res.ninePatch = p; 1661 } 1662 foreach(int y; 0 .. _dy) { 1663 uint * srcline = scanLine(y); 1664 uint * dstline = res.scanLine(y); 1665 bool allowTransformY = !skipFrame || (y !=0 && y != _dy - 1); 1666 foreach(int x; 0 .. _dx) { 1667 bool allowTransformX = !skipFrame || (x !=0 && x != _dx - 1); 1668 if (!allowTransformX || !allowTransformY) 1669 dstline[x] = srcline[x]; 1670 else 1671 dstline[x] = transform.transform(srcline[x]); 1672 } 1673 } 1674 return res; 1675 } 1676 1677 /// Apply Gaussian blur on the image 1678 void blur(uint blurSize) { 1679 if (blurSize == 0) 1680 return; // trivial case 1681 1682 // utility functions to get and set color 1683 float[4] get(uint[] buf, uint x, uint y) { 1684 uint c = buf[x + y * _dx]; 1685 float a = 255 - (c >> 24); 1686 float r = (c >> 16) & 0xFF; 1687 float g = (c >> 8) & 0xFF; 1688 float b = (c >> 0) & 0xFF; 1689 return [r, g, b, a]; 1690 } 1691 void set(uint[] buf, uint x, uint y, float[4] c) { 1692 buf[x + y * _dx] = makeRGBA(c[0], c[1], c[2], 255 - c[3]); 1693 } 1694 1695 1696 import std.algorithm : max, min; 1697 import std.math; 1698 1699 // Gaussian function 1700 float weight(in float x, in float sigma) pure nothrow { 1701 enum inv_sqrt_2pi = 1 / sqrt(2 * PI); 1702 return exp(- x ^^ 2 / (2 * sigma ^^ 2)) * inv_sqrt_2pi / sigma; 1703 } 1704 1705 void blurOneDimension(uint[] bufIn, uint[] bufOut, uint blurSize, bool horizontally) { 1706 1707 float sigma = blurSize > 2 ? blurSize / 3.0 : blurSize / 2.0; 1708 1709 foreach (x; 0 .. _dx) { 1710 foreach (y; 0 .. _dy) { 1711 float[4] c; 1712 c[] = 0; 1713 1714 float sum = 0; 1715 foreach (int i; 1 .. blurSize + 1) { 1716 float[4] c1 = get(bufIn, 1717 horizontally ? min(x + i, _dx - 1) : x, 1718 horizontally ? y : min(y + i, _dy - 1) 1719 ); 1720 float[4] c2 = get(bufIn, 1721 horizontally ? max(x - i, 0) : x, 1722 horizontally ? y : max(y - i, 0) 1723 ); 1724 float w = weight(i, sigma); 1725 c[] += (c1[] + c2[]) * w; 1726 sum += 2 * w; 1727 } 1728 c[] += get(bufIn, x, y)[] * (1 - sum); 1729 set(bufOut, x, y, c); 1730 } 1731 } 1732 } 1733 // intermediate buffer for image 1734 uint[] tmpbuf; 1735 tmpbuf.length = _buf.length; 1736 // do horizontal blur 1737 blurOneDimension(_buf[], tmpbuf, blurSize, true); 1738 // then do vertical blur 1739 blurOneDimension(tmpbuf, _buf[], blurSize, false); 1740 } 1741 } 1742 1743 1744 // line clipping algorithm from https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm 1745 private alias OutCode = int; 1746 1747 private const int INSIDE = 0; // 0000 1748 private const int LEFT = 1; // 0001 1749 private const int RIGHT = 2; // 0010 1750 private const int BOTTOM = 4; // 0100 1751 private const int TOP = 8; // 1000 1752 1753 // Compute the bit code for a point (x, y) using the clip rectangle 1754 // bounded diagonally by (xmin, ymin), and (xmax, ymax) 1755 1756 // ASSUME THAT xmax, xmin, ymax and ymin are global constants. 1757 1758 private OutCode ComputeOutCode(Rect clipRect, double x, double y) 1759 { 1760 OutCode code; 1761 1762 code = INSIDE; // initialised as being inside of clip window 1763 1764 if (x < clipRect.left) // to the left of clip window 1765 code |= LEFT; 1766 else if (x > clipRect.right) // to the right of clip window 1767 code |= RIGHT; 1768 if (y < clipRect.top) // below the clip window 1769 code |= BOTTOM; 1770 else if (y > clipRect.bottom) // above the clip window 1771 code |= TOP; 1772 1773 return code; 1774 } 1775 1776 package bool clipLine(ref Rect clipRect, ref Point p1, ref Point p2) { 1777 double x0 = p1.x; 1778 double y0 = p1.y; 1779 double x1 = p2.x; 1780 double y1 = p2.y; 1781 bool res = CohenSutherlandLineClipAndDraw(clipRect, x0, y0, x1, y1); 1782 if (res) { 1783 p1.x = cast(int)x0; 1784 p1.y = cast(int)y0; 1785 p2.x = cast(int)x1; 1786 p2.y = cast(int)y1; 1787 } 1788 return res; 1789 } 1790 1791 // Cohen–Sutherland clipping algorithm clips a line from 1792 // P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with 1793 // diagonal from (xmin, ymin) to (xmax, ymax). 1794 private bool CohenSutherlandLineClipAndDraw(ref Rect clipRect, ref double x0, ref double y0, ref double x1, ref double y1) 1795 { 1796 // compute outcodes for P0, P1, and whatever point lies outside the clip rectangle 1797 OutCode outcode0 = ComputeOutCode(clipRect, x0, y0); 1798 OutCode outcode1 = ComputeOutCode(clipRect, x1, y1); 1799 bool accept = false; 1800 1801 while (true) { 1802 if (!(outcode0 | outcode1)) { // Bitwise OR is 0. Trivially accept and get out of loop 1803 accept = true; 1804 break; 1805 } else if (outcode0 & outcode1) { // Bitwise AND is not 0. Trivially reject and get out of loop 1806 break; 1807 } else { 1808 // failed both tests, so calculate the line segment to clip 1809 // from an outside point to an intersection with clip edge 1810 double x, y; 1811 1812 // At least one endpoint is outside the clip rectangle; pick it. 1813 OutCode outcodeOut = outcode0 ? outcode0 : outcode1; 1814 1815 // Now find the intersection point; 1816 // use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0) 1817 if (outcodeOut & TOP) { // point is above the clip rectangle 1818 x = x0 + (x1 - x0) * (clipRect.bottom - y0) / (y1 - y0); 1819 y = clipRect.bottom; 1820 } else if (outcodeOut & BOTTOM) { // point is below the clip rectangle 1821 x = x0 + (x1 - x0) * (clipRect.top - y0) / (y1 - y0); 1822 y = clipRect.top; 1823 } else if (outcodeOut & RIGHT) { // point is to the right of clip rectangle 1824 y = y0 + (y1 - y0) * (clipRect.right - x0) / (x1 - x0); 1825 x = clipRect.right; 1826 } else if (outcodeOut & LEFT) { // point is to the left of clip rectangle 1827 y = y0 + (y1 - y0) * (clipRect.left - x0) / (x1 - x0); 1828 x = clipRect.left; 1829 } 1830 1831 // Now we move outside point to intersection point to clip 1832 // and get ready for next pass. 1833 if (outcodeOut == outcode0) { 1834 x0 = x; 1835 y0 = y; 1836 outcode0 = ComputeOutCode(clipRect, x0, y0); 1837 } else { 1838 x1 = x; 1839 y1 = y; 1840 outcode1 = ComputeOutCode(clipRect, x1, y1); 1841 } 1842 } 1843 } 1844 return accept; 1845 }