1 /* 2 canvas_ity v1.00 -- ISC license 3 Copyright (c) 2022 Andrew Kensler 4 Copyright (c) 2024 Guillaume Piolat - translation to D. 5 6 Permission to use, copy, modify, and/or distribute this software 7 for any purpose with or without fee is hereby granted, provided 8 that the above copyright notice and this permission notice appear 9 in all copies. 10 11 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 12 WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 13 WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 14 AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR 15 CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 16 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 17 NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 18 CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 20 21 ======== ABOUT ======== 22 23 This is a tiny D library for rasterizing immediate-mode 2D vector 24 graphics, closely modeled on the basic W3C (not WHATWG) HTML5 2D 25 canvas specification: 26 https://www.w3.org/TR/2015/REC-2dcontext-20151119/ 27 28 The priorities for this library are high-quality rendering, ease 29 of use, and compact size. Speed is important too, but secondary to 30 the other priorities. Notably, this library takes an opinionated 31 approach and does not provide options for trading off quality for 32 speed. 33 34 Despite its small size, it supports nearly everything listed in 35 the W3C HTML5 2D canvas specification, except for hit regions and 36 getting certain properties. 37 38 The main differences lie in the surface-level API to make this 39 easier for C++ use, while the underlying implementation is 40 carefully based on the specification. 41 42 In particular: stroke, fill, gradient, pattern, image, and font 43 styles are specified slightly differently (avoiding strings and 44 auxiliary classes). Nonetheless, the goal is that this library 45 could produce a conforming HTML5 2D canvas implementation if 46 wrapped in a thin layer of JavaScript bindings. 47 48 See the accompanying C++ automated test suite and its HTML5 port 49 for a mapping between the APIs and a comparison of this library's 50 rendering output against browser canvas implementations. 51 Original link: https://github.com/a-e-k/canvas_ity 52 53 54 ======== FEATURES ======== 55 56 High-quality rendering: 57 58 - Trapezoidal area antialiasing provides very smooth antialiasing, 59 even when lines are nearly horizontal or vertical. 60 61 - Gamma-correct blending, interpolation, and resampling are used 62 throughout. It linearizes all colors and premultiplies alpha on 63 input and converts back to unpremultiplied sRGB on output. This 64 reduces muddiness on many gradients (e.g., red to green), makes 65 line thicknesses more perceptually uniform, and avoids dark 66 fringes when interpolating opacity. 67 68 - Bicubic convolution resampling is used whenever it needs to 69 resample a pattern or image. This smoothly interpolates with 70 less blockiness when magnifying, and antialiases well when 71 minifying. It can simultaneously magnify and minify along 72 different axes. 73 74 - Ordered dithering is used on output. This reduces banding on 75 subtle gradients while still being compression-friendly. 76 77 - High curvature is handled carefully in line joins. Thick lines 78 are drawn correctly as though tracing with a wide pen nib, even 79 where the lines curve sharply. (Simpler curve offsetting 80 approaches tend to show bite-like artifacts in these cases.) 81 82 - Uses no static or global variables. Threads may safely work with 83 different canvas instances concurrently without locking. 84 85 86 ======== LIMITATIONS ======== 87 88 - Trapezoidal antialiasing overestimates coverage where paths self- 89 intersect within a single pixel. Where inner joins are visible, 90 this can lead to a "grittier" appearance due to the extra 91 windings used. 92 93 - Clipping uses an antialiased sparse pixel mask rather than 94 geometrically intersecting paths. Therefore, it is not 95 subpixel-accurate. 96 97 - Text rendering is extremely basic and mainly for convenience. It 98 only supports left-to-right text, and does not do any hinting, 99 kerning, ligatures, text shaping, or text layout. If you require 100 any of those, consider using another library to provide those and 101 feed the results to this library as either placed glyphs or raw 102 paths. 103 104 - TRUETYPE FONT PARSING IS NOT SECURE! It does some basic validity 105 checking, but should only be used with known-good or sanitized 106 fonts. 107 108 - Parameter checking does not test for non-finite floating-point 109 values. 110 111 - Rendering is single-threaded, not explicitly vectorized, and not 112 GPU-accelerated. It also copies data to avoid ownership issues. 113 If you need the speed, you are better off using a more 114 fully-featured library. 115 116 - The library does no input or output on its own. Instead, you must 117 provide it with buffers to copy into or out of. 118 119 ======== USAGE ======== 120 121 1. Construct an instance of `Canvasity` with a iven image buffer. 122 2. Use public methods of `Canvasity`. 123 */ 124 /// D Port of canvas_ity.h 125 /// Modifications: 126 /// * some SIMD 127 /// * buffer is passed and external instead of owned, it can be any 128 /// format 129 /// * removal of text, gradients, patterns, TODO add them back 130 /// * integration with `gamut` and `colors` 131 module canvasity; 132 133 nothrow @nogc: 134 135 import core.stdc.stdlib: malloc, free; 136 import core.stdc.math: cosf, sinf, tanf, floorf, fmodf, roundf, 137 sqrtf, acosf, ceilf, atan2f; 138 import core.stdc.string: memset, memcpy; 139 import dplug.core.vec; 140 import dplug.core.nogc; 141 import gamut; 142 import colors; 143 import inteli.emmintrin; 144 import inteli.math; 145 146 // Public API enums 147 148 /** 149 Compositing operation for blending new drawing and old pixels. 150 The sourceCopy, sourceIn, sourceOut, destinationAtop, and 151 destinationIn operations may clear parts of the canvas outside 152 the new drawing but within the clip region. Defaults to: 153 `sourceOver`. 154 */ 155 enum CanvasCompositeOp { 156 sourceIn = 1, /// Replace old with new where old was opaque. 157 sourceCopy, /// Replace old with new. 158 sourceOut, /// Replace old with new where old is transparent. 159 destinationIn, /// Clear old where new is transparent. 160 destinationAtop = 7, /// Show old over new where new is opaque. 161 add = 10, /// Sum old with new. 162 lighter = 10, /// ditto 163 destinationOver,/// Show new under old. 164 destinationOut, /// Clear old where new is opaque. 165 sourceAtop, /// Show new over old where old is opaque. 166 sourceOver, /// Show new over old. 167 exclusiveOr /// Show new and old, clear where both are opaque. 168 } 169 170 /** 171 The shape used to draw the end points of lines. 172 173 The actual shape may be affected by the current transform at the 174 time of drawing. Only affects stroking. 175 176 Defaults to `LineCap.butt`. 177 */ 178 enum CanvasLineCap { 179 180 butt, /// Use a flat cap flush to the end of the line. 181 square, /// Use a half-square cap that extends past the end of the 182 /// line. 183 circle /// Use a semicircular cap. 184 } 185 186 /** 187 The shape used to join two line segments where they meet. 188 189 The actual shape may be affected by the current transform at the 190 time of drawing. Only affects stroking. 191 192 Defaults to `LineJoin.miter`. 193 */ 194 enum CanvasLineJoin { 195 196 miter, /// Continue the ends until they intersect, if within miter 197 /// limit. 198 bevel, /// Connect the ends with a flat triangle. 199 round /// Join the ends with a circular arc. 200 } 201 202 203 // TODO comment 204 private enum repetition_style { 205 206 repeat, 207 repeat_x, 208 repeat_y, 209 no_repeat 210 } 211 212 /** 213 Horizontal position of the text relative to the anchor point. 214 215 When drawing text, the positioning of the text relative to the 216 anchor point includes the side bearings of the first and last 217 glyphs. 218 Defaults to leftward. 219 */ 220 private enum align_style { // TODO rename and provide API to be like in HTML 221 222 leftward, /// Draw the text's left edge at the anchor point. 223 rightward, /// Draw the text's right edge at the anchor point. 224 center, /// Draw the text's horizontal center at the anchor 225 /// point. 226 start = 0, /// This is a synonym for leftward. 227 ending /// This is a synonym for rightward. 228 } 229 230 231 /** 232 Vertical position of the text relative to the anchor point. 233 234 Defaults to alphabetic. 235 */ 236 private enum baseline_style { // TODO rename and provide API to be like in HTML 237 238 alphabetic, /// Alphabetic baseline as the anchor point. 239 top, /// Top of the em box as the anchor point. 240 middle, /// Exact middle of the em box as the anchor point. 241 bottom, /// Bottom of the em box as the anchor point. 242 hanging, /// Draw 60% of an em over the baseline at the anchor 243 /// point. (??? Not what I remember from printed:font) 244 ideographic = 3 /// This is a synonym for bottom. 245 } 246 247 /** 248 The gamma space where colors are manipulated. 249 Proper sRGB conversion to linear and back is very expensive. 250 251 Note: alpha itself is always kept as is and considered linear. 252 */ 253 enum CanvasGammaCurve { 254 255 none, /// Colors are blended in storage space 256 /// without gamma-conversion beyond going float(fastest). 257 /// It can look way worse than the other modes. 258 259 pow2, /// Colors are squared/sqrt to fake linear space. 260 /// without accounting for sRGB linear part. 261 /// Essentially nearly the quality of linear at much 262 /// cheaper cost. 263 264 linear, /// Colors converted to linear space before blend (slow). 265 } 266 267 /** 268 Options you can give while initializing a `Canvasity`. 269 */ 270 struct CanvasOptions { 271 272 /// Default: medium quality. 273 CanvasGammaCurve gammaCurve = CanvasGammaCurve.pow2; 274 } 275 276 277 /** 278 Main canvas API. 279 Given an image buffer, a `Canvasity` can draw 2D shapes without 280 any GPU usage. 281 */ 282 struct Canvasity { 283 284 nothrow @nogc: 285 public: 286 287 288 // ======== LIFECYCLE ======== 289 290 /** 291 Construct a new canvas. Reset to default state. 292 293 The buffer is provided externally and will NOT be cleared to 294 transparent black. It MUST outlive the Canvasity. 295 296 Initially, the visible coordinates will run from (0,0) in the 297 upper-left to (width, height) in the lower-right and with 298 pixel centers offset (0.5, 0.5) from the integer grid, though 299 all this may be changed by transforms. 300 301 The sizes must be between 1 and 32768, inclusive. 302 303 Note: internal allocated memory is reused if you keep 304 reusing the same instance with `.initialize`. 305 306 Params: 307 buffer Buffer to use as output. Not cleared on init. 308 options Creation options. 309 310 Warning: this does NOT clear the image. If you reuse the same 311 Canvasity struct, all allocations will be reused eventually, 312 leading to zero allocation per frame. 313 */ 314 this(ref Image buffer, 315 CanvasOptions options = CanvasOptions.init) { 316 317 initialize(buffer, options); 318 } 319 ///ditto 320 void initialize(ref Image buffer, 321 CanvasOptions options = CanvasOptions.init) { 322 323 initialized = true; 324 325 // Initialize as reference. 326 outBitmap = buffer.layer(0); 327 328 this.options = options; 329 this.size_x = outBitmap.width(); 330 this.size_y = outBitmap.height(); 331 332 // Initialize state 333 if (_state is null) { 334 size_t stackSize = (maxSaveRestoreDepth + 1); 335 size_t stateBytes = State.sizeof * stackSize; 336 _state = cast(State*) malloc(stateBytes); 337 338 // Initialize all stack items with State.init 339 for (size_t n = 0; n < stackSize; ++n) { 340 State i; 341 memcpy(&_state[n], &i, State.sizeof); 342 } 343 } 344 else 345 { 346 // Reset state[0] to State.init, without overriding the Vec 347 // allocations 348 { 349 State initial; 350 _state[0] = initial; 351 } 352 } 353 354 // Reset stack, some allocation will linger if canvas reused. 355 _stateCount = 1; 356 357 fillStyle("black"); 358 strokeStyle("black"); 359 360 // Initialize clipping state 361 // PERF: create that lazily? 362 363 State* curr = current; 364 ushort sz_x = cast(ushort)size_x; 365 for ( ushort y = 0; y < size_y; ++y ) { 366 pixel_run piece_1 = pixel_run( 0, y, 1.0f); 367 pixel_run piece_2 = pixel_run(sz_x, y, -1.0f); 368 curr.mask.pushBack(piece_1); 369 curr.mask.pushBack(piece_2); 370 } 371 372 interType = scanlinesInterType(outBitmap.type, PixelType.rgbaf32); 373 scanBuf.resize(buffer.width * pixelTypeSize(PixelType.rgbaf32)); 374 interBuf.resize(buffer.width * pixelTypeSize(interType)); 375 } 376 377 ~this() { 378 379 if (!initialized) 380 return; 381 382 // free each State 383 size_t stackSize = (maxSaveRestoreDepth + 1); 384 for (size_t n = 0; n < stackSize; ++n) { 385 _state[n].free(); 386 } 387 388 free( _state); 389 } 390 391 392 // ======== TRANSFORMS ======== 393 394 // document things that are part of the state stack, and saved 395 // by the save/restore sequence. 396 enum { savedBySaveRestore } 397 398 399 /** 400 Scale the current transform. 401 402 Negative scaling factors will flip or mirror it in that 403 direction. The scaling factors must be non-zero. 404 If either is zero, most drawing operations will do nothing. 405 406 Params: 407 x = Horizontal scaling factor. 408 y = Vertical scaling factor. 409 */ 410 @savedBySaveRestore 411 void scale(float x, float y) { 412 transform(x, 0, 0, y, 0, 0); 413 } 414 415 416 /** 417 Rotate the current transform. 418 419 The rotation is applied clockwise in a direction around the 420 origin. 421 422 Note: To rotate around another point, first translate that 423 point to the origin, then do the rotation, and then 424 translate back. 425 426 Params: 427 angle = Clockwise angle in radians. 428 */ 429 @savedBySaveRestore 430 void rotate(float angle) { 431 float cosine = cosf(angle); 432 float sine = sinf(angle); 433 transform(cosine, sine, -sine, cosine, 0, 0); 434 } 435 436 437 /** 438 Translate the current transform. 439 440 By default, positive x values shift that many pixels to the 441 right, while negative y values shift left, positive y values 442 shift up, and negative y values shift down. 443 444 Params: 445 x = Amount to shift horizontally. 446 y = Amount to shift vertically. 447 */ 448 @savedBySaveRestore 449 void translate(float x, float y) { 450 transform(1, 0, 0, 1, x, y); 451 } 452 453 454 /** 455 Add an arbitrary transform to the current transform. 456 457 This takes six values for the upper two rows of a homogenous 458 3x3 matrix (i.e., {{a, c, e}, {b, d, f}, {0.0, 0.0, 1.0}}) 459 describing an arbitrary affine transform and appends it to 460 the current transform. The values can represent any affine 461 transform such as scaling, rotation, translation, or skew, 462 or any composition of affine transforms. 463 The matrix must be invertible. If it is not, most drawing 464 operations will do nothing. 465 466 Params: 467 a = Horizontal scaling factor (m11). 468 b = Vertical skewing (m12). 469 c = Horizontal skewing (m21). 470 d = vertical scaling factor (m22). 471 e = Horizontal translation (m31). 472 f = Vertical translation (m32). 473 */ 474 @savedBySaveRestore 475 void transform(float a, float b, 476 float c, float d, 477 float e, float f) { 478 affine_matrix fwd = current.forward; 479 setTransform( fwd.a * a + fwd.c * b, 480 fwd.b * a + fwd.d * b, 481 fwd.a * c + fwd.c * d, 482 fwd.b * c + fwd.d * d, 483 fwd.a * e + fwd.c * f + fwd.e, 484 fwd.b * e + fwd.d * f + fwd.f ); 485 } 486 487 488 /** 489 Replace the current transform. 490 491 This takes six values for the upper two rows of a homogenous 492 3x3 matrix (i.e., {{a, c, e}, {b, d, f}, {0.0, 0.0, 1.0}}) 493 describing an arbitrary affine transform and replaces the 494 current transform with it. The values can represent any affine 495 transform such as scaling, rotation, translation, or skew, or 496 any composition of affine transforms. 497 498 The matrix must be invertible. f it is not, most drawing 499 operations will do nothing. 500 501 Note: to reset the current transform back to the default, use 502 1.0, 0.0, 0.0, 1.0, 0.0, 0.0. 503 504 Params: 505 a = Horizontal scaling factor (m11). 506 b = Vertical skewing (m12). 507 c = Horizontal skewing (m21). 508 d = Vertical scaling factor (m22). 509 e = Horizontal translation (m31). 510 f = Vertical translation (m32). 511 */ 512 @savedBySaveRestore 513 void setTransform(float a, float b, 514 float c, float d, 515 float e, float f) 516 { 517 float determinant = a * d - b * c; 518 float scaling = determinant != 0 ? (1 / determinant) : 0; 519 affine_matrix new_forward = affine_matrix(a, b, c, d, e, f); 520 affine_matrix new_inverse = affine_matrix( 521 scaling * d, scaling * -b, scaling * -c, scaling * a, 522 scaling * ( c * f - d * e ), scaling * ( b * e - a * f )); 523 current.forward = new_forward; 524 current.inverse = new_inverse; 525 } 526 527 528 // ======== COMPOSITING ======== 529 530 531 /** 532 Set/get the compositing operation for blending new drawing 533 and old pixels. 534 */ 535 @savedBySaveRestore 536 void globalCompositeOperation(CanvasCompositeOp op) { 537 current.op = op; 538 } 539 ///ditto 540 void globalCompositeOperation(const(char)[] compositeOp) { 541 542 CanvasCompositeOp op; 543 switch(compositeOp) with (CanvasCompositeOp) { 544 case "source-in": op = sourceIn; break; 545 case "copy": op = sourceCopy; break; 546 case "source-out": op = sourceOut; break; 547 case "destination-in": op = destinationIn; break; 548 case "destination-atop": op = destinationAtop; break; 549 case "add": op = add; break; 550 case "lighter": op = lighter; break; 551 case "destination-over": op = destinationOver; break; 552 case "destination-out": op = destinationOut; break; 553 case "source-atop": op = sourceAtop; break; 554 case "source-over": op = sourceOver; break; 555 case "xor": op = exclusiveOr; break; 556 default: return; // ignored 557 } 558 current.op = op; 559 } 560 ///ditto 561 CanvasCompositeOp globalCompositeOperation() { 562 return current.op; 563 } 564 565 566 567 /** 568 Set/get the opacity applied to all drawing operations. 569 570 If an operation already uses a transparent color, this can 571 make it yet more transparent. 572 573 `alpha` must be in the range 0.0 for fully transparent 574 to 1.0 for fully opaque. 575 576 Defaults to 1.0 (opaque). 577 578 Params: 579 alpha Degree of opacity applied to all drawing operations. 580 */ 581 @savedBySaveRestore 582 void globalAlpha(float alpha) { 583 if (0 <= alpha && alpha <= 1.0) 584 current.global_alpha = alpha; 585 } 586 ///ditto 587 float globalAlpha() { 588 return current.global_alpha; 589 } 590 591 592 // ======== SHADOWS ======== 593 594 595 /** 596 Set the color and opacity of the shadow. 597 598 Shadows will only be drawn if the shadow color has any 599 opacity and the shadow is either offset or blurred. 600 601 Defaults to transparent black. 602 603 Can give: 604 - a `Color`, such as one obtained by the `colors` package 605 - a CSS string, 606 - a `RGBA8` 8-bit sRGB ubyte quadruplet 607 - a `RGBA16` 16-bit sRGB ubyte quadruplet 608 - a `RGBAf` 32-bit sRGB ubyte quadruplet 609 */ 610 @savedBySaveRestore 611 void shadowColor(Color col) { 612 RGBAf co = col.toRGBAf(); 613 rgba c = rgba(co.r,co.g,co.b,co.a); 614 c = clamped(c); 615 fromGammaSpace((&c)[0..1], options.gammaCurve); 616 current.shadow_color = premultiplied(c); 617 } 618 ///ditto 619 void shadowColor(const(char)[] cssColor) { 620 shadowColor(Color(cssColor)); 621 } 622 ///ditto 623 void shadowColor(RGBAf col) { shadowColor(Color(col)); } 624 ///ditto 625 void shadowColor(RGBA8 col) { shadowColor(Color(col)); } 626 ///ditto 627 void shadowColor(RGBA16 col) { shadowColor(Color(col)); } 628 // <old method of original library> 629 deprecated void shadowColor(float r, float g, float b, float a) { 630 shadowColor(RGBAf(r, g, b, a)); 631 } 632 // </old method of original library> 633 634 // TODO: getter, store shadow color as Color 635 636 637 /** 638 Set/get the level of gaussian blurring on the shadow. 639 640 Zero produces no blur, while larger values will blur the 641 shadow more strongly. This is not affected by the current 642 transform. Must be non-negative. If it is not, this does 643 nothing. 644 645 Defaults to 0.0 (no blur). 646 647 Params: 648 level The level of gaussian blurring on the shadow. 649 */ 650 @savedBySaveRestore 651 void shadowBlur(float level) { 652 if (0.0f <= level) 653 current.shadow_blur = level; 654 } 655 ///ditto 656 float shadowBlur() { 657 return current.shadow_blur; 658 } 659 660 /** 661 Set/get offset of the shadow in pixels. 662 663 Negative shifts left/top, positive shifts right/bottom. 664 Not affected by the current transform. 665 Defaults to 0 (no offset). 666 */ 667 @savedBySaveRestore 668 void shadowOffsetX(float offsetX) { 669 current.shadow_offset_x = offsetX; 670 } 671 ///ditto 672 float shadowOffsetX() { 673 return current.shadow_offset_x; 674 } 675 ///ditto 676 @savedBySaveRestore 677 void shadowOffsetY(float offsetY) { 678 current.shadow_offset_y = offsetY; 679 } 680 ///ditto 681 float shadowOffsetY() { 682 return current.shadow_offset_y; 683 } 684 685 686 // ======== LINE STYLES ======== 687 688 689 /** 690 Set/get the width of the lines when stroking. 691 692 Initially this is measured in pixels, though the current 693 transform at the time of drawing can affect this. 694 Must be positive. If it is not, this does nothing. 695 Defaults to 1.0. 696 697 Params: 698 width Width of the lines when stroking. 699 */ 700 @savedBySaveRestore 701 void lineWidth(float width) { 702 if ( 0.0f < width ) 703 current.line_width = width; 704 } 705 ///ditto 706 float lineWidth() { 707 return current.line_width; 708 } 709 710 711 /// 712 @savedBySaveRestore 713 void lineCap(CanvasLineCap capStyle) { 714 current.line_cap = capStyle; 715 } 716 ///ditto 717 void lineCap(const(char)[] capStyle) { 718 switch(capStyle) with (CanvasLineCap) { 719 case "butt": current.line_cap = butt; break; 720 case "square": current.line_cap = square; break; 721 case "circle": current.line_cap = circle; break; 722 default: 723 } 724 } 725 ///ditto 726 CanvasLineCap lineCap() { 727 return current.line_cap; 728 } 729 730 731 /// 732 @savedBySaveRestore 733 void lineJoin(CanvasLineJoin joinStyle) { 734 current.line_join = joinStyle; 735 } 736 ///ditto 737 void lineJoin(const(char)[] joinStyle) { 738 switch(joinStyle) with (CanvasLineJoin) { 739 case "miter": current.line_join = miter; break; 740 case "bevel": current.line_join = bevel; break; 741 case "round": current.line_join = round; break; 742 default: 743 } 744 } 745 ///ditto 746 CanvasLineJoin lineJoin() { 747 return current.line_join; 748 } 749 750 751 /** 752 Set/get the limit on maximum pointiness allowed for miter 753 joins. 754 755 If the distance from the point where the lines intersect to 756 the point where the outside edges of the join intersect 757 exceeds this ratio relative to the line width, then a bevel 758 join will be used instead, and the miter will be lopped off. 759 Larger values allow pointier miters. Only affects stroking 760 and only when the line join style is miter. Must be positive. 761 If it is not, this does nothing. 762 763 Defaults to 10.0. 764 765 Params: 766 limit Limit on maximum pointiness allowed for miter joins. 767 */ 768 @savedBySaveRestore 769 void miterLimit(float limit) { 770 if (0 < limit) 771 current.miter_limit = limit; 772 } 773 ///ditto 774 float miterLimit() { 775 return current.miter_limit; 776 } 777 778 779 /** 780 Set or clear the line dash pattern. 781 782 Takes an array with entries alternately giving the lengths of 783 dash and gap segments. All must be non-negative; if any are 784 not, this does nothing. These will be used to draw with dashed 785 lines when stroking, with each subpath starting at the length 786 along the dash pattern indicated by the line dash offset. 787 Initially these lengths are measured in pixels, though the 788 current transform at the time of drawing can affect this. 789 The count must be non-negative. If it is odd, the array will 790 be appended to itself to make an even count. If it is zero, 791 or the pointer is null, the dash pattern will be cleared and 792 strokes will be drawn as solid lines. The array is copied and 793 it is safe to change or destroy it after this call. 794 Defaults to solid lines. 795 796 Params: 797 segments Pointer to array for dash pattern. 798 count Number of entries in the array. 799 */ 800 @savedBySaveRestore 801 void setLineDash(const(float)*segments, int count ) { 802 803 if (segments) 804 for (int i = 0; i < count; ++i) 805 if (segments[i] < 0) 806 return; 807 808 current.line_dash.clearContents(); 809 810 if ( ! segments) 811 return; 812 813 for (int i = 0; i < count; ++i) 814 current.line_dash.pushBack(segments[i]); 815 816 if (count & 1) // odd 817 for (int i = 0; i < count; ++i) 818 current.line_dash.pushBack(segments[i]); 819 } 820 ///ditto 821 @savedBySaveRestore 822 void setLineDash(const(float)[] segments) { 823 setLineDash(segments.ptr, cast(int)segments.length); 824 } 825 ///ditto 826 @savedBySaveRestore 827 void setLineDash() { 828 setLineDash(null, 0); 829 } 830 831 832 /** 833 Offset where each subpath starts the dash pattern. 834 835 Changing this shifts the location of the dashes along the path 836 and animating it will produce a marching ants effect. Only 837 affects stroking and only when a dash pattern is set. May be 838 negative or exceed the length of the dash pattern, in which 839 case it will wrap. 840 Defaults to 0.0. 841 */ 842 @savedBySaveRestore 843 void lineDashOffset(float offset) { 844 current.line_dash_offset = offset; 845 } 846 float lineDashOffset() { 847 return current.line_dash_offset; 848 } 849 850 851 // ======== FILL AND STROKE STYLES ======== 852 853 854 /** 855 Set filling or stroking to use a constant color and opacity. 856 857 Can give: 858 - a `Color`, such as one obtained by the `colors` package 859 - a CSS string, 860 - a `RGBA8` 8-bit sRGB ubyte quadruplet 861 - a `RGBA16` 16-bit sRGB ubyte quadruplet 862 - a `RGBAf` 32-bit sRGB ubyte quadruplet 863 */ 864 @savedBySaveRestore 865 void fillStyle(Color col) { 866 RGBAf c = col.toRGBAf(); 867 set_color(brush_type.fill_style, c.r, c.g, c.b, c.a); 868 } 869 ///ditto 870 void fillStyle(const(char)[] cssColor) { 871 fillStyle(Color(cssColor)); 872 } 873 ///ditto 874 void fillStyle(RGBAf col) { fillStyle(Color(col)); } 875 ///ditto 876 void fillStyle(RGBA8 col) { fillStyle(Color(col)); } 877 ///ditto 878 void fillStyle(RGBA16 col) { fillStyle(Color(col)); } 879 ///ditto 880 void fillStyle(T)(T col) if (isLikeRGBA8!T) { 881 // Support a color-like struct like Dplug's RGBA 882 fillStyle(RGBA8(cast(ubyte)col.r, 883 cast(ubyte)col.g, 884 cast(ubyte)col.b, 885 cast(ubyte)col.a)); 886 } 887 888 ///ditto 889 @savedBySaveRestore 890 void strokeStyle(Color col) { 891 RGBAf c = col.toRGBAf(); 892 set_color(brush_type.stroke_style, c.r, c.g, c.b, c.a); 893 } 894 ///ditto 895 void strokeStyle(const(char)[] cssColor) { 896 strokeStyle(Color(cssColor)); 897 } 898 ///ditto 899 void strokeStyle(RGBAf col) { strokeStyle(Color(col)); } 900 ///ditto 901 void strokeStyle(RGBA8 col) { strokeStyle(Color(col)); } 902 ///ditto 903 void strokeStyle(RGBA16 col) { strokeStyle(Color(col)); } 904 ///ditto 905 void strokeStyle(T)(T rgba) if (isLikeRGBA8!T) { 906 strokeStyle(RGBA8(cast(ubyte)rgba.r, 907 cast(ubyte)rgba.g, 908 cast(ubyte)rgba.b, 909 cast(ubyte)rgba.a)); 910 } 911 912 // <Old canvasity ways to give a color> 913 deprecated("Use fillStyle(str or Color) instead") 914 void fillStyle(float r, float g, float b, float a) { 915 set_color(brush_type.fill_style, r, g, b, a); 916 } 917 deprecated("Use strokeStyle(str or Color) instead") 918 void strokeStyle(float r, float g, float b, float a) { 919 set_color(brush_type.stroke_style, r, g, b, a); 920 } 921 // </Old canvasity ways to give a color> 922 923 924 // Note: the following doesn't follow the HTML5 Canvas API, which 925 // is different from dplug:canvas unfortunately. 926 927 928 // TODO: port later 929 930 /+ 931 /// @brief Set filling or stroking to use a linear gradient. 932 /// 933 /// Positions the start and end points of the gradient and clears all 934 /// color stops to reset the gradient to transparent black. Color stops 935 /// can then be added again. When drawing, pixels will be painted with 936 /// the color of the gradient at the nearest point on the line segment 937 /// between the start and end points. This is affected by the current 938 /// transform at the time of drawing. 939 /// 940 /// @param type whether to set the fill_style or stroke_style 941 /// @param start_x horizontal coordinate of the start of the gradient 942 /// @param start_y vertical coordinate of the start of the gradient 943 /// @param end_x horizontal coordinate of the end of the gradient 944 /// @param end_y vertical coordinate of the end of the gradient 945 /// 946 void set_linear_gradient(brush_type type, float start_x, float start_y, 947 float end_x, float end_y ) 948 { 949 paint_brush* brush = type == brush_type.fill_style ? &fill_brush : &stroke_brush; 950 brush.type = paint_brush.types.linear; 951 brush.colors.clearContents(); 952 brush.stops.clearContents(); 953 brush.start = xy( start_x, start_y ); 954 brush.end = xy( end_x, end_y ); 955 } 956 957 /// @brief Set filling or stroking to use a radial gradient. 958 /// 959 /// Positions the start and end circles of the gradient and clears all 960 /// color stops to reset the gradient to transparent black. Color stops 961 /// can then be added again. When drawing, pixels will be painted as 962 /// though the starting circle moved and changed size linearly to match 963 /// the ending circle, while sweeping through the colors of the gradient. 964 /// Pixels not touched by the moving circle will be left transparent 965 /// black. The radial gradient is affected by the current transform 966 /// at the time of drawing. The radii must be non-negative. 967 /// 968 /// @param type whether to set the fill_style or stroke_style 969 /// @param start_x horizontal starting coordinate of the circle 970 /// @param start_y vertical starting coordinate of the circle 971 /// @param start_radius starting radius of the circle 972 /// @param end_x horizontal ending coordinate of the circle 973 /// @param end_y vertical ending coordinate of the circle 974 /// @param end_radius ending radius of the circle 975 /// 976 void set_radial_gradient(brush_type type, 977 float start_x, 978 float start_y, 979 float start_radius, 980 float end_x, 981 float end_y, 982 float end_radius ) 983 { 984 if ( start_radius < 0.0f || end_radius < 0.0f ) 985 return; 986 paint_brush* brush = type == brush_type.fill_style ? &fill_brush : &stroke_brush; 987 brush.type = paint_brush.types.radial; 988 brush.colors.clear(); 989 brush.stops.clear(); 990 brush.start = xy( start_x, start_y ); 991 brush.end = xy( end_x, end_y ); 992 brush.start_radius = start_radius; 993 brush.end_radius = end_radius; 994 } 995 996 /// @brief Add a color stop to a linear or radial gradient. 997 /// 998 /// Each color stop has an offset which defines its position from 0.0 at 999 /// the start of the gradient to 1.0 at the end. Colors and opacity are 1000 /// linearly interpolated along the gradient between adjacent pairs of 1001 /// stops without premultiplying the alpha. If more than one stop is 1002 /// added for a given offset, the first one added is considered closest 1003 /// to 0.0 and the last is closest to 1.0. If no stop is at offset 0.0 1004 /// or 1.0, the stops with the closest offsets will be extended. If no 1005 /// stops are added, the gradient will be fully transparent black. Set a 1006 /// new linear or radial gradient to clear all the stops and redefine the 1007 /// gradient colors. Color stops may be added to a gradient at any time. 1008 /// The color and opacity values will be clamped to the 0.0 to 1.0 range, 1009 /// inclusive. The offset must be in the 0.0 to 1.0 range, inclusive. 1010 /// If it is not, or if chosen style type is not currently set to a 1011 /// gradient, this does nothing. 1012 /// 1013 /// @param type whether to add to the fill_style or stroke_style 1014 /// @param offset position of the color stop along the gradient 1015 /// @param red sRGB red component of the color stop 1016 /// @param green sRGB green component of the color stop 1017 /// @param blue sRGB blue component of the color stop 1018 /// @param alpha opacity of the color stop (not premultiplied) 1019 /// 1020 void add_color_stop(brush_type type, 1021 float offset, 1022 float red, 1023 float green, 1024 float blue, 1025 float alpha ) 1026 { 1027 paint_brush* brush = type == brush_type.fill_style ? &fill_brush : &stroke_brush; 1028 if ( ( brush.type != paint_brush.types.linear && 1029 brush.type != paint_brush.types.radial ) || 1030 offset < 0.0f || 1.0f < offset ) 1031 return; 1032 1033 // Finds the first element in stop that is greater than offset. 1034 size_t index = brush.stops.length; 1035 for (size_t i = 0; i < brush.stops.length; ++i) 1036 { 1037 if ( brush.stops[i] > offset) 1038 { 1039 index = i; 1040 break; 1041 } 1042 } 1043 1044 rgba color = linearized( clamped( rgba( red, green, blue, alpha ) ) ); 1045 1046 // Insert into colors and stops 1047 brush.colors.pushBack(rgba.init); 1048 brush.stops.pushBack(float.init); 1049 int last = cast(int)(brush.colors.length - 1); 1050 for (int i = last; i > index; --i) 1051 { 1052 brush.colors[i] = brush.colors[i-1]; 1053 brush.stops[i] = brush.stops[i-1]; 1054 } 1055 brush.colors[index] = color; 1056 brush.stops[index] = offset; 1057 } 1058 +/ 1059 1060 1061 1062 // ======== BUILDING PATHS ======== 1063 1064 1065 /** 1066 Reset the current path. 1067 1068 The current path and all subpaths will be cleared after this, 1069 and a new path can be built. 1070 */ 1071 void beginPath() { 1072 path.points.clearContents(); 1073 path.subpaths.clearContents(); 1074 } 1075 1076 1077 /** 1078 Create a new subpath. 1079 1080 The given point will become the first point of the new subpath 1081 and is subject to the current transform at the time this is 1082 called. 1083 1084 Params: 1085 x Horizontal coordinate of the new first point. 1086 y Vertical coordinate of the new first point. 1087 */ 1088 void moveTo(float x, float y) { 1089 xy transformed = forwardTransform(xy(x, y)); 1090 if ( (path.subpaths.length != 0) 1091 && path.subpaths[$-1].count == 1 ) { 1092 path.points[$-1] = transformed; 1093 return; 1094 } 1095 subpath_data subpath = subpath_data(1, false); 1096 path.points.pushBack(transformed); 1097 path.subpaths.pushBack( subpath ); 1098 } 1099 ///ditto 1100 void moveTo(T)(T v) { 1101 moveTo(v.x, v.y); 1102 } 1103 1104 1105 /** 1106 Close the current subpath. 1107 1108 Adds a straight line from the end of the current subpath back 1109 to its first point and marks the subpath as closed so that 1110 this line will join with the beginning of the path at this 1111 point. A new, empty subpath will be started beginning with the 1112 same first point. If the current path is empty, this does 1113 nothing. 1114 */ 1115 void closePath() { 1116 if (path.subpaths.length == 0) 1117 return; 1118 size_t pointsInSubpath = path.subpaths[$-1].count; 1119 xy first = path.points[path.points.length - pointsInSubpath]; 1120 1121 // MAYDO: ugly, maybe lineTo and moveTo could have a private 1122 // impl with abs coordinates 1123 affine_matrix saved_forward = current.forward; 1124 current.forward = affine_matrix.identity; 1125 1126 // finish path 1127 lineTo(first.x, first.y); 1128 path.subpaths[$-1].closed = true; 1129 1130 // move there. 1131 moveTo(first.x, first.y); 1132 current.forward = saved_forward; 1133 } 1134 1135 1136 /** 1137 Extend the current subpath with a straight line. 1138 1139 The line will go from the current end point (if the current 1140 path is not empty) to the given point, which will become the 1141 new end point. Its position is affected by the current 1142 transform at the time that this is called. If the current path 1143 was empty, this is equivalent to just a move. 1144 1145 Params: 1146 x Horizontal coordinate of the new end point. 1147 y Vertical coordinate of the new end point. 1148 */ 1149 void lineTo(float x, float y) { 1150 if (path.subpaths.length == 0) { 1151 moveTo(x, y); 1152 return; 1153 } 1154 xy p1 = path.points[$-1]; 1155 xy p2 = forwardTransform(xy(x, y)); 1156 if (dot(p2 - p1, p2 - p1 ) == 0.0f) 1157 return; 1158 // PERF: pushBack all 3 at once 1159 path.points.pushBack(p1); 1160 path.points.pushBack(p2); 1161 path.points.pushBack(p2); 1162 path.subpaths[$-1].count += 3; 1163 } 1164 ///ditto 1165 void lineTo(T)(T v) { 1166 lineTo(v.x, v.y); // support point types 1167 } 1168 1169 1170 /** 1171 Extend the current subpath with a quadratic Bezier curve. 1172 1173 The curve will go from the current end point (or the control 1174 point if the current path is empty) to the given point, which 1175 will become the new end point. The curve will be tangent 1176 toward the control point at both ends. The current transform 1177 at the time that this is called will affect both points passed 1178 in. 1179 1180 Tip: to make multiple curves join smoothly, ensure that each 1181 new end point is on a line between the adjacent control 1182 points. 1183 1184 Params: 1185 cx Horizontal coordinate of the control point. 1186 cy Vertical coordinate of the control point. 1187 x Horizontal coordinate of the new end point. 1188 y Vertical coordinate of the new end point. 1189 */ 1190 void quadraticCurveYo(float cx, float cy, float x, float y ) 1191 { 1192 if (path.subpaths.length == 0) 1193 moveTo(cx, cy); 1194 xy point_1 = path.points[$-1]; 1195 xy control = forwardTransform(xy(cx, cy)); 1196 xy point_2 = forwardTransform(xy( x, y)); 1197 xy control_1 = lerp(point_1, control, 2.0f / 3.0f); 1198 xy control_2 = lerp(point_2, control, 2.0f / 3.0f); 1199 // PERF: same, pushback all 3 at once 1200 path.points.pushBack(control_1); 1201 path.points.pushBack(control_2); 1202 path.points.pushBack(point_2); 1203 path.subpaths[$-1].count += 3; 1204 } 1205 ///ditto 1206 void quadraticCurveYo(T)(T c, T p) { 1207 quadraticCurveYo(c.x, c.y, p.x, p.y); // support point types 1208 } 1209 1210 /** 1211 Extend the current subpath with a cubic Bezier curve. 1212 1213 The curve will go from the current end point (or the first 1214 control point if the current path is empty) to the given 1215 point, which will become the new end point. The curve will be 1216 tangent toward the first control point at the beginning and 1217 tangent toward the second control point at the end. The 1218 current transform at the time that this is called will affect 1219 all points passed in. 1220 1221 Tip: to make multiple curves join smoothly, ensure that each 1222 new end point is on a line between the adjacent control 1223 points. 1224 1225 Params: 1226 c1_x Horizontal coordinate of 1st control point. 1227 c1_y Vertical coordinate of 1st control point. 1228 c2_x Horizontal coordinate of 2nd control point. 1229 c2_y Vertical coordinate of 2nd control point. 1230 x Horizontal coordinate of new end point. 1231 y Vertical coordinate of new end point. 1232 */ 1233 void bezierCurveTo(float c1_x, float c1_y, 1234 float c2_x, float c2_y, 1235 float x, float y ) { 1236 if ( path.subpaths.length == 0 ) 1237 moveTo( c1_x, c1_y ); 1238 xy control_1 = forwardTransform(xy( c1_x, c1_y )); 1239 xy control_2 = forwardTransform(xy( c2_x, c2_y )); 1240 xy point_2 = forwardTransform(xy( x, y )); 1241 // PERF: same, pushback all 3 at once 1242 path.points.pushBack(control_1); 1243 path.points.pushBack(control_2); 1244 path.points.pushBack(point_2); 1245 path.subpaths[$-1].count += 3; 1246 } 1247 ///ditto 1248 void bezierCurveTo(T)(T c1, T c2, T p) { 1249 // support point types 1250 bezierCurveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y); 1251 } 1252 1253 /** 1254 Extend the current subpath with an arc tangent to two lines. 1255 1256 The arc is from the circle with the given radius tangent to 1257 both the line from the current end point to the vertex, and to 1258 the line from the vertex to the given point. A straight line 1259 will be added from the current end point to the first tangent 1260 point (unless the current path is empty), then the shortest 1261 arc from the first to the second tangent points will be added. 1262 The second tangent point will become the new end point. 1263 If the radius is large, these tangent points may fall outside 1264 the line segments. The current transform at the time that this 1265 is called will affect the passed in points and the arc. 1266 If the current path was empty, this is equivalent to a move. 1267 If the arc would be degenerate, it is equivalent to a line to 1268 the vertex point. The radius must be non-negative. 1269 If it is not, or if the current transform is not invertible, 1270 this does nothing. 1271 1272 Note: To draw a polygon with rounded corners, call this once 1273 for each vertex and pass the midpoint of the adjacent 1274 edge as the second point; this works especially well for 1275 rounded boxes. 1276 1277 Params: 1278 v_x Horizontal coordinate where the tangent lines meet. 1279 v_y Vertical coordinate where the tangent lines meet. 1280 x A horizontal coordinate on the second tangent line. 1281 y A vertical coordinate on the second tangent line. 1282 r Radius of the circle containing the arc. 1283 */ 1284 void arcTo(float v_x, float v_y, float x, float y, float r ) { 1285 affine_matrix fwd = current.forward; 1286 if ( (r < 0) || ( ! fwd.isInvertible) == 0.0f) 1287 return; 1288 if (path.subpaths.length == 0) 1289 moveTo(v_x, v_y); 1290 xy point_1 = inverseTransform(path.points[$-1]); 1291 xy vertex = xy(v_x, v_y); 1292 xy point_2 = xy(x, y); 1293 xy edge_1 = normalized(point_1 - vertex); 1294 xy edge_2 = normalized(point_2 - vertex); 1295 float sine = fabsf( dot( perpendicular( edge_1 ), edge_2 ) ); 1296 enum float epsilon = 1.0e-4f; 1297 if (sine < epsilon) { 1298 lineTo(v_x, v_y); 1299 return; 1300 } 1301 xy offset = ( edge_1 + edge_2 ) * (r / sine); 1302 xy center = vertex + offset; 1303 float a1 = direction(dot(offset, edge_1)*edge_1 - offset); 1304 float a2 = direction(dot(offset, edge_2)*edge_2 - offset); 1305 bool reverse = cast(int)(floorf((a2 - a1) / 3.14159265f)) & 1; 1306 arc(center.x, center.y, r, a1, a2, reverse); 1307 } 1308 ///ditto 1309 void arcTo(T)(T v, T p, float r) { 1310 // support point types 1311 arcTo(v.x, v.y, p.x, p.y, r); 1312 } 1313 1314 1315 /** 1316 Extend the current subpath with an arc between two angles. 1317 1318 The arc is from the circle centered at the given point and 1319 with the given radius. A straight line will be added from the 1320 current end point to the starting point of the arc (unless the 1321 current path is empty), then the arc along the circle from the 1322 starting angle to the ending angle in the given direction will 1323 be added. 1324 1325 If they are more than two pi radians apart in the given 1326 direction, the arc will stop after one full circle. The point 1327 at the ending angle will become the new end point of the path. 1328 Initially, the angles are clockwise relative to the x-axis. 1329 The current transform at the time that this is called will 1330 affect the passed in point, angles, and arc. 1331 The radius must be non-negative else it does nothing. 1332 1333 Params: 1334 x Horizontal coordinate of circle center. 1335 y Vertical coordinate of circle center. 1336 radius Radius of the circle containing the arc. 1337 start_angle Radians clockwise from x-axis to begin. 1338 end_angle Radians clockwise from x-axis to end. 1339 counterClockwise `true` if arc turns counter-clockwise. 1340 The default is false (clockwise). 1341 */ 1342 void arc(float x, float y, float radius, 1343 float start_angle, float end_angle, 1344 bool counter_clockwise = false) { 1345 1346 if (radius < 0) 1347 return; 1348 1349 enum float tau = 6.28318531f; 1350 float winding = counter_clockwise ? -1.0f : 1.0f; 1351 float from = fmodf(start_angle, tau); 1352 float span = fmodf(end_angle, tau) - from; 1353 1354 if (( end_angle - start_angle) * winding >= tau) 1355 span = tau * winding; 1356 else if (span * winding < 0.0f) 1357 span += tau * winding; 1358 1359 xy centered_1 = radius * xy(cosf(from), sinf(from)); 1360 lineTo(x + centered_1.x, y + centered_1.y); 1361 if (span == 0.0f) 1362 return; 1363 1364 float fsteps = roundf(16.0f / tau * span * winding); 1365 int steps = cast(int)(fmaxf(1.0f, fsteps)); 1366 float segment = span / cast(float)(steps); 1367 float alpha = 4.0f / 3.0f * tanf(0.25f * segment); 1368 1369 // Note: it's a bit of the same weakness as dplug:canvas, 1370 // in that the number of bezier subdivide do not depend 1371 // on the transform. 1372 for ( int step = 0; step < steps; ++step ) { 1373 float angle = from + cast(float)( step + 1 ) * segment; 1374 xy centered_2 = radius * xy(cosf(angle), sinf(angle)); 1375 xy point_1 = xy( x, y ) + centered_1; 1376 xy point_2 = xy( x, y ) + centered_2; 1377 xy control_1 = point_1 + alpha*perpendicular(centered_1); 1378 xy control_2 = point_2 - alpha*perpendicular(centered_2); 1379 bezierCurveTo(control_1.x, control_1.y, 1380 control_2.x, control_2.y, 1381 point_2.x, point_2.y ); 1382 centered_1 = centered_2; 1383 } 1384 } 1385 ///ditto 1386 void arc(T)(T p, float radius, float start_angle, 1387 float end_angle, bool counter_clockwise = false) { 1388 // support point types 1389 arc(p.x, p.y, radius, start_angle, end_angle, counter_clockwise); 1390 } 1391 1392 1393 /** 1394 Add a closed subpath in the shape of a rectangle. 1395 1396 The rectangle has one corner at the given point and then goes 1397 in the direction along the width before going in the direction 1398 of the height towards the opposite corner. The current 1399 transform at the time that this is called will affect the 1400 given point and rectangle. The width and/or the height may be 1401 negative or zero, and this can affect the winding direction. 1402 1403 Params: 1404 x = Horizontal coordinate of a rectangle corner. 1405 y = Vertical coordinate of a rectangle corner. 1406 width = Width of the rectangle. 1407 height = Height of the rectangle. 1408 */ 1409 void rect(float x, float y, float width, float height) { 1410 moveTo(x, y); 1411 lineTo(x + width, y); 1412 lineTo(x + width, y + height); 1413 lineTo(x, y + height); 1414 closePath(); 1415 } 1416 1417 1418 // ======== DRAWING PATHS ======== 1419 1420 1421 /** Draw the interior of the current path using the fill style. 1422 1423 Interior pixels are determined by the non-zero winding rule, 1424 with all open subpaths implicitly closed by a straight line 1425 beforehand. If shadows have been enabled by setting the shadow 1426 color with any opacity and either offsetting or blurring the 1427 shadows, then the shadows of the filled areas will be drawn 1428 first, followed by the filled areas themselves. Both will be 1429 blended into the canvas according to the global alpha, the 1430 global compositing operation, and the clip region. If the fill 1431 style is a gradient or a pattern, it will be affected by the 1432 current transform. The current path is left unchanged by 1433 filling; begin a new path to clear it. If the current 1434 transform is not invertible, this does nothing. 1435 */ 1436 void fill() { 1437 path_to_lines(false); 1438 render_main(current.fill_brush); 1439 } 1440 1441 1442 /** 1443 Draw the edges of the current path using the stroke style. 1444 1445 Edges of the path will be expanded into strokes according to 1446 the current dash pattern, dash offset, line width, line join 1447 style (and possibly miter limit), line cap, and transform. 1448 If shadows have been enabled by setting the shadow color with 1449 any opacity and either offsetting or blurring the shadows, 1450 then the shadow will be drawn for the stroked lines first, 1451 then the stroked lines themselves. Both will be blended into 1452 the canvas according to the global alpha, the global 1453 compositing operation, and the clip region. If the stroke 1454 style is a gradient or a pattern, it will be affected by the 1455 current transform. The current path is left unchanged by 1456 stroking; begin a new path to clear it. If the current 1457 transform is not invertible, this does nothing. 1458 1459 Note: to draw with a calligraphy-like angled brush effect, add 1460 a non-uniform scale transform just before stroking. 1461 */ 1462 void stroke() { 1463 path_to_lines(true); 1464 stroke_lines(); 1465 render_main(current.stroke_brush); 1466 } 1467 1468 /** 1469 Restrict the clip region by the current path. 1470 1471 Intersects the current clip region with the interior of the 1472 current path (the region that would be filled), and replaces 1473 the current clip region with this intersection. Subsequent 1474 calls to clip can only reduce this further. When filling or 1475 stroking, only pixels within the current clip region will 1476 change. The current path is left unchanged by updating the 1477 clip region; begin a new path to clear it. Defaults to the 1478 entire canvas. 1479 1480 Tip: to be able to reset the current clip region, save the 1481 canvas state first before clipping then restore the state 1482 to reset it. 1483 */ 1484 void clip() { 1485 path_to_lines(false); 1486 lines_to_runs(xy(0.0f, 0.0f), 0); 1487 size_t part = runs.length; 1488 runs.pushBack(current.mask); 1489 Vec!pixel_run* mask = ¤t.mask; 1490 mask.clearContents(); 1491 int y = -1; 1492 float last = 0; 1493 float sum_1 = 0; 1494 float sum_2 = 0; 1495 size_t index_1 = 0; 1496 size_t index_2 = part; 1497 while (index_1 < part && index_2 < runs.length) { 1498 bool which = comparePixelRuns(runs[index_1], 1499 runs[index_2]) < 0; 1500 pixel_run next = (which != 0) ? runs[index_1] 1501 : runs[index_2]; 1502 if (next.y != y) { 1503 y = next.y; 1504 last = 0; 1505 sum_1 = 0; 1506 sum_2 = 0; 1507 } 1508 if ( which ) 1509 sum_1 += runs[ index_1++ ].delta; 1510 else 1511 sum_2 += runs[ index_2++ ].delta; 1512 float visibility = ( fminf(fabsf(sum_1), 1.0f) * 1513 fminf(fabsf(sum_2), 1.0f) ); 1514 if ( visibility == last ) 1515 continue; 1516 size_t lastI = mask.length - 1; 1517 if ( (mask.length != 0) && 1518 (*mask)[lastI].x == next.x && (*mask)[lastI].y == next.y) 1519 (*mask)[lastI].delta += visibility - last; 1520 else { 1521 pixel_run piece; 1522 piece = pixel_run(next.x, next.y, visibility-last); 1523 mask.pushBack(piece); 1524 } 1525 last = visibility; 1526 } 1527 } 1528 1529 /** 1530 Tests whether a point is in or on the current path. 1531 1532 Interior areas are determined by the non-zero winding rule, 1533 with all open subpaths treated as implicitly closed by a 1534 straight line beforehand. Points exactly on the boundary are 1535 also considered inside. The point to test is interpreted 1536 without being affected by the current transform, nor is the 1537 clip region considered. The current path is left unchanged by 1538 this test. 1539 1540 Params: 1541 x = Horizontal coordinate of the point to test. 1542 y = Vertical coordinate of the point to test. 1543 Returns: `true` if the point is in or on the current path. 1544 */ 1545 bool isPointInPath(float x, float y) { 1546 path_to_lines( false ); 1547 int winding = 0; 1548 1549 size_t subpath = 0; 1550 size_t beginning = 0; 1551 size_t ending = 0; 1552 1553 xy[] points = lines.points[]; 1554 for (size_t i = 0; i < points.length; ++i) { 1555 while ( i >= ending ) { 1556 beginning = ending; 1557 ending += lines.subpaths[subpath++].count; 1558 } 1559 xy A = points[i]; 1560 xy B = points[i + 1 < ending ? i + 1 : beginning]; 1561 1562 if ( (A.y < y && y <= B.y) || (B.y < y && y <= A.y) ) { 1563 float side = dot(perpendicular(B - A), xy(x, y) - A); 1564 if (side == 0.0f) 1565 return true; 1566 winding += side > 0.0f ? 1 : -1; 1567 } 1568 else if ( A.y == y && y == B.y && 1569 ( ( A.x <= x && x <= B.x ) || 1570 ( B.x <= x && x <= A.x ) ) ) 1571 return true; 1572 } 1573 return winding != 0; 1574 } 1575 ///ditto 1576 bool isPointInPath(T)(T p) { 1577 return isPointInPath(p.x, p.y); 1578 } 1579 1580 1581 // ======== DRAWING RECTANGLES ======== 1582 1583 1584 /** 1585 Clear a rectangular area back to transparent black. 1586 1587 The clip region may limit the area cleared. The current path 1588 is not affected by this clearing. The current transform at the 1589 time that this is called will affect the given point and 1590 rectangle. The width and/or the height may be negative or 1591 zero. If either is zero, or the current transform is not 1592 invertible, this does nothing. 1593 1594 Params: 1595 x = Horizontal coordinate of rectangle corner. 1596 y = Vertical coordinate of rectangle corner. 1597 width = Width of the rectangle. 1598 height = Height of the rectangle. 1599 */ 1600 void clearRect(float x, float y, float width, float height) { 1601 1602 CanvasCompositeOp saved_operation = current.op; 1603 float saved_global_alpha = current.global_alpha; 1604 float saved_alpha = current.shadow_color.a; 1605 paint_brush.types saved_type = current.fill_brush.type; 1606 1607 current.op = CanvasCompositeOp.destinationOut; 1608 current.global_alpha = 1.0f; 1609 current.shadow_color.a = 0.0f; 1610 current.fill_brush.type = paint_brush.types.color; 1611 1612 fillRect(x, y, width, height); 1613 1614 current.fill_brush.type = saved_type; 1615 current.shadow_color.a = saved_alpha; 1616 current.global_alpha = saved_global_alpha; 1617 current.op = saved_operation; 1618 } 1619 1620 1621 /** 1622 Fill a rectangular area. 1623 1624 This behaves as though the current path were reset to a single 1625 rectangle and then filled as usual. However, the current path 1626 is not actually changed. The current transform at the time 1627 that this is called will affect the given point and rectangle. 1628 The width and/or the height may be negative but not zero. 1629 If either is zero, or the current transform is not invertible, 1630 this does nothing. 1631 1632 Params: 1633 x = Horizontal coordinate of a rectangle corner. 1634 y = Vertical coordinate of a rectangle corner. 1635 w = Width of the rectangle. 1636 h = Height of the rectangle. 1637 */ 1638 void fillRect(float x, float y, float w, float h) { 1639 1640 if (w == 0 || h == 0) 1641 return; 1642 1643 Vec!xy* points = &lines.points; 1644 points.clearContents(); 1645 lines.subpaths.clearContents(); 1646 // PERF 1647 points.pushBack(forwardTransform(xy(x, y))); 1648 points.pushBack(forwardTransform(xy(x + w, y))); 1649 points.pushBack(forwardTransform(xy(x + w, y + h))); 1650 points.pushBack(forwardTransform(xy(x, y + h))); 1651 subpath_data entry = subpath_data(4, true); 1652 lines.subpaths.pushBack(entry); 1653 render_main(current.fill_brush); 1654 } 1655 1656 1657 /** 1658 Stroke a rectangular area. 1659 1660 This behaves as though the current path were reset to a single 1661 rectangle and then stroked as usual. However, the current 1662 path is not actually changed. The current transform at the 1663 time that this is called will affect the given point and 1664 rectangle. The width and/or the height may be negative or 1665 zero. If both are zero, or the current transform is not 1666 invertible, this does nothing. If only one is zero, this 1667 behaves as though it strokes a single horizontal or vertical 1668 line. 1669 1670 Params: 1671 x = Horizontal coordinate of a rectangle corner. 1672 y = Vertical coordinate of a rectangle corner. 1673 w = Width of the rectangle. 1674 h = Height of the rectangle. 1675 */ 1676 void strokeRect(float x, float y, float w, float h) { 1677 if ( w == 0.0f && h == 0.0f ) 1678 return; 1679 Vec!xy* points = &lines.points; 1680 points.clearContents(); 1681 lines.subpaths.clearContents(); 1682 if ( w == 0.0f || h == 0.0f ) { 1683 points.pushBack(forwardTransform(xy( x, y ))); 1684 points.pushBack(forwardTransform(xy(x+w, y+h))); 1685 subpath_data entry = subpath_data(2, false); 1686 lines.subpaths.pushBack( entry ); 1687 } 1688 else { 1689 points.pushBack(forwardTransform(xy( x, y ))); 1690 points.pushBack(forwardTransform(xy(x+w, y ))); 1691 points.pushBack(forwardTransform(xy(x+w, y+h))); 1692 points.pushBack(forwardTransform(xy( x, y+h))); 1693 points.pushBack(forwardTransform(xy( x, y ))); 1694 subpath_data entry = { 5, true }; 1695 lines.subpaths.pushBack(entry); 1696 } 1697 stroke_lines(); 1698 render_main(current.stroke_brush); 1699 } 1700 1701 1702 // ======== DRAWING TEXT ======== 1703 1704 1705 /** 1706 Set the font to use for text drawing. 1707 1708 The font must be a TrueType font (TTF) file which has been 1709 loaded or mapped into memory. Following some basic 1710 validation, the relevant sections of the font file contents 1711 are copied, and it is safe to change or destroy after this 1712 call. As an optimization, calling this with either a null 1713 pointer or zero for the number of bytes will allow for 1714 changing the size of the previous font without recopying from 1715 the file. Note that the font parsing is not meant to be 1716 secure; only use this with trusted TTF files! 1717 1718 Params: 1719 font = Contents of a TrueType font (TTF) file. 1720 bytes = Number of bytes in the font file. 1721 size = Size in pixels per em to draw at. 1722 1723 Returns: 1724 `true` if the font was set successfully. 1725 */ 1726 bool setFont(const(ubyte) *font, int bytes, float size) { 1727 1728 if ( font && bytes ) { 1729 current.face.data.clearContents(); 1730 current.face.cmap = 0; 1731 current.face.glyf = 0; 1732 current.face.head = 0; 1733 current.face.hhea = 0; 1734 current.face.hmtx = 0; 1735 current.face.loca = 0; 1736 current.face.maxp = 0; 1737 current.face.os_2 = 0; 1738 if ( bytes < 6 ) 1739 return false; 1740 int version_ = ( font[ 0 ] << 24 | font[ 1 ] << 16 | 1741 font[ 2 ] << 8 | font[ 3 ] << 0 ); 1742 int tables = font[ 4 ] << 8 | font[ 5 ]; 1743 if ( ( version_ != 0x00010000 && version_ != 0x74727565 ) 1744 || bytes < tables * 16 + 12 ) 1745 return false; 1746 1747 foreach(ubyte b; font[0..tables*16+12]) 1748 current.face.data.pushBack(b); 1749 1750 //face.data.insert( face.data.end(), font, 1751 // font + tables * 16 + 12 ); 1752 for ( int index = 0; index < tables; ++index ) 1753 { 1754 int tag = signed_32(current.face.data, index * 16 + 12); 1755 int ofs = signed_32(current.face.data, index * 16 + 20); 1756 int span = signed_32(current.face.data, index * 16 + 24); 1757 if ( bytes < ofs + span ) 1758 { 1759 current.face.data.clearContents(); 1760 return false; 1761 } 1762 int place = cast(int)( current.face.data.length() ); 1763 if ( tag == 0x636d6170 ) 1764 current.face.cmap = place; 1765 else if ( tag == 0x676c7966 ) 1766 current.face.glyf = place; 1767 else if ( tag == 0x68656164 ) 1768 current.face.head = place; 1769 else if ( tag == 0x68686561 ) 1770 current.face.hhea = place; 1771 else if ( tag == 0x686d7478 ) 1772 current.face.hmtx = place; 1773 else if ( tag == 0x6c6f6361 ) 1774 current.face.loca = place; 1775 else if ( tag == 0x6d617870 ) 1776 current.face.maxp = place; 1777 else if ( tag == 0x4f532f32 ) 1778 current.face.os_2 = place; 1779 else 1780 continue; 1781 foreach(ubyte b; font[ofs..ofs+span]) 1782 current.face.data.pushBack(b); 1783 } 1784 if ( !current.face.cmap || !current.face.glyf 1785 || !current.face.head || !current.face.hhea 1786 || !current.face.hmtx || !current.face.loca 1787 || !current.face.maxp || !current.face.os_2 ) 1788 { 1789 current.face.data.clearContents(); 1790 return false; 1791 } 1792 } 1793 if ( current.face.data.length == 0 ) 1794 return false; 1795 int units_per_em = unsigned_16( current.face.data, 1796 current.face.head + 18 ); 1797 current.face.scale = size / cast(float)( units_per_em ); 1798 return true; 1799 } 1800 1801 1802 /** 1803 Draw a line of text by filling its outline. 1804 1805 This behaves as though the current path were reset to the 1806 outline of the given text in the current font and size, 1807 positioned relative to the given anchor point according to the 1808 current alignment and baseline, and then filled as usual. 1809 However, the current path is not actually changed. The current 1810 transform at the time that this is called will affect the 1811 given anchor point and the text outline. However, the 1812 comparison to the maximum width in pixels and the condensing 1813 if needed is done before applying the current transform. 1814 The maximum width (if given) must be positive. 1815 If it is not, or the text pointer is null, or the font has not 1816 been set yet, or the last setting of it was unsuccessful, or 1817 the current transform is not invertible, this does nothing. 1818 1819 Params: 1820 text = Null-terminated UTF-8 string of text to fill. 1821 x = Horizontal coordinate of the anchor point. 1822 y = Vertical coordinate of the anchor point. 1823 maxWidth = Horizontal width to condense wider text to. 1824 */ 1825 // TODO: take regular D string instead 1826 void fillText(const(char)* text, float x, float y, 1827 float maxWidth = 1.0e30f) { 1828 text_to_lines(text, xy(x, y), maxWidth, false); 1829 render_main(current.fill_brush); 1830 } 1831 1832 /** 1833 Draw a line of text by stroking its outline. 1834 1835 This behaves as though the current path were reset to the 1836 outline of the given text in the current font and size, 1837 positioned relative to the given anchor point according to the 1838 current alignment and baseline, and then stroked as usual. 1839 However, the current path is not actually changed. The current 1840 transform at the time that this is called will affect the 1841 given anchor point and the text outline. 1842 However, the comparison to the maximum width in pixels and the 1843 condensing if needed is done before applying the current 1844 transform. The maximum width (if given) must be positive. 1845 If it is not, or the text pointer is null, or the font has not 1846 been set yet, or the last setting of it was unsuccessful, or 1847 the current transform is not invertible, this does nothing. 1848 1849 Params: 1850 text = Null-terminated UTF-8 string to stroke. 1851 x = Horizontal coordinate of the anchor point. 1852 y = Vertical coordinate of the anchor point. 1853 maxWidth = Horizontal width to condense wider text to. 1854 */ 1855 void stroke_text(const(char)* text, 1856 float x, float y, 1857 float maxWidth = 1.0e30f) { 1858 text_to_lines(text, xy(x, y), maxWidth, true); 1859 stroke_lines(); 1860 render_main(current.stroke_brush); 1861 } 1862 1863 /** 1864 Measure the width in pixels of a line of text. 1865 1866 The measured width is the advance width, which includes the 1867 side bearings of the first and last glyphs. However, text as 1868 drawn may go outside this (e.g., due to glyphs that spill 1869 beyond their nominal widths or stroked text with wide lines). 1870 Measurements ignore the current transform. If the text 1871 pointer is null, or the font has not been set yet, or the last 1872 setting of it was unsuccessful, this returns zero. 1873 1874 Params: 1875 text = Null-terminated UTF-8 string to measure. 1876 1877 Returns: 1878 Width of the text in pixels. 1879 FUTURE: more metrics, use a font API in another package 1880 */ 1881 float measure_text(const(char)* text) { 1882 if ( (current.face.data.length == 0) || !text ) 1883 return 0.0f; 1884 int hmetrics = unsigned_16(current.face.data, 1885 current.face.hhea+34); 1886 int width = 0; 1887 for ( int index = 0; text[index]; ) { 1888 int glyph = character_to_glyph(text, index); 1889 int entry = min_int( glyph, hmetrics - 1 ); 1890 width += unsigned_16(current.face.data, 1891 current.face.hmtx+entry*4); 1892 } 1893 return cast(float)(width) * current.face.scale; 1894 } 1895 1896 // ======== DRAWING IMAGES ======== 1897 1898 /** 1899 Draw an image onto the canvas. 1900 1901 The position of the rectangle that the image is drawn to is 1902 affected by the current transform at the time of drawing, and 1903 the image will be resampled as needed (with the filtering 1904 always clamping to the edges of the image). The drawing is 1905 also affected by the shadow, global alpha, global compositing 1906 operation settings, and by the clip region. The current path 1907 is not affected by drawing an image. The image data, which 1908 should be in top to bottom rows of contiguous pixels from left 1909 to right, is not retained and it is safe to change or destroy 1910 it after this call. The width and height must both be positive 1911 and the width and/or the height to scale to may be negative 1912 but not zero. Otherwise, or if the image pointer is null, or 1913 the current transform is not invertible, this does nothing. 1914 1915 Note: to use a small piece of a larger image, reduce the width 1916 and height, and offset the image pointer while keeping 1917 the stride. 1918 1919 Params: 1920 image = Unpremultiplied sRGB RGBA8 image data. 1921 width = Width of the image in pixels. 1922 height = Height of the image in pixels. 1923 stride = Bytes between the start of each image row. 1924 x = Horizontal coordinate to draw the corner at. 1925 y = Vertical coordinate to draw the corner at. 1926 to_width = Width to scale the image to. 1927 to_height = Height to scale the image to. 1928 */ 1929 void drawImage(const(ubyte)* image, 1930 int width, 1931 int height, 1932 int stride, 1933 float x, 1934 float y, 1935 float to_width, 1936 float to_height) { 1937 if (!image || width <= 0 || height <= 0 || 1938 to_width == 0.0f || to_height == 0.0f) 1939 return; 1940 swap_brush(current.fill_brush, image_brush, temp_brush); 1941 setPattern(brush_type.fill_style, image, width, height, 1942 stride, repetition_style.repeat); 1943 swap_brush(current.fill_brush, image_brush, temp_brush); 1944 Vec!xy* pts = &lines.points; 1945 pts.clearContents(); 1946 lines.subpaths.clearContents(); 1947 pts.pushBack(forwardTransform(xy(x, y ))); 1948 pts.pushBack(forwardTransform(xy(x+to_width, y))); 1949 pts.pushBack(forwardTransform(xy(x+to_width, y+to_height))); 1950 pts.pushBack(forwardTransform(xy(x, y+to_height))); 1951 subpath_data entry = subpath_data(4, true); 1952 lines.subpaths.pushBack(entry); 1953 affine_matrix saved_forward = current.forward; 1954 affine_matrix saved_inverse = current.inverse; 1955 translate( x + fminf( 0.0f, to_width ), 1956 y + fminf( 0.0f, to_height ) ); 1957 scale( fabsf( to_width ) / cast(float)( width ), 1958 fabsf( to_height ) / cast(float)( height ) ); 1959 render_main( image_brush ); 1960 current.forward = saved_forward; 1961 current.inverse = saved_inverse; 1962 } 1963 1964 1965 // ======== PIXEL MANIPULATION ======== 1966 1967 // Note: in original canvas_ity, there is a getImageData call, 1968 // and putImageData call, because the buffer is internal. 1969 // Dithering is applied on 1970 // export using this as luminance offset (index by [y&3][x&3]) 1971 // divided by 255. 1972 // But if we dither on each operation, the offset will 1973 // accumulate? 1974 // 1975 // static immutable float[4][4] bayer = [ 1976 // [ 0.03125f, 0.53125f, 0.15625f, 0.65625f ], 1977 // [ 0.78125f, 0.28125f, 0.90625f, 0.40625f ], 1978 // [ 0.21875f, 0.71875f, 0.09375f, 0.59375f ], 1979 // [ 0.96875f, 0.46875f, 0.84375f, 0.34375f ] 1980 // ]; 1981 1982 // ======== CANVAS STATE ======== 1983 1984 // Maximum number of times you can call save() and have things restored. 1985 // If you exceed this limit, it will crash. 1986 enum maxSaveRestoreDepth = 15; 1987 1988 /** 1989 Save the current state as though to a stack. 1990 1991 The full state of the canvas is saved, except for the pixels 1992 in the canvas buffer, and the current path. 1993 1994 TODO: this isn't strictly true, as the pattern image isn't 1995 saved. 1996 1997 Tip: to be able to reset the current clip region, save the 1998 canvas state first before clipping then restore the state 1999 to reset it. 2000 */ 2001 void save() 2002 { 2003 // PERF: state index into resources and just hold an index 2004 // to brushes/fonts/gradients. 2005 2006 // PERF: states are still kept in the stack, so that their 2007 // allocations are reused 2008 // First push a .init state without data 2009 int lastTop = _stateCount - 1; 2010 _state[lastTop + 1] = _state[lastTop]; 2011 _stateCount++; 2012 assert(_stateCount <= maxSaveRestoreDepth); 2013 } 2014 2015 /** 2016 Restore a previously saved state as though from a stack. 2017 */ 2018 void restore() { 2019 2020 // too many restore() without corresponding save() 2021 if (_stateCount <= 1) 2022 assert(false); 2023 2024 _stateCount--; 2025 } 2026 2027 // non-copyable 2028 @disable this(this); 2029 2030 private: 2031 2032 enum brush_type 2033 { 2034 fill_style, 2035 stroke_style 2036 } 2037 2038 int size_x; 2039 int size_y; 2040 2041 xy forwardTransform(xy pt) 2042 { 2043 return matrix_mul_vec(current.forward, pt); 2044 } 2045 2046 xy inverseTransform(xy pt) 2047 { 2048 return matrix_mul_vec(current.inverse, pt); 2049 } 2050 2051 // Canvas state. It is store on a stack by `save`/`restore` calls. 2052 struct State 2053 { 2054 nothrow @nogc: 2055 @disable this(this); 2056 CanvasCompositeOp op = CanvasCompositeOp.sourceOver; 2057 float shadow_offset_x = 0.0f; 2058 float shadow_offset_y = 0.0f; 2059 CanvasLineCap line_cap = CanvasLineCap.butt; 2060 CanvasLineJoin line_join = CanvasLineJoin.miter; 2061 float line_dash_offset = 0.0f; 2062 align_style text_align = align_style.start; 2063 baseline_style text_baseline = baseline_style.alphabetic; 2064 affine_matrix forward = affine_matrix.identity; 2065 affine_matrix inverse = affine_matrix.identity; 2066 float global_alpha = 1.0f; 2067 rgba shadow_color = rgba(0.0f, 0.0f, 0.0f, 0.0f); 2068 float shadow_blur = 0.0f; 2069 float line_width = 1.0f; 2070 float miter_limit = 10.0f; 2071 Vec!float line_dash; 2072 paint_brush fill_brush; 2073 paint_brush stroke_brush; 2074 Vec!pixel_run mask; 2075 font_face face; 2076 2077 // that assign ensures amortized allocation by reusing vectors 2078 void opAssign(ref const(State) other) { 2079 2080 this.op = other.op; 2081 this.shadow_offset_x = other.shadow_offset_x; 2082 this.shadow_offset_y = other.shadow_offset_y; 2083 this.line_cap = other.line_cap; 2084 this.line_join = other.line_join; 2085 this.line_dash_offset = other.line_dash_offset; 2086 this.text_align = other.text_align; 2087 this.text_baseline = other.text_baseline; 2088 this.forward = other.forward; 2089 this.inverse = other.inverse; 2090 this.global_alpha = other.global_alpha; 2091 this.shadow_color = other.shadow_color; 2092 this.shadow_blur = other.shadow_blur; 2093 this.line_width = other.line_width; 2094 this.miter_limit = other.miter_limit; 2095 assign_vec!float(this.line_dash, other.line_dash); 2096 this.fill_brush = other.fill_brush; 2097 this.stroke_brush = other.stroke_brush; 2098 assign_vec!pixel_run(this.mask, other.mask); 2099 this.face = other.face; 2100 } 2101 2102 void free() { 2103 destroyNoGC(line_dash); 2104 destroyNoGC(stroke_brush); 2105 destroyNoGC(fill_brush); 2106 destroyNoGC(mask); 2107 } 2108 2109 } 2110 2111 paint_brush image_brush; // Note: not sure why not in State 2112 paint_brush temp_brush; 2113 bezier_path path; 2114 line_path lines; 2115 line_path scratch; 2116 Vec!pixel_run runs; 2117 2118 bool initialized = false; 2119 Image outBitmap; 2120 2121 Vec!float shadow; 2122 Vec!ubyte scanBuf; 2123 PixelType interType; 2124 Vec!ubyte interBuf; 2125 2126 // State stack. 2127 // +1 to be able to call `save()` maxSaveRestoreDepth times. 2128 int _stateCount = 0; 2129 State* _state; 2130 CanvasOptions options; 2131 2132 // ".current" state is the last element of that stack. 2133 // Holds current color, transforms, etc. 2134 State* current() pure { 2135 return _state + (_stateCount - 1); 2136 } 2137 2138 void set_color(brush_type type, float red, float green, 2139 float blue, float alpha ) 2140 { 2141 paint_brush* brush = type == brush_type.fill_style ? 2142 &(current.fill_brush) 2143 : &(current.stroke_brush); 2144 brush.type = paint_brush.types.color; 2145 brush.colors.clearContents(); 2146 2147 rgba c = rgba(red, green, blue, alpha); 2148 c = clamped(c); 2149 fromGammaSpace((&c)[0..1], options.gammaCurve); 2150 brush.colors.pushBack( premultiplied(c) ); 2151 } 2152 2153 // Tessellate (at low-level) a cubic Bezier curve and add it to the polyline 2154 // data. This recursively splits the curve until two criteria are met 2155 // (subject to a hard recursion depth limit). First, the control points 2156 // must not be farther from the line between the endpoints than the tolerance. 2157 // By the Bezier convex hull property, this ensures that the distance between 2158 // the true curve and the polyline approximation will be no more than the 2159 // tolerance. Secondly, it takes the cosine of an angular turn limit; the 2160 // curve will be split until it turns less than this amount. This is used 2161 // for stroking, with the angular limit chosen such that the sagita of an arc 2162 // with that angle and a half-stroke radius will be equal to the tolerance. 2163 // This keeps expanded strokes approximately within tolerance. Note that 2164 // in the base case, it adds the control points as well as the end points. 2165 // This way, stroke expansion infers the correct tangents from the ends of 2166 // the polylines. 2167 // 2168 void add_tessellation(xy point_1, xy control_1, xy control_2, xy point_2, float angular, int limit ) 2169 { 2170 enum float tolerance = 0.125f; 2171 float flatness = tolerance * tolerance; 2172 xy edge_1 = control_1 - point_1; 2173 xy edge_2 = control_2 - control_1; 2174 xy edge_3 = point_2 - control_2; 2175 xy segment = point_2 - point_1; 2176 float squared_1 = dot( edge_1, edge_1 ); 2177 float squared_2 = dot( edge_2, edge_2 ); 2178 float squared_3 = dot( edge_3, edge_3 ); 2179 enum float epsilon = 1.0e-4f; 2180 float length_squared = dot( segment, segment ); 2181 if (length_squared < epsilon) length_squared = epsilon; 2182 float projection_1 = dot( edge_1, segment ) / length_squared; 2183 float projection_2 = dot( edge_3, segment ) / length_squared; 2184 float clamped_1 = projection_1; 2185 if (clamped_1 < 0) clamped_1 = 0; 2186 if (clamped_1 > 1) clamped_1 = 1; 2187 float clamped_2 = projection_2; 2188 if (clamped_2 < 0) clamped_2 = 0; 2189 if (clamped_2 > 1) clamped_2 = 1; 2190 xy to_line_1 = point_1 + clamped_1 * segment - control_1; 2191 xy to_line_2 = point_2 - clamped_2 * segment - control_2; 2192 float cosine = 1.0f; 2193 if ( angular > -1.0f ) 2194 { 2195 if ( squared_1 * squared_3 != 0.0f ) 2196 cosine = dot( edge_1, edge_3 ) / sqrtf( squared_1 * squared_3 ); 2197 else if ( squared_1 * squared_2 != 0.0f ) 2198 cosine = dot( edge_1, edge_2 ) / sqrtf( squared_1 * squared_2 ); 2199 else if ( squared_2 * squared_3 != 0.0f ) 2200 cosine = dot( edge_2, edge_3 ) / sqrtf( squared_2 * squared_3 ); 2201 } 2202 if ( ( dot( to_line_1, to_line_1 ) <= flatness && 2203 dot( to_line_2, to_line_2 ) <= flatness && 2204 cosine >= angular ) || 2205 !limit ) 2206 { 2207 if ( angular > -1.0f && squared_1 != 0.0f ) 2208 lines.points.pushBack( control_1 ); 2209 if ( angular > -1.0f && squared_2 != 0.0f ) 2210 lines.points.pushBack( control_2 ); 2211 if ( angular == -1.0f || squared_3 != 0.0f ) 2212 lines.points.pushBack( point_2 ); 2213 return; 2214 } 2215 xy left_1 = lerp( point_1, control_1, 0.5f ); 2216 xy middle = lerp( control_1, control_2, 0.5f ); 2217 xy right_2 = lerp( control_2, point_2, 0.5f ); 2218 xy left_2 = lerp( left_1, middle, 0.5f ); 2219 xy right_1 = lerp( middle, right_2, 0.5f ); 2220 xy split = lerp( left_2, right_1, 0.5f ); 2221 add_tessellation( point_1, left_1, left_2, split, angular, limit - 1 ); 2222 add_tessellation( split, right_1, right_2, point_2, angular, limit - 1 ); 2223 } 2224 2225 // Tessellate (at high-level) a cubic Bezier curve and add it to the polyline 2226 // data. This solves both for the extreme in curvature and for the horizontal 2227 // and vertical extrema. It then splits the curve into segments at these 2228 // points and passes them off to the lower-level recursive tessellation. 2229 // This preconditioning means the polyline exactly includes any cusps or 2230 // ends of tight loops without the flatness test needing to locate it via 2231 // bisection, and the angular limit test can use simple dot products without 2232 // fear of curves turning more than 90 degrees. 2233 // 2234 void add_bezier(xy point_1, xy control_1, 2235 xy control_2, 2236 xy point_2, 2237 float angular ) 2238 { 2239 xy edge_1 = control_1 - point_1; 2240 xy edge_2 = control_2 - control_1; 2241 xy edge_3 = point_2 - control_2; 2242 if ( dot( edge_1, edge_1 ) == 0.0f && 2243 dot( edge_3, edge_3 ) == 0.0f ) 2244 { 2245 lines.points.pushBack( point_2 ); 2246 return; 2247 } 2248 float[7] at = [ 0.0f, 1.0f, 0, 0, 0, 0, 0 ]; 2249 int cuts = 2; 2250 xy extrema_a = -9.0f * edge_2 + 3.0f * ( point_2 - point_1 ); 2251 xy extrema_b = 6.0f * ( point_1 + control_2 ) - 12.0f * control_1; 2252 xy extrema_c = 3.0f * edge_1; 2253 enum float epsilon = 1.0e-4f; 2254 if ( fabsf( extrema_a.x ) > epsilon ) 2255 { 2256 float discriminant = 2257 extrema_b.x * extrema_b.x - 4.0f * extrema_a.x * extrema_c.x; 2258 if ( discriminant >= 0.0f ) 2259 { 2260 float sign = extrema_b.x > 0.0f ? 1.0f : -1.0f; 2261 float term = -extrema_b.x - sign * sqrtf( discriminant ); 2262 float extremum_1 = term / ( 2.0f * extrema_a.x ); 2263 at[ cuts++ ] = extremum_1; 2264 at[ cuts++ ] = extrema_c.x / ( extrema_a.x * extremum_1 ); 2265 } 2266 } 2267 else if ( fabsf( extrema_b.x ) > epsilon ) 2268 at[ cuts++ ] = -extrema_c.x / extrema_b.x; 2269 if ( fabsf( extrema_a.y ) > epsilon ) 2270 { 2271 float discriminant = 2272 extrema_b.y * extrema_b.y - 4.0f * extrema_a.y * extrema_c.y; 2273 if ( discriminant >= 0.0f ) 2274 { 2275 float sign = extrema_b.y > 0.0f ? 1.0f : -1.0f; 2276 float term = -extrema_b.y - sign * sqrtf( discriminant ); 2277 float extremum_1 = term / ( 2.0f * extrema_a.y ); 2278 at[ cuts++ ] = extremum_1; 2279 at[ cuts++ ] = extrema_c.y / ( extrema_a.y * extremum_1 ); 2280 } 2281 } 2282 else if ( fabsf( extrema_b.y ) > epsilon ) 2283 at[ cuts++ ] = -extrema_c.y / extrema_b.y; 2284 float determinant_1 = dot( perpendicular( edge_1 ), edge_2 ); 2285 float determinant_2 = dot( perpendicular( edge_1 ), edge_3 ); 2286 float determinant_3 = dot( perpendicular( edge_2 ), edge_3 ); 2287 float curve_a = determinant_1 - determinant_2 + determinant_3; 2288 float curve_b = -2.0f * determinant_1 + determinant_2; 2289 if ( fabsf( curve_a ) > epsilon && 2290 fabsf( curve_b ) > epsilon ) 2291 at[ cuts++ ] = -0.5f * curve_b / curve_a; 2292 for ( int index = 1; index < cuts; ++index ) 2293 { 2294 float value = at[ index ]; 2295 int sorted = index - 1; 2296 for ( ; 0 <= sorted && value < at[ sorted ]; --sorted ) 2297 at[ sorted + 1 ] = at[ sorted ]; 2298 at[ sorted + 1 ] = value; 2299 } 2300 xy split_point_1 = point_1; 2301 for ( int index = 0; index < cuts - 1; ++index ) 2302 { 2303 if ( !( 0.0f <= at[ index ] && at[ index + 1 ] <= 1.0f && 2304 at[ index ] != at[ index + 1 ] ) ) 2305 continue; 2306 float ratio = at[ index ] / at[ index + 1 ]; 2307 xy partial_1 = lerp( point_1, control_1, at[ index + 1 ] ); 2308 xy partial_2 = lerp( control_1, control_2, at[ index + 1 ] ); 2309 xy partial_3 = lerp( control_2, point_2, at[ index + 1 ] ); 2310 xy partial_4 = lerp( partial_1, partial_2, at[ index + 1 ] ); 2311 xy partial_5 = lerp( partial_2, partial_3, at[ index + 1 ] ); 2312 xy partial_6 = lerp( partial_1, partial_4, ratio ); 2313 xy split_point_2 = lerp( partial_4, partial_5, at[ index + 1 ] ); 2314 xy split_control_2 = lerp( partial_4, split_point_2, ratio ); 2315 xy split_control_1 = lerp( partial_6, split_control_2, ratio ); 2316 add_tessellation( split_point_1, split_control_1, 2317 split_control_2, split_point_2, 2318 angular, 20 ); 2319 split_point_1 = split_point_2; 2320 } 2321 } 2322 2323 2324 // Convert the current path to a set of polylines. This walks over the 2325 // complete set of subpaths in the current path (stored as sets of cubic 2326 // Beziers) and converts each Bezier curve segment to a polyline while 2327 // preserving information about where subpaths begin and end and whether 2328 // they are closed or open. This replaces the previous polyline data. 2329 // 2330 void path_to_lines(bool stroking ) 2331 { 2332 enum float tolerance = 0.125f; 2333 float ratio = tolerance / fmaxf( 0.5f * current.line_width, tolerance ); 2334 float angular = stroking ? ( ratio - 2.0f ) * ratio * 2.0f + 1.0f : -1.0f; 2335 lines.points.clearContents(); 2336 lines.subpaths.clearContents(); 2337 size_t index = 0; 2338 size_t ending = 0; 2339 for ( size_t subpath = 0; subpath < path.subpaths.length; ++subpath ) 2340 { 2341 ending += path.subpaths[ subpath ].count; 2342 size_t first = lines.points.length; 2343 xy point_1 = path.points[ index++ ]; 2344 lines.points.pushBack( point_1 ); 2345 for ( ; index < ending; index += 3 ) 2346 { 2347 xy control_1 = path.points[ index + 0 ]; 2348 xy control_2 = path.points[ index + 1 ]; 2349 xy point_2 = path.points[ index + 2 ]; 2350 add_bezier( point_1, control_1, control_2, point_2, angular ); 2351 point_1 = point_2; 2352 } 2353 subpath_data entry = subpath_data( 2354 lines.points.length - first, 2355 path.subpaths[ subpath ].closed ); 2356 lines.subpaths.pushBack( entry ); 2357 } 2358 } 2359 2360 // Add a text glyph directly to the polylines. Given a glyph index, this 2361 // parses the data for that glyph directly from the TTF glyph data table and 2362 // immediately tessellates it to a set of a polyline subpaths which it adds 2363 // to any subpaths already present. It uses the current transform matrix to 2364 // transform from the glyph's vertices in font units to the proper size and 2365 // position on the canvas. 2366 // 2367 void add_glyph( 2368 int glyph, 2369 float angular ) 2370 { 2371 int loc_format = unsigned_16( current.face.data, current.face.head + 50 ); 2372 int offset = current.face.glyf + ( loc_format ? 2373 signed_32( current.face.data, current.face.loca + glyph * 4 ) : 2374 unsigned_16( current.face.data, current.face.loca + glyph * 2 ) * 2 ); 2375 int next = current.face.glyf + ( loc_format ? 2376 signed_32( current.face.data, current.face.loca + glyph * 4 + 4 ) : 2377 unsigned_16( current.face.data, current.face.loca + glyph * 2 + 2 ) * 2 ); 2378 if ( offset == next ) 2379 return; 2380 int contours = signed_16( current.face.data, offset ); 2381 if ( contours < 0 ) 2382 { 2383 offset += 10; 2384 for ( ; ; ) 2385 { 2386 int flags = unsigned_16( current.face.data, offset ); 2387 int component = unsigned_16( current.face.data, offset + 2 ); 2388 if ( !( flags & 2 ) ) 2389 return; // Matching points are not supported 2390 float e = cast( float )( flags & 1 ? 2391 signed_16( current.face.data, offset + 4 ) : 2392 signed_8( current.face.data, offset + 4 ) ); 2393 float f = cast( float )( flags & 1 ? 2394 signed_16( current.face.data, offset + 6 ) : 2395 signed_8( current.face.data, offset + 5 ) ); 2396 offset += flags & 1 ? 8 : 6; 2397 float a = flags & 200 ? cast( float )( 2398 signed_16( current.face.data, offset ) ) / 16384.0f : 1.0f; 2399 float b = flags & 128 ? cast( float )( 2400 signed_16( current.face.data, offset + 2 ) ) / 16384.0f : 0.0f; 2401 float c = flags & 128 ? cast( float )( 2402 signed_16( current.face.data, offset + 4 ) ) / 16384.0f : 0.0f; 2403 float d = flags & 8 ? a : 2404 flags & 64 ? cast(float)( 2405 signed_16( current.face.data, offset + 2 ) ) / 16384.0f : 2406 flags & 128 ? cast(float)( 2407 signed_16( current.face.data, offset + 6 ) ) / 16384.0f : 2408 1.0f; 2409 offset += flags & 8 ? 2 : flags & 64 ? 4 : flags & 128 ? 8 : 0; 2410 affine_matrix saved_forward = current.forward; 2411 affine_matrix saved_inverse = current.inverse; 2412 transform( a, b, c, d, e, f ); 2413 add_glyph( component, angular ); 2414 current.forward = saved_forward; 2415 current.inverse = saved_inverse; 2416 if ( !( flags & 32 ) ) 2417 return; 2418 } 2419 } 2420 int hmetrics = unsigned_16( current.face.data, current.face.hhea + 34 ); 2421 int left_side_bearing = glyph < hmetrics ? 2422 signed_16( current.face.data, current.face.hmtx + glyph * 4 + 2 ) : 2423 signed_16( current.face.data, current.face.hmtx + hmetrics * 2 + glyph * 2 ); 2424 int x_min = signed_16( current.face.data, offset + 2 ); 2425 int points = unsigned_16( current.face.data, offset + 8 + contours * 2 ) + 1; 2426 int instructions = unsigned_16( current.face.data, offset + 10 + contours * 2 ); 2427 int flags_array = offset + 12 + contours * 2 + instructions; 2428 int flags_size = 0; 2429 int x_size = 0; 2430 for ( int index = 0; index < points; ) 2431 { 2432 int flags = unsigned_8( current.face.data, flags_array + flags_size++ ); 2433 int repeated = flags & 8 ? 2434 unsigned_8( current.face.data, flags_array + flags_size++ ) + 1 : 1; 2435 x_size += repeated * ( flags & 2 ? 1 : flags & 16 ? 0 : 2 ); 2436 index += repeated; 2437 } 2438 int x_array = flags_array + flags_size; 2439 int y_array = x_array + x_size; 2440 int x = left_side_bearing - x_min; 2441 int y = 0; 2442 int flags = 0; 2443 int repeated = 0; 2444 int index = 0; 2445 for ( int contour = 0; contour < contours; ++contour ) 2446 { 2447 int beginning = index; 2448 int ending = unsigned_16( current.face.data, offset + 10 + contour * 2 ); 2449 xy begin_point = xy( 0.0f, 0.0f ); 2450 bool begin_on = false; 2451 xy end_point = xy( 0.0f, 0.0f ); 2452 bool end_on = false; 2453 size_t first = lines.points.length; 2454 for ( ; index <= ending; ++index ) 2455 { 2456 if ( repeated ) 2457 --repeated; 2458 else 2459 { 2460 flags = unsigned_8( current.face.data, flags_array++ ); 2461 if ( flags & 8 ) 2462 repeated = unsigned_8( current.face.data, flags_array++ ); 2463 } 2464 if ( flags & 2 ) 2465 x += ( unsigned_8( current.face.data, x_array ) * 2466 ( flags & 16 ? 1 : -1 ) ); 2467 else if ( !( flags & 16 ) ) 2468 x += signed_16( current.face.data, x_array ); 2469 if ( flags & 4 ) 2470 y += ( unsigned_8( current.face.data, y_array ) * 2471 ( flags & 32 ? 1 : -1 ) ); 2472 else if ( !( flags & 32 ) ) 2473 y += signed_16( current.face.data, y_array ); 2474 x_array += flags & 2 ? 1 : flags & 16 ? 0 : 2; 2475 y_array += flags & 4 ? 1 : flags & 32 ? 0 : 2; 2476 xy point = forwardTransform( xy( cast(float)( x ), 2477 cast(float)( y ) ) ); 2478 int on_curve = flags & 1; 2479 if ( index == beginning ) 2480 { 2481 begin_point = point; 2482 begin_on = on_curve != 0; 2483 if ( on_curve ) 2484 lines.points.pushBack( point ); 2485 } 2486 else 2487 { 2488 xy point_2 = on_curve ? point : 2489 lerp( end_point, point, 0.5f ); 2490 if ( lines.points.length == first || 2491 ( end_on && on_curve ) ) 2492 lines.points.pushBack( point_2 ); 2493 else if ( !end_on || on_curve ) 2494 { 2495 xy point_1 = lines.points[$-1]; 2496 xy control_1 = lerp( point_1, end_point, 2.0f / 3.0f ); 2497 xy control_2 = lerp( point_2, end_point, 2.0f / 3.0f ); 2498 add_bezier( point_1, control_1, control_2, point_2, 2499 angular ); 2500 } 2501 } 2502 end_point = point; 2503 end_on = on_curve != 0; 2504 } 2505 if ( begin_on ^ end_on ) 2506 { 2507 xy point_1 = lines.points[$-1]; 2508 xy point_2 = lines.points[ first ]; 2509 xy control = end_on ? begin_point : end_point; 2510 xy control_1 = lerp( point_1, control, 2.0f / 3.0f ); 2511 xy control_2 = lerp( point_2, control, 2.0f / 3.0f ); 2512 add_bezier( point_1, control_1, control_2, point_2, angular ); 2513 } 2514 else if ( !begin_on && !end_on ) 2515 { 2516 xy point_1 = lines.points[$-1]; 2517 xy split = lerp( begin_point, end_point, 0.5f ); 2518 xy point_2 = lines.points[ first ]; 2519 xy left_1 = lerp( point_1, end_point, 2.0f / 3.0f ); 2520 xy left_2 = lerp( split, end_point, 2.0f / 3.0f ); 2521 xy right_1 = lerp( split, begin_point, 2.0f / 3.0f ); 2522 xy right_2 = lerp( point_2, begin_point, 2.0f / 3.0f ); 2523 add_bezier( point_1, left_1, left_2, split, angular ); 2524 add_bezier( split, right_1, right_2, point_2, angular ); 2525 } 2526 lines.points.pushBack( lines.points[ first ] ); 2527 subpath_data entry = subpath_data(lines.points.length - first, true); 2528 lines.subpaths.pushBack( entry ); 2529 } 2530 } 2531 2532 // Decode the next codepoint from a null-terminated UTF-8 string to its glyph 2533 // index within the font. The index to the next codepoint in the string 2534 // is advanced accordingly. It checks for valid UTF-8 encoding, but not 2535 // for valid unicode codepoints. Where it finds an invalid encoding, it 2536 // decodes it as the Unicode replacement character (U+FFFD) and advances to 2537 // the invalid byte, per Unicode recommendation. It also replaces low-ASCII 2538 // whitespace characters with regular spaces. After decoding the codepoint, 2539 // it looks up the corresponding glyph index from the current font's character 2540 // map table, returning a glyph index of 0 for the .notdef character (i.e., 2541 // "tofu") if the font lacks a glyph for that codepoint. 2542 // 2543 int character_to_glyph(const(char)* text, ref int index ) 2544 { 2545 int bytes = ( ( text[ index ] & 0x80 ) == 0x00 ? 1 : 2546 ( text[ index ] & 0xe0 ) == 0xc0 ? 2 : 2547 ( text[ index ] & 0xf0 ) == 0xe0 ? 3 : 2548 ( text[ index ] & 0xf8 ) == 0xf0 ? 4 : 2549 0 ); 2550 const int[5] masks = [ 0x0, 0x7f, 0x1f, 0x0f, 0x07 ]; 2551 int codepoint = bytes ? text[ index ] & masks[ bytes ] : 0xfffd; 2552 ++index; 2553 while ( --bytes > 0 ) 2554 if ( ( text[ index ] & 0xc0 ) == 0x80 ) 2555 codepoint = codepoint << 6 | ( text[ index++ ] & 0x3f ); 2556 else 2557 { 2558 codepoint = 0xfffd; 2559 break; 2560 } 2561 if ( codepoint == '\t' || codepoint == '\v' || codepoint == '\f' || 2562 codepoint == '\r' || codepoint == '\n' ) 2563 codepoint = ' '; 2564 int tables = unsigned_16( current.face.data, current.face.cmap + 2 ); 2565 int format_12 = 0; 2566 int format_4 = 0; 2567 int format_0 = 0; 2568 for ( int table = 0; table < tables; ++table ) 2569 { 2570 int platform = unsigned_16( current.face.data, current.face.cmap + table * 8 + 4 ); 2571 int encoding = unsigned_16( current.face.data, current.face.cmap + table * 8 + 6 ); 2572 int offset = signed_32( current.face.data, current.face.cmap + table * 8 + 8 ); 2573 int format = unsigned_16( current.face.data, current.face.cmap + offset ); 2574 if ( platform == 3 && encoding == 10 && format == 12 ) 2575 format_12 = current.face.cmap + offset; 2576 else if ( platform == 3 && encoding == 1 && format == 4 ) 2577 format_4 = current.face.cmap + offset; 2578 else if ( format == 0 ) 2579 format_0 = current.face.cmap + offset; 2580 } 2581 if ( format_12 ) 2582 { 2583 int groups = signed_32( current.face.data, format_12 + 12 ); 2584 for ( int group = 0; group < groups; ++group ) 2585 { 2586 int start = signed_32( current.face.data, format_12 + 16 + group * 12 ); 2587 int end = signed_32( current.face.data, format_12 + 20 + group * 12 ); 2588 int glyph = signed_32( current.face.data, format_12 + 24 + group * 12 ); 2589 if ( start <= codepoint && codepoint <= end ) 2590 return codepoint - start + glyph; 2591 } 2592 } 2593 else if ( format_4 ) 2594 { 2595 int segments = unsigned_16( current.face.data, format_4 + 6 ); 2596 int end_array = format_4 + 14; 2597 int start_array = end_array + 2 + segments; 2598 int delta_array = start_array + segments; 2599 int range_array = delta_array + segments; 2600 for ( int segment = 0; segment < segments; segment += 2 ) 2601 { 2602 int start = unsigned_16( current.face.data, start_array + segment ); 2603 int end = unsigned_16( current.face.data, end_array + segment ); 2604 int delta = signed_16( current.face.data, delta_array + segment ); 2605 int range = unsigned_16( current.face.data, range_array + segment ); 2606 if ( start <= codepoint && codepoint <= end ) 2607 return range ? 2608 unsigned_16( current.face.data, range_array + segment + 2609 ( codepoint - start ) * 2 + range ) : 2610 ( codepoint + delta ) & 0xffff; 2611 } 2612 } 2613 else if ( format_0 && 0 <= codepoint && codepoint < 256 ) 2614 return unsigned_8( current.face.data, format_0 + 6 + codepoint ); 2615 return 0; 2616 } 2617 2618 // Convert a text string to a set of polylines. This works out the placement 2619 // of the text string relative to the anchor position. Then it walks through 2620 // the string, sizing and placing each character by temporarily changing the 2621 // current transform matrix to map from font units to canvas pixel coordinates 2622 // before adding the glyph to the polylines. This replaces the previous 2623 // polyline data. 2624 // 2625 void text_to_lines(const(char)* text, xy position, float maximum_width, bool stroking ) 2626 { 2627 enum float tolerance = 0.125f; 2628 float ratio = tolerance / fmaxf( 0.5f * current.line_width, tolerance ); 2629 float angular = stroking ? ( ratio - 2.0f ) * ratio * 2.0f + 1.0f : -1.0f; 2630 lines.points.clearContents(); 2631 lines.subpaths.clearContents(); 2632 if ( (current.face.data.length == 0) || !text || maximum_width <= 0.0f ) 2633 return; 2634 float width = maximum_width == 1.0e30f && current.text_align == align_style.leftward ? 0.0f : 2635 measure_text( text ); 2636 float reduction = maximum_width / fmaxf( maximum_width, width ); 2637 if ( current.text_align == align_style.rightward ) 2638 position.x -= width * reduction; 2639 else if ( current.text_align == align_style.center ) 2640 position.x -= 0.5f * width * reduction; 2641 xy scaling = current.face.scale * xy( reduction, 1.0f ); 2642 float units_per_em = cast(float)( 2643 unsigned_16( current.face.data, current.face.head + 18 ) ); 2644 float ascender = cast(float)( 2645 signed_16( current.face.data, current.face.os_2 + 68 ) ); 2646 float descender = cast(float)( 2647 signed_16( current.face.data, current.face.os_2 + 70 ) ); 2648 float normalize = current.face.scale * units_per_em / ( ascender - descender ); 2649 if ( current.text_baseline == baseline_style.top ) 2650 position.y += ascender * normalize; 2651 else if ( current.text_baseline == baseline_style.middle ) 2652 position.y += ( ascender + descender ) * 0.5f * normalize; 2653 else if ( current.text_baseline == baseline_style.bottom ) 2654 position.y += descender * normalize; 2655 else if ( current.text_baseline == baseline_style.hanging ) 2656 position.y += 0.6f * current.face.scale * units_per_em; 2657 affine_matrix saved_forward = current.forward; 2658 affine_matrix saved_inverse = current.inverse; 2659 int hmetrics = unsigned_16( current.face.data, current.face.hhea + 34 ); 2660 int place = 0; 2661 for ( int index = 0; text[ index ]; ) 2662 { 2663 int glyph = character_to_glyph( text, index ); 2664 current.forward = saved_forward; 2665 transform( scaling.x, 0.0f, 0.0f, -scaling.y, 2666 position.x + cast(float)( place ) * scaling.x, 2667 position.y ); 2668 add_glyph( glyph, angular ); 2669 int entry = min_int( glyph, hmetrics - 1 ); 2670 place += unsigned_16( current.face.data, current.face.hmtx + entry * 4 ); 2671 } 2672 current.forward = saved_forward; 2673 current.inverse = saved_inverse; 2674 } 2675 2676 2677 // Break the polylines into smaller pieces according to the dash settings. 2678 // This walks along the polyline subpaths and dash pattern together, emitting 2679 // new points via lerping where dash segments begin and end. Each dash 2680 // segment becomes a new open subpath in the polyline. Some care is to 2681 // taken to handle two special cases of closed subpaths. First, those that 2682 // are completely within the first dash segment should be emitted as-is and 2683 // remain closed. Secondly, those that start and end within a dash should 2684 // have the two dashes merged together so that the lines join. This replaces 2685 // the previous polyline data. 2686 // 2687 void dash_lines() 2688 { 2689 if ( current.line_dash.length == 0 ) 2690 return; 2691 2692 assign_vec!xy(scratch.points, lines.points); 2693 lines.points.clearContents(); 2694 2695 assign_vec!subpath_data(scratch.subpaths, lines.subpaths); 2696 lines.subpaths.clearContents(); 2697 2698 float total = 0; 2699 foreach(ld; current.line_dash[]) 2700 { 2701 total += ld; 2702 } 2703 float offset = fmodf( current.line_dash_offset, total ); 2704 if ( offset < 0.0f ) 2705 offset += total; 2706 size_t start = 0; 2707 while ( offset >= current.line_dash[ start ] ) 2708 { 2709 offset -= current.line_dash[ start ]; 2710 start = start + 1 < current.line_dash.length ? start + 1 : 0; 2711 } 2712 size_t ending = 0; 2713 for ( size_t subpath = 0; subpath < scratch.subpaths.length; ++subpath ) 2714 { 2715 size_t index = ending; 2716 ending += scratch.subpaths[ subpath ].count; 2717 size_t first = lines.points.length; 2718 size_t segment = start; 2719 bool emit = ~start & 1; 2720 size_t merge_point = lines.points.length; 2721 size_t merge_subpath = lines.subpaths.length; 2722 bool merge_emit = emit; 2723 float next = current.line_dash[ start ] - offset; 2724 for ( ; index + 1 < ending; ++index ) 2725 { 2726 xy from = scratch.points[ index ]; 2727 xy to = scratch.points[ index + 1 ]; 2728 if ( emit ) 2729 lines.points.pushBack( from ); 2730 float line = length( inverseTransform(to) - inverseTransform(from) ); 2731 while ( next < line ) 2732 { 2733 lines.points.pushBack( lerp( from, to, next / line ) ); 2734 if ( emit ) 2735 { 2736 subpath_data entry = { 2737 lines.points.length - first, false }; 2738 lines.subpaths.pushBack( entry ); 2739 first = lines.points.length; 2740 } 2741 segment = segment + 1 < current.line_dash.length ? segment + 1 : 0; 2742 emit = !emit; 2743 next += current.line_dash[ segment ]; 2744 } 2745 next -= line; 2746 } 2747 if ( emit ) 2748 { 2749 lines.points.pushBack( scratch.points[ index ] ); 2750 subpath_data entry = { lines.points.length - first, false }; 2751 lines.subpaths.pushBack( entry ); 2752 if ( scratch.subpaths[ subpath ].closed && merge_emit ) 2753 { 2754 if ( lines.subpaths.length == merge_subpath + 1 ) 2755 lines.subpaths[$-1].closed = true; 2756 else 2757 { 2758 size_t count = lines.subpaths[$-1].count; 2759 rotateArray!xy(lines.points[], merge_point, lines.points.length - count, lines.points.length); 2760 lines.subpaths[ merge_subpath ].count += count; 2761 lines.subpaths.popBack(); 2762 } 2763 } 2764 } 2765 } 2766 } 2767 2768 // std::rotate translation 2769 // first - the beginning of the original range 2770 // middle - the element that should appear at the beginning of the rotated range 2771 // last - the end of the original range 2772 size_t rotateArray(T)(T[] arr, size_t first, size_t middle, size_t last) 2773 { 2774 if (first == middle) 2775 return last; 2776 2777 if (middle == last) 2778 return first; 2779 2780 size_t write = first; 2781 size_t next_read = first; // read position for when “read” hits “last” 2782 2783 for (size_t read = middle; read != last; ++write, ++read) 2784 { 2785 if (write == next_read) 2786 next_read = read; // track where “first” went 2787 T tmp = arr[write]; 2788 arr[write] = arr[read]; 2789 arr[read] = tmp; 2790 } 2791 2792 // rotate the remaining sequence into place 2793 rotateArray(arr, write, next_read, last); 2794 return write; 2795 } 2796 2797 /* 2798 Set filling or stroking to draw with an image pattern. 2799 2800 Initially, pixels in the pattern correspond exactly to pixels 2801 on the canvas, with the pattern starting in the upper left. 2802 The pattern is affected by the current transform at the time 2803 of drawing, and the pattern will be resampled as needed (with 2804 the filtering always wrapping regardless of the repetition 2805 setting). The pattern can be repeated either horizontally, 2806 vertically, both, or neither, relative to the source image. If 2807 the pattern is not repeated, then beyond it will be considered 2808 transparent black. The pattern image, which should be in top 2809 to bottom rows of contiguous pixels from left to right, is 2810 copied and it is safe to change or destroy it after this call. 2811 The width and height must both be positive. If either are not, 2812 or the image pointer is null, this does nothing. 2813 2814 Tip: to use a small piece of a larger image, reduce the width 2815 and height, and offset the image pointer while keeping 2816 the stride. 2817 2818 Params: 2819 type Whether to set the fill_style or stroke_style. 2820 image Unpremultiplied sRGB RGBA8 image data. 2821 width Width of the pattern image in pixels. 2822 height Height of the pattern image in pixels. 2823 stride Bytes between the start of each image row. 2824 repetition repeat, repeat_x, repeat_y, or no_repeat. 2825 */ 2826 void setPattern(brush_type type, 2827 const(ubyte)* image, 2828 int width, 2829 int height, 2830 int stride, 2831 repetition_style repetition) { 2832 if ( !image || width <= 0 || height <= 0 ) 2833 return; 2834 paint_brush* brush = type == brush_type.fill_style ? ¤t.fill_brush : ¤t.stroke_brush; 2835 brush.type = paint_brush.types.pattern; 2836 brush.colors.clearContents(); 2837 for ( int y = 0; y < height; ++y ) 2838 for ( int x = 0; x < width; ++x ) 2839 { 2840 int index = y * stride + x * 4; 2841 rgba color = rgba(image[ index + 0 ] / 255.0f, image[ index + 1 ] / 255.0f, 2842 image[ index + 2 ] / 255.0f, image[ index + 3 ] / 255.0f ); 2843 fromGammaSpace((&color)[0..1], options.gammaCurve); 2844 brush.colors.pushBack( premultiplied( color ) ); 2845 } 2846 brush.width = width; 2847 brush.height = height; 2848 brush.repetition = repetition; 2849 } 2850 2851 2852 // Trace along a series of points from a subpath in the scratch polylines 2853 // and add new points to the main polylines with the stroke expansion on 2854 // one side. Calling this again with the ends reversed adds the other 2855 // half of the stroke. If the original subpath was closed, each pass 2856 // adds a complete closed loop, with one adding the inside and one adding 2857 // the outside. The two will wind in opposite directions. If the original 2858 // subpath was open, each pass ends with one of the line caps and the two 2859 // passes together form a single closed loop. In either case, this handles 2860 // adding line joins, including inner joins. Care is taken to fill tight 2861 // inside turns correctly by adding additional windings. See Figure 10 of 2862 // "Converting Stroked Primitives to Filled Primitives" by Diego Nehab, for 2863 // the inspiration for these extra windings. 2864 // 2865 void add_half_stroke(size_t beginning, size_t ending, bool closed ) 2866 { 2867 float half = current.line_width * 0.5f; 2868 float ratio = current.miter_limit * current.miter_limit * half * half; 2869 xy in_direction = xy( 0.0f, 0.0f ); 2870 float in_length = 0.0f; 2871 xy point = inverseTransform(scratch.points[ beginning ]); 2872 size_t finish = beginning; 2873 size_t index = beginning; 2874 do 2875 { 2876 xy next = inverseTransform(scratch.points[ index ]); 2877 xy out_direction = normalized( next - point ); 2878 float out_length = length( next - point ); 2879 enum float epsilon = 1.0e-4f; 2880 if ( in_length != 0.0f && out_length >= epsilon ) 2881 { 2882 if ( closed && finish == beginning ) 2883 finish = index; 2884 xy side_in = point + half * perpendicular( in_direction ); 2885 xy side_out = point + half * perpendicular( out_direction ); 2886 float turn = dot( perpendicular( in_direction ), out_direction ); 2887 if ( fabsf( turn ) < epsilon ) 2888 turn = 0.0f; 2889 xy offset = turn == 0.0f ? xy( 0.0f, 0.0f ) : 2890 half / turn * ( out_direction - in_direction ); 2891 bool tight = ( dot( offset, in_direction ) < -in_length && 2892 dot( offset, out_direction ) > out_length ); 2893 if ( turn > 0.0f && tight ) 2894 { 2895 swap_xy(side_in, side_out ); 2896 swap_xy( in_direction, out_direction ); 2897 // PERF 2898 lines.points.pushBack( forwardTransform(side_out) ); 2899 lines.points.pushBack( forwardTransform(point) ); 2900 lines.points.pushBack( forwardTransform(side_in) ); 2901 } 2902 if ( ( turn > 0.0f && !tight ) || 2903 ( turn != 0.0f && current.line_join == CanvasLineJoin.miter && 2904 dot( offset, offset ) <= ratio ) ) 2905 lines.points.pushBack( forwardTransform( point + offset ) ); 2906 else if ( current.line_join == CanvasLineJoin.round ) 2907 { 2908 float cosine = dot( in_direction, out_direction ); 2909 float angle = acosf(fminf( fmaxf( cosine, -1.0f ), 1.0f ) ); 2910 float alpha = 4.0f / 3.0f * tanf( 0.25f * angle ); 2911 lines.points.pushBack( forwardTransform(side_in )); 2912 add_bezier( 2913 forwardTransform(side_in), 2914 forwardTransform(( side_in + alpha * half * in_direction )), 2915 forwardTransform(( side_out - alpha * half * out_direction )), 2916 forwardTransform(side_out), 2917 -1.0f ); 2918 } 2919 else 2920 { 2921 lines.points.pushBack( forwardTransform(side_in )); 2922 lines.points.pushBack( forwardTransform(side_out )); 2923 } 2924 if ( turn > 0.0f && tight ) 2925 { 2926 lines.points.pushBack( forwardTransform(side_out )); 2927 lines.points.pushBack( forwardTransform(point )); 2928 lines.points.pushBack( forwardTransform(side_in )); 2929 swap_xy( in_direction, out_direction ); 2930 } 2931 } 2932 if ( out_length >= epsilon ) 2933 { 2934 in_direction = out_direction; 2935 in_length = out_length; 2936 point = next; 2937 } 2938 index = ( index == ending ? beginning : 2939 ending > beginning ? index + 1 : 2940 index - 1 ); 2941 } while ( index != finish ); 2942 if ( closed || in_length == 0.0f ) 2943 return; 2944 xy ahead = half * in_direction; 2945 xy side = perpendicular( ahead ); 2946 if ( current.line_cap == CanvasLineCap.butt ) 2947 { 2948 lines.points.pushBack( forwardTransform(( point + side ) )); 2949 lines.points.pushBack( forwardTransform(( point - side ) )); 2950 } 2951 else if ( current.line_cap == CanvasLineCap.square ) 2952 { 2953 lines.points.pushBack( forwardTransform(( point + ahead + side ) )); 2954 lines.points.pushBack( forwardTransform(( point + ahead - side ) )); 2955 } 2956 else if ( current.line_cap == CanvasLineCap.circle ) 2957 { 2958 enum float alpha = 0.55228475f; // 4/3*tan(pi/8) 2959 lines.points.pushBack( forwardTransform(( point + side ) )); 2960 add_bezier( forwardTransform( point + side ), 2961 forwardTransform( point + side + alpha * ahead ), 2962 forwardTransform( point + ahead + alpha * side ), 2963 forwardTransform( point + ahead ), 2964 -1.0f ); 2965 add_bezier( forwardTransform( point + ahead ), 2966 forwardTransform( point + ahead - alpha * side ), 2967 forwardTransform( point - side + alpha * ahead ), 2968 forwardTransform( point - side ), 2969 -1.0f ); 2970 } 2971 } 2972 2973 // Perform stroke expansion on the polylines. After first breaking them up 2974 // according to the dash pattern (if any), it then moves the polyline data to 2975 // the scratch space. Each subpath is then traced both forwards and backwards 2976 // to add the points for a half stroke, which together create the points for 2977 // one (if the original subpath was open) or two (if it was closed) new closed 2978 // subpaths which, when filled, will draw the stroked lines. While the lower 2979 // level code this calls only adds the points of the half strokes, this 2980 // adds subpath information for the strokes. This replaces the previous 2981 // polyline data. 2982 // 2983 void stroke_lines() 2984 { 2985 affine_matrix fwd = current.forward; 2986 if ( fwd.a * fwd.d - fwd.b * fwd.c == 0.0f ) 2987 return; 2988 dash_lines(); 2989 2990 assign_vec!xy(scratch.points, lines.points); 2991 lines.points.clearContents(); 2992 2993 assign_vec!subpath_data(scratch.subpaths, lines.subpaths); 2994 lines.subpaths.clearContents(); 2995 2996 size_t ending = 0; 2997 for ( size_t subpath = 0; subpath < scratch.subpaths.length; ++subpath ) 2998 { 2999 size_t beginning = ending; 3000 ending += scratch.subpaths[ subpath ].count; 3001 if ( ending - beginning < 2 ) 3002 continue; 3003 size_t first = lines.points.length; 3004 add_half_stroke( beginning, ending - 1, 3005 scratch.subpaths[ subpath ].closed ); 3006 if ( scratch.subpaths[ subpath ].closed ) 3007 { 3008 subpath_data entry = { lines.points.length - first, true }; 3009 lines.subpaths.pushBack( entry ); 3010 first = lines.points.length; 3011 } 3012 add_half_stroke( ending - 1, beginning, 3013 scratch.subpaths[ subpath ].closed ); 3014 subpath_data entry = { lines.points.length - first, true }; 3015 lines.subpaths.pushBack( entry ); 3016 } 3017 } 3018 3019 // Scan-convert a single polyline segment. This walks along the pixels that 3020 // the segment touches in left-to-right order, using signed trapezoidal area 3021 // to accumulate a list of changes in signed coverage at each visible pixel 3022 // when processing them from left to right. Each run of horizontal pixels 3023 // ends with one final update to the right of the last pixel to bring the 3024 // total up to date. Note that this does not clip to the screen boundary. 3025 // 3026 void add_runs(xy from, xy to ) 3027 { 3028 enum float epsilon = 2.0e-5f; 3029 if ( fabsf( to.y - from.y ) < epsilon) 3030 return; 3031 float sign = to.y > from.y ? 1.0f : -1.0f; 3032 if ( from.x > to.x ) 3033 swap_xy( from, to ); 3034 xy now = from; 3035 xy pixel = xy( floorf( now.x ), floorf( now.y ) ); 3036 xy corner = pixel + xy( 1.0f, to.y > from.y ? 1.0f : 0.0f ); 3037 xy slope = xy( ( to.x - from.x ) / ( to.y - from.y ), 3038 ( to.y - from.y ) / ( to.x - from.x ) ); 3039 xy next_x = ( to.x - from.x < epsilon ) ? to : 3040 xy( corner.x, now.y + ( corner.x - now.x ) * slope.y ); 3041 xy next_y = xy( now.x + ( corner.y - now.y ) * slope.x, corner.y ); 3042 if ( ( from.y < to.y && to.y < next_y.y ) || 3043 ( from.y > to.y && to.y > next_y.y ) ) 3044 next_y = to; 3045 float y_step = to.y > from.y ? 1.0f : -1.0f; 3046 do 3047 { 3048 float carry = 0.0f; 3049 while ( next_x.x < next_y.x ) 3050 { 3051 float strip = fminf( fmaxf( ( next_x.y - now.y ) * y_step, 3052 0.0f ), 1.0f ); 3053 float mid = ( next_x.x + now.x ) * 0.5f; 3054 float area = ( mid - pixel.x ) * strip; 3055 pixel_run piece = pixel_run(cast(ushort)pixel.x, 3056 cast(ushort)pixel.y, 3057 ( carry + strip - area ) * sign ); 3058 runs.pushBack( piece ); 3059 carry = area; 3060 now = next_x; 3061 next_x.x += 1.0f; 3062 next_x.y = ( next_x.x - from.x ) * slope.y + from.y; 3063 pixel.x += 1.0f; 3064 } 3065 float strip = fminf( fmaxf( ( next_y.y - now.y ) * y_step, 3066 0.0f ), 1.0f ); 3067 float mid = ( next_y.x + now.x ) * 0.5f; 3068 float area = ( mid - pixel.x ) * strip; 3069 pixel_run piece_1 = pixel_run( cast(ushort)pixel.x, cast(ushort)pixel.y, ( carry + strip - area ) * sign ); 3070 pixel_run piece_2 = pixel_run( cast(ushort)(pixel.x + 1.0f ),cast(ushort)pixel.y, area * sign ); 3071 runs.pushBack( piece_1 ); 3072 runs.pushBack( piece_2 ); 3073 now = next_y; 3074 next_y.y += y_step; 3075 next_y.x = ( next_y.y - from.y ) * slope.x + from.x; 3076 pixel.y += y_step; 3077 if ( ( from.y < to.y && to.y < next_y.y ) || 3078 ( from.y > to.y && to.y > next_y.y ) ) 3079 next_y = to; 3080 } while ( now.y != to.y ); 3081 } 3082 3083 // Scan-convert the polylines to prepare them for antialiased rendering. 3084 // For each of the polyline loops, it first clips them to the screen. 3085 // See "Reentrant Polygon Clipping" by Sutherland and Hodgman for details. 3086 // Then it walks the polyline loop and scan-converts each line segment to 3087 // produce a list of changes in signed pixel coverage when processed in 3088 // left-to-right, top-to-bottom order. The list of changes is then sorted 3089 // into that order, and multiple changes to the same pixel are coalesced 3090 // by summation. The result is a sparse, run-length encoded description 3091 // of the coverage of each pixel to be drawn. 3092 // 3093 void lines_to_runs(xy offset, int padding ) 3094 { 3095 runs.clearContents(); 3096 float width = cast(float)( size_x + padding ); 3097 float height = cast(float)( size_y + padding ); 3098 size_t ending = 0; 3099 for ( size_t subpath = 0; subpath < lines.subpaths.length; ++subpath ) 3100 { 3101 size_t beginning = ending; 3102 ending += lines.subpaths[ subpath ].count; 3103 scratch.points.clearContents(); 3104 for ( size_t index = beginning; index < ending; ++index ) 3105 scratch.points.pushBack( offset + lines.points[ index ] ); 3106 for ( int edge = 0; edge < 4; ++edge ) 3107 { 3108 xy normal = xy( edge == 0 ? 1.0f : edge == 2 ? -1.0f : 0.0f, 3109 edge == 1 ? 1.0f : edge == 3 ? -1.0f : 0.0f ); 3110 float place = edge == 2 ? width : edge == 3 ? height : 0.0f; 3111 size_t first = scratch.points.length; 3112 for ( size_t index = 0; index < first; ++index ) 3113 { 3114 xy from = scratch.points[ ( index ? index : first ) - 1 ]; 3115 xy to = scratch.points[ index ]; 3116 float from_side = dot( from, normal ) + place; 3117 float to_side = dot( to, normal ) + place; 3118 if ( from_side * to_side < 0.0f ) 3119 scratch.points.pushBack( lerp( from, to, 3120 from_side / ( from_side - to_side ) ) ); 3121 if ( to_side >= 0.0f ) 3122 scratch.points.pushBack( to ); 3123 } 3124 3125 scratch.points.removeAndShiftRestOfArray(0, first); 3126 } 3127 size_t last = scratch.points.length; 3128 for ( size_t index = 0; index < last; ++index ) 3129 { 3130 xy from = scratch.points[ ( index ? index : last ) - 1 ]; 3131 xy to = scratch.points[ index ]; 3132 add_runs( xy( fminf( fmaxf( from.x, 0.0f ), width ), 3133 fminf( fmaxf( from.y, 0.0f ), height ) ), 3134 xy( fminf( fmaxf( to.x, 0.0f ), width ), 3135 fminf( fmaxf( to.y, 0.0f ), height ) ) ); 3136 } 3137 } 3138 if ( runs.isEmpty) 3139 return; 3140 3141 // copied here for the sake of being a delegate 3142 int comparePixelRuns(in pixel_run a, in pixel_run b ) 3143 { 3144 if (a.y != b.y) 3145 return a.y - b.y; 3146 if (a.x != b.x) 3147 return a.x - b.x; 3148 3149 float diff = fabsf(a.delta ) - fabsf( b.delta ); 3150 if (diff < 0) 3151 return -1; 3152 if (diff > 0) 3153 return 1; 3154 return 0; 3155 } 3156 3157 timSort!pixel_run(runs[], timsortBuf, &comparePixelRuns); 3158 3159 // PERF: why push the pixel runs with delta 0.0f or -0.0f? Sounds 3160 // like missed opportunity to sort and remove them later. 3161 3162 size_t to = 0; 3163 for ( size_t from = 1; from < runs.length; ++from ) 3164 { 3165 if ( runs[ from ].x == runs[ to ].x && runs[ from ].y == runs[ to ].y ) 3166 runs[ to ].delta += runs[ from ].delta; 3167 else if ( runs[ from ].delta != 0.0f ) 3168 runs[ ++to ] = runs[ from ]; 3169 } 3170 3171 to += 1; 3172 runs.resize(to); 3173 } 3174 3175 import dplug.core.vec: Vec; 3176 private Vec!pixel_run timsortBuf; // used as temp space 3177 3178 // Paint a pixel according to its point location and a paint style to produce 3179 // a premultiplied, linearized RGBA color. This handles all supported paint 3180 // styles: solid colors, linear gradients, radial gradients, and patterns. 3181 // For gradients and patterns, it takes into account the current transform. 3182 // Patterns are resampled using a separable bicubic convolution filter, 3183 // with edges handled according to the wrap mode. See "Cubic Convolution 3184 // Interpolation for Digital Image Processing" by Keys. This filter is best 3185 // known for magnification, but also works well for antialiased minification, 3186 // since it's actually a Catmull-Rom spline approximation of Lanczos-2. 3187 // 3188 rgba paint_pixel(xy point, ref const(paint_brush) brush ) 3189 { 3190 if ( brush.colors.isEmpty() ) 3191 return rgba( 0.0f, 0.0f, 0.0f, 0.0f ); 3192 if ( brush.type == paint_brush.types.color ) 3193 return brush.colors[0]; 3194 point = inverseTransform(point); 3195 affine_matrix inverse = current.inverse; 3196 if ( brush.type == paint_brush.types.pattern ) 3197 { 3198 float width = cast(float)( brush.width ); 3199 float height = cast(float)( brush.height ); 3200 if ( ( ( brush.repetition & 2 ) && 3201 ( point.x < 0.0f || width <= point.x ) ) || 3202 ( ( brush.repetition & 1 ) && 3203 ( point.y < 0.0f || height <= point.y ) ) ) 3204 return rgba( 0.0f, 0.0f, 0.0f, 0.0f ); 3205 float scale_x = fabsf( inverse.a ) + fabsf( inverse.c ); 3206 float scale_y = fabsf( inverse.b ) + fabsf( inverse.d ); 3207 scale_x = fmaxf( 1.0f, fminf( scale_x, width * 0.25f ) ); 3208 scale_y = fmaxf( 1.0f, fminf( scale_y, height * 0.25f ) ); 3209 float reciprocal_x = 1.0f / scale_x; 3210 float reciprocal_y = 1.0f / scale_y; 3211 point = point - xy( 0.5f, 0.5f ); 3212 int left = cast(int)( ceilf( point.x - scale_x * 2.0f ) ); 3213 int top = cast(int)( ceilf( point.y - scale_y * 2.0f ) ); 3214 int right = cast(int)( ceilf( point.x + scale_x * 2.0f ) ); 3215 int bottom = cast(int)( ceilf( point.y + scale_y * 2.0f ) ); 3216 rgba total_color = rgba( 0.0f, 0.0f, 0.0f, 0.0f ); 3217 float total_weight = 0.0f; 3218 for ( int pattern_y = top; pattern_y < bottom; ++pattern_y ) 3219 { 3220 float y = fabsf( reciprocal_y * 3221 ( cast(float)( pattern_y ) - point.y ) ); 3222 float weight_y = ( y < 1.0f ? 3223 ( 1.5f * y - 2.5f ) * y * y + 1.0f : 3224 ( ( -0.5f * y + 2.5f ) * y - 4.0f ) * y + 2.0f ); 3225 int wrapped_y = pattern_y % brush.height; 3226 if ( wrapped_y < 0 ) 3227 wrapped_y += brush.height; 3228 if ( &brush == &image_brush ) 3229 wrapped_y = min_int( max_int( pattern_y, 0 ), 3230 brush.height - 1 ); 3231 for ( int pattern_x = left; pattern_x < right; ++pattern_x ) 3232 { 3233 float x = fabsf( reciprocal_x * 3234 ( cast(float)( pattern_x ) - point.x ) ); 3235 float weight_x = ( x < 1.0f ? 3236 ( 1.5f * x - 2.5f ) * x * x + 1.0f : 3237 ( ( -0.5f * x + 2.5f ) * x - 4.0f ) * x + 2.0f ); 3238 int wrapped_x = pattern_x % brush.width; 3239 if ( wrapped_x < 0 ) 3240 wrapped_x += brush.width; 3241 if ( &brush == &image_brush ) 3242 wrapped_x = min_int( max_int( pattern_x, 0 ), 3243 brush.width - 1 ); 3244 float weight = weight_x * weight_y; 3245 size_t index = cast(size_t)(wrapped_y * brush.width + wrapped_x ); 3246 total_color = total_color + (weight * brush.colors[ index ]); 3247 total_weight += weight; 3248 } 3249 } 3250 return ( 1.0f / total_weight ) * total_color; 3251 } 3252 float offset; 3253 xy relative = point - brush.start; 3254 xy line = brush.end - brush.start; 3255 float gradient = dot( relative, line ); 3256 float span = dot( line, line ); 3257 if ( brush.type == paint_brush.types.linear ) 3258 { 3259 if ( span == 0.0f ) 3260 return rgba( 0.0f, 0.0f, 0.0f, 0.0f ); 3261 offset = gradient / span; 3262 } 3263 else 3264 { 3265 float initial = brush.start_radius; 3266 float change = brush.end_radius - initial; 3267 float a = span - change * change; 3268 float b = -2.0f * ( gradient + initial * change ); 3269 float c = dot( relative, relative ) - initial * initial; 3270 float discriminant = b * b - 4.0f * a * c; 3271 if ( discriminant < 0.0f || 3272 ( span == 0.0f && change == 0.0f ) ) 3273 return rgba( 0.0f, 0.0f, 0.0f, 0.0f ); 3274 float root = sqrtf( discriminant ); 3275 float reciprocal = 1.0f / ( 2.0f * a ); 3276 float offset_1 = ( -b - root ) * reciprocal; 3277 float offset_2 = ( -b + root ) * reciprocal; 3278 float radius_1 = initial + change * offset_1; 3279 float radius_2 = initial + change * offset_2; 3280 if ( radius_2 >= 0.0f ) 3281 offset = offset_2; 3282 else if ( radius_1 >= 0.0f ) 3283 offset = offset_1; 3284 else 3285 return rgba( 0.0f, 0.0f, 0.0f, 0.0f ); 3286 } 3287 3288 // Finds the first element in stop that is greater than offset. 3289 size_t index = brush.stops.length; 3290 for (size_t i = 0; i < brush.stops.length; ++i) 3291 { 3292 if ( brush.stops[i] > offset) 3293 { 3294 index = i; 3295 break; 3296 } 3297 } 3298 3299 if ( index == 0 ) 3300 return premultiplied( brush.colors[0] ); 3301 if ( index == brush.stops.length ) 3302 return premultiplied( brush.colors[$-1] ); 3303 float mix = ( ( offset - brush.stops[ index - 1 ] ) / 3304 ( brush.stops[ index ] - brush.stops[ index - 1 ] ) ); 3305 rgba delta = brush.colors[ index ] - brush.colors[ index - 1 ]; 3306 return premultiplied( brush.colors[ index - 1 ] + mix * delta ); 3307 } 3308 3309 // Render the shadow of the polylines into the pixel buffer if needed. After 3310 // computing the border as the maximum distance that one pixel can affect 3311 // another via the blur, it scan-converts the lines to runs with the shadow 3312 // offset and that extra amount of border padding. Then it bounds the scan 3313 // converted shape, pads this with border, clips that to the extended canvas 3314 // size, and rasterizes to fill a working area with the alpha. Next, a fast 3315 // approximation of a Gaussian blur is done using three passes of box blurs 3316 // each in the rows and columns. Note that these box blurs have a small extra 3317 // weight on the tails to allow for fractional widths. See "Theoretical 3318 // Foundations of Gaussian Convolution by Extended Box Filtering" by Gwosdek 3319 // et al. for details. Finally, it colors the blurred alpha image with 3320 // the shadow color and blends this into the pixel buffer according to the 3321 // compositing settings and clip mask. Note that it does not bother clearing 3322 // outside the area of the alpha image when the compositing settings require 3323 // clearing; that will be done on the subsequent main rendering pass. 3324 // 3325 void render_shadow(ref const(paint_brush) brush ) 3326 { 3327 if ( current.shadow_color.a == 0.0f || ( current.shadow_blur == 0.0f && 3328 current.shadow_offset_x == 0.0f && 3329 current.shadow_offset_y == 0.0f ) ) 3330 return; 3331 float sigma_squared = 0.25f * current.shadow_blur * current.shadow_blur; 3332 size_t radius = cast(size_t)( 3333 0.5f * sqrtf( 4.0f * sigma_squared + 1.0f ) - 0.5f ); 3334 int border = 3 * ( cast(int)( radius ) + 1 ); 3335 xy offset = xy( cast(float)( border ) + current.shadow_offset_x, 3336 cast(float)( border ) + current.shadow_offset_y ); 3337 lines_to_runs( offset, 2 * border ); 3338 int left = size_x + 2 * border; 3339 int right = 0; 3340 int top = size_y + 2 * border; 3341 int bottom = 0; 3342 for ( size_t index = 0; index < runs.length; ++index ) 3343 { 3344 left = min_int( left, cast(int)( runs[ index ].x ) ); 3345 right = max_int( right, cast(int)( runs[ index ].x ) ); 3346 top = min_int( top, cast(int)( runs[ index ].y ) ); 3347 bottom = max_int( bottom, cast(int)( runs[ index ].y ) ); 3348 } 3349 left = max_int( left - border, 0 ); 3350 right = min_int( right + border, size_x + 2 * border ) + 1; 3351 top = max_int( top - border, 0 ); 3352 bottom = min_int( bottom + border, size_y + 2 * border ); 3353 size_t width = cast(size_t)( max_int( right - left, 0 ) ); 3354 size_t height = cast(size_t)( max_int( bottom - top, 0 ) ); 3355 size_t working = width * height; 3356 shadow.clearContents(); 3357 shadow.resize( working + max_size_t( width, height ) ); 3358 memset(shadow.ptr, 0, float.sizeof * shadow.length); 3359 enum float threshold = 1.0f / 8160.0f; 3360 { 3361 int x = -1; 3362 int y = -1; 3363 float sum = 0.0f; 3364 for ( size_t index = 0; index < runs.length; ++index ) 3365 { 3366 pixel_run next = runs[ index ]; 3367 float coverage = fminf( fabsf( sum ), 1.0f ); 3368 int to = next.y == y ? next.x : x + 1; 3369 if ( coverage >= threshold ) 3370 for ( ; x < to; ++x ) 3371 shadow[ cast(size_t)( y - top ) * width + 3372 cast(size_t)( x - left ) ] = coverage * 3373 paint_pixel( xy( cast(float)( x ) + 0.5f, 3374 cast(float)( y ) + 0.5f ) - 3375 offset, brush ).a; 3376 if ( next.y != y ) 3377 sum = 0.0f; 3378 x = next.x; 3379 y = next.y; 3380 sum += next.delta; 3381 } 3382 } 3383 float alpha = cast(float)( 2 * radius + 1 ) * 3384 ( cast(float)( radius * ( radius + 1 ) ) - sigma_squared ) / 3385 ( 2.0f * sigma_squared - 3386 cast(float)( 6 * ( radius + 1 ) * ( radius + 1 ) ) ); 3387 float divisor = 2.0f * ( alpha + cast(float)( radius ) ) + 1.0f; 3388 float weight_1 = alpha / divisor; 3389 float weight_2 = ( 1.0f - alpha ) / divisor; 3390 for ( size_t y = 0; y < height; ++y ) 3391 for ( int pass = 0; pass < 3; ++pass ) 3392 { 3393 for ( size_t x = 0; x < width; ++x ) 3394 shadow[ working + x ] = shadow[ y * width + x ]; 3395 float running = weight_1 * shadow[ working + radius + 1 ]; 3396 for ( size_t x = 0; x <= radius; ++x ) 3397 running += ( weight_1 + weight_2 ) * shadow[ working + x ]; 3398 shadow[ y * width ] = running; 3399 for ( size_t x = 1; x < width; ++x ) 3400 { 3401 if ( x >= radius + 1 ) 3402 running -= weight_2 * shadow[ working + x - radius - 1 ]; 3403 if ( x >= radius + 2 ) 3404 running -= weight_1 * shadow[ working + x - radius - 2 ]; 3405 if ( x + radius < width ) 3406 running += weight_2 * shadow[ working + x + radius ]; 3407 if ( x + radius + 1 < width ) 3408 running += weight_1 * shadow[ working + x + radius + 1 ]; 3409 shadow[ y * width + x ] = running; 3410 } 3411 } 3412 for ( size_t x = 0; x < width; ++x ) 3413 for ( int pass = 0; pass < 3; ++pass ) 3414 { 3415 for ( size_t y = 0; y < height; ++y ) 3416 shadow[ working + y ] = shadow[ y * width + x ]; 3417 float running = weight_1 * shadow[ working + radius + 1 ]; 3418 for ( size_t y = 0; y <= radius; ++y ) 3419 running += ( weight_1 + weight_2 ) * shadow[ working + y ]; 3420 shadow[ x ] = running; 3421 for ( size_t y = 1; y < height; ++y ) 3422 { 3423 if ( y >= radius + 1 ) 3424 running -= weight_2 * shadow[ working + y - radius - 1 ]; 3425 if ( y >= radius + 2 ) 3426 running -= weight_1 * shadow[ working + y - radius - 2 ]; 3427 if ( y + radius < height ) 3428 running += weight_2 * shadow[ working + y + radius ]; 3429 if ( y + radius + 1 < height ) 3430 running += weight_1 * shadow[ working + y + radius + 1 ]; 3431 shadow[ y * width + x ] = running; 3432 } 3433 } 3434 int operation = current.op; 3435 int x = -1; 3436 int y = -1; 3437 float sum = 0.0f; 3438 for ( size_t index = 0; index < current.mask.length; ++index ) 3439 { 3440 pixel_run next = current.mask[ index ]; 3441 float visibility = fminf( fabsf( sum ), 1.0f ); 3442 int to = min_int( next.y == y ? next.x : x + 1, right - border ); 3443 if ( visibility >= threshold && 3444 top <= y + border && y + border < bottom ) { 3445 3446 int x_offset = pixelTypeSize(outBitmap.type()) * x; 3447 3448 // How many pixels to do at once? 3449 int scanWidth = to - x; 3450 3451 // 1. Convert pixels [x..to] to rgbaf32 3452 scanlinesConvert(outBitmap.type(), 3453 cast(ubyte*)(outBitmap.scanptr(y)) + x_offset, 3454 outBitmap.pitchInBytes(), 3455 PixelType.rgbaf32, 3456 scanBuf.ptr, 3457 0, 3458 scanWidth, 3459 1, 3460 interType, 3461 interBuf.ptr); 3462 3463 // 2. Background is made delinearized then premultiplied 3464 rgba* scan = cast(rgba*) scanBuf.ptr; 3465 fromGammaSpace(scan[0..scanWidth], options.gammaCurve); 3466 for(int n = 0; n < scanWidth; ++n) 3467 { 3468 scan[n] = premultiplied(scan[n]); 3469 } 3470 3471 // 3. Render shadow 3472 for(int n = 0; n < scanWidth; ++n) 3473 { 3474 rgba back = scan[n]; 3475 rgba fore = current.global_alpha * 3476 shadow[ 3477 cast(size_t)( y + border - top ) * width + 3478 cast(size_t)( x + n + border - left ) ] * 3479 current.shadow_color; 3480 float mix_fore = operation & 1 ? back.a : 0.0f; 3481 if ( operation & 2 ) 3482 mix_fore = 1.0f - mix_fore; 3483 float mix_back = operation & 4 ? fore.a : 0.0f; 3484 if ( operation & 8 ) 3485 mix_back = 1.0f - mix_back; 3486 rgba blend = mix_fore * fore + mix_back * back; 3487 blend.a = fminf( blend.a, 1.0f ); 3488 rgba colout = visibility * blend 3489 + ( 1.0f - visibility ) * back; 3490 scan[n] = colout; 3491 } 3492 3493 // 4. Unpremultiply and put back in gamma-space. 3494 for(int n = 0; n < scanWidth; ++n) 3495 { 3496 scan[n] = unpremultiplied(scan[n]); 3497 } 3498 toGammaSpace(scan[0..scanWidth], options.gammaCurve); 3499 3500 // 5. Convert pixels [x..to] to rgbaf32 3501 scanlinesConvert(PixelType.rgbaf32, 3502 scanBuf.ptr, 3503 0, 3504 outBitmap.type(), 3505 cast(ubyte*)(outBitmap.scanptr(y)) + x_offset, 3506 outBitmap.pitchInBytes(), 3507 3508 scanWidth, 3509 1, 3510 interType, 3511 interBuf.ptr); 3512 3513 } 3514 3515 if ( next.y != y ) 3516 sum = 0.0f; 3517 x = max_int( cast(int)( next.x ), left - border ); 3518 y = next.y; 3519 sum += next.delta; 3520 } 3521 } 3522 3523 // Render the polylines into the pixel buffer. It scan-converts the lines 3524 // to runs which represent changes to the signed fractional coverage when 3525 // read from left-to-right, top-to-bottom. It scans through these to 3526 // determine spans of pixels that need to be drawn, paints those pixels 3527 // according to the brush, and then blends them into the buffer according 3528 // to the current compositing settings. This is slightly more complicated 3529 // because it interleaves this with a simultaneous scan through a similar 3530 // set of runs representing the current clip mask to determine which pixels 3531 // it can composite into. Note that shadows are always drawn first. 3532 // 3533 void render_main(ref const(paint_brush) brush ) 3534 { 3535 if ( ! current.forward.isInvertible) 3536 return; 3537 3538 render_shadow( brush ); 3539 lines_to_runs( xy( 0.0f, 0.0f ), 0 ); 3540 int operation = current.op; 3541 int x = -1; 3542 int y = -1; 3543 float path_sum = 0.0f; 3544 float clip_sum = 0.0f; 3545 size_t path_index = 0; 3546 size_t clip_index = 0; 3547 while ( clip_index < current.mask.length ) 3548 { 3549 bool which = ( path_index < runs.length) && 3550 (comparePixelRuns(runs[ path_index ], current.mask[ clip_index ] ) < 0); 3551 pixel_run next = which ? runs[ path_index ] : current.mask[ clip_index ]; 3552 float coverage = fminf( fabsf( path_sum ), 1.0f ); 3553 float visibility = fminf( fabsf( clip_sum ), 1.0f ); 3554 int to = next.y == y ? next.x : x + 1; 3555 enum float threshold = 1.0f / 8160.0f; 3556 if ( ( coverage >= threshold || ~operation & 8 ) && 3557 visibility >= threshold ) { 3558 3559 int x_offset = pixelTypeSize(outBitmap.type()) * x; 3560 3561 // How many pixels to do at once? 3562 int scanWidth = to - x; 3563 3564 // 1. Convert pixels [x..to] to rgbaf32 3565 scanlinesConvert(outBitmap.type(), 3566 cast(ubyte*)(outBitmap.scanptr(y)) + x_offset, 3567 outBitmap.pitchInBytes(), 3568 PixelType.rgbaf32, 3569 scanBuf.ptr, 3570 0, 3571 scanWidth, 3572 1, 3573 interType, 3574 interBuf.ptr); 3575 3576 // 2. Background is made delinearized then premultiplied 3577 rgba* scan = cast(rgba*) scanBuf.ptr; 3578 fromGammaSpace(scan[0..scanWidth], options.gammaCurve); 3579 for(int n = 0; n < scanWidth; ++n) 3580 { 3581 scan[n] = premultiplied(scan[n]); 3582 } 3583 3584 // 3. Render main 3585 for(int n = 0; n < scanWidth; ++n) 3586 { 3587 rgba back = scan[n]; 3588 3589 rgba fore = coverage * current.global_alpha * 3590 paint_pixel( xy( cast(float)( x ) + 0.5f, 3591 cast(float)( y ) + 0.5f ), 3592 brush ); 3593 float mix_fore = operation & 1 ? back.a : 0.0f; 3594 if ( operation & 2 ) 3595 mix_fore = 1.0f - mix_fore; 3596 float mix_back = operation & 4 ? fore.a : 0.0f; 3597 if ( operation & 8 ) 3598 mix_back = 1.0f - mix_back; 3599 rgba blend = mix_fore * fore + mix_back * back; 3600 blend.a = fminf( blend.a, 1.0f ); 3601 3602 rgba colout = visibility * blend 3603 + ( 1.0f - visibility ) * back; 3604 scan[n] = colout; 3605 } 3606 3607 // 4. Unpremultiply and put back in gamma-space. 3608 for(int n = 0; n < scanWidth; ++n) 3609 { 3610 scan[n] = unpremultiplied(scan[n]); 3611 } 3612 toGammaSpace(scan[0..scanWidth], options.gammaCurve); 3613 3614 // 5. Convert pixels [x..to] to rgbaf32 3615 scanlinesConvert(PixelType.rgbaf32, 3616 scanBuf.ptr, 3617 0, 3618 outBitmap.type(), 3619 cast(ubyte*)(outBitmap.scanptr(y)) + x_offset, 3620 outBitmap.pitchInBytes(), 3621 3622 scanWidth, 3623 1, 3624 interType, 3625 interBuf.ptr); 3626 } 3627 x = next.x; 3628 if ( next.y != y ) 3629 { 3630 y = next.y; 3631 path_sum = 0.0f; 3632 clip_sum = 0.0f; 3633 } 3634 if ( which ) 3635 path_sum += runs[ path_index++ ].delta; 3636 else 3637 clip_sum += current.mask[ clip_index++ ].delta; 3638 } 3639 } 3640 } 3641 3642 // ======== IMPLEMENTATION ======== 3643 // 3644 // The general internal data flow (albeit not control flow!) for rendering 3645 // a shape onto the canvas is as follows: 3646 // 3647 // 1. Construct a set of polybeziers representing the current path via the 3648 // public path building API (move_to, line_to, bezier_curve_to, etc.). 3649 // 2. Adaptively tessellate the polybeziers into polylines (path_to_lines). 3650 // 3. Maybe break the polylines into shorter polylines according to the dash 3651 // pattern (dash_lines). 3652 // 4. Maybe stroke expand the polylines into new polylines that can be filled 3653 // to show the lines with width, joins, and caps (stroke_lines). 3654 // 5. Scan-convert the polylines into a sparse representation of fractional 3655 // pixel coverage (lines_to_runs). 3656 // 6. Maybe paint the covered pixel span alphas "offscreen", blur, color, 3657 // and blend them onto the canvas where not clipped (render_shadow). 3658 // 7. Paint the covered pixel spans and blend them onto the canvas where not 3659 // clipped (render_main). 3660 3661 3662 3663 // Helpers for TTF file parsing 3664 int unsigned_8(ref Vec!ubyte data, int index ) 3665 { 3666 return data[ cast(size_t)index ]; 3667 } 3668 3669 int signed_8( ref Vec!ubyte data, int index ) 3670 { 3671 size_t place = cast(size_t)( index ); 3672 return cast(byte)(data[ place ]); 3673 } 3674 3675 int unsigned_16( ref Vec!ubyte data, int index ) { 3676 size_t place = cast(size_t)( index ); 3677 return data[ place ] << 8 | data[ place + 1 ]; 3678 } 3679 3680 int signed_16( ref Vec!ubyte data, int index ) 3681 { 3682 size_t place = cast(size_t)( index ); 3683 return cast(short)( data[ place ] << 8 | data[ place + 1 ] ); 3684 } 3685 3686 int signed_32( ref Vec!ubyte data, int index ) 3687 { 3688 size_t place = cast(size_t)( index ); 3689 return ( data[ place + 0 ] << 24 | data[ place + 1 ] << 16 | 3690 data[ place + 2 ] << 8 | data[ place + 3 ] << 0 ); 3691 } 3692 3693 int comparePixelRuns(in pixel_run a, in pixel_run b ) 3694 { 3695 if (a.y != b.y) 3696 return a.y - b.y; 3697 if (a.x != b.x) 3698 return a.x - b.x; 3699 3700 float diff = fabsf(a.delta ) - fabsf( b.delta ); 3701 if (diff < 0) 3702 return -1; 3703 if (diff > 0) 3704 return 1; 3705 return 0; 3706 } 3707 3708 // Implementation details 3709 private 3710 { 3711 template isLikeRGBA8(T) 3712 { 3713 enum isLikeRGBA8 = 3714 !is(T==RGBA8) 3715 && T.sizeof == 4 3716 && __traits(hasMember, T, "r") 3717 && __traits(hasMember, T, "g") 3718 && __traits(hasMember, T, "b") 3719 && __traits(hasMember, T, "a"); 3720 } 3721 3722 float fabsf(float a) 3723 { 3724 return a >= 0 ? a : -a; 3725 } 3726 struct xy 3727 { 3728 pure nothrow @nogc @safe: 3729 float x, y; // nothing seems to rely on .init 3730 3731 xy opBinary(string op)(float t) const if (op == "*") 3732 { 3733 xy r; 3734 r.x = x * t; 3735 r.y = y * t; 3736 return r; 3737 } 3738 3739 xy opBinaryRight(string op)(float t) const if (op == "*") 3740 { 3741 xy r; 3742 r.x = x * t; 3743 r.y = y * t; 3744 return r; 3745 } 3746 3747 xy opBinary(string op)(xy o) const if (op == "+") 3748 { 3749 xy r; 3750 r.x = x + o.x; 3751 r.y = y + o.y; 3752 return r; 3753 } 3754 3755 xy opBinary(string op)(xy o) const if (op == "-") 3756 { 3757 xy r; 3758 r.x = x - o.x; 3759 r.y = y - o.y; 3760 return r; 3761 } 3762 } 3763 3764 xy matrix_mul_vec(const(affine_matrix) left, xy right) 3765 { 3766 return xy( left.a * right.x + left.c * right.y + left.e, 3767 left.b * right.x + left.d * right.y + left.f ); 3768 } 3769 3770 float dot(xy left, xy right ) 3771 { 3772 return left.x * right.x + left.y * right.y; 3773 } 3774 3775 float length( xy that ) 3776 { 3777 return sqrtf( dot( that, that ) ); 3778 } 3779 3780 float direction( xy that ) 3781 { 3782 return atan2f( that.y, that.x ); 3783 } 3784 3785 xy normalized( xy that ) 3786 { 3787 float len = length( that ); 3788 float m = fmaxf(len, 1.0e-6f); 3789 return that * (1.0f / m); 3790 } 3791 3792 xy perpendicular( xy that ) 3793 { 3794 return xy( -that.y, that.x ); 3795 } 3796 3797 xy lerp( xy from, xy to, float ratio ) 3798 { 3799 return from + ( to - from ) * ratio; 3800 } 3801 3802 struct rgba 3803 { 3804 pure nothrow @nogc @safe: 3805 float r, g, b, a; 3806 3807 rgba opBinary(string op)(float t) const if (op == "*") 3808 { 3809 rgba c; 3810 c.r = r * t; 3811 c.g = g * t; 3812 c.b = b * t; 3813 c.a = a * t; 3814 return c; 3815 } 3816 3817 rgba opBinaryRight(string op)(float t) const if (op == "*") 3818 { 3819 rgba c; 3820 c.r = r * t; 3821 c.g = g * t; 3822 c.b = b * t; 3823 c.a = a * t; 3824 return c; 3825 } 3826 3827 rgba opBinary(string op)(rgba o) const if (op == "+") 3828 { 3829 rgba c; 3830 c.r = r + o.r; 3831 c.g = g + o.g; 3832 c.b = b + o.b; 3833 c.a = a + o.a; 3834 return c; 3835 } 3836 3837 rgba opBinary(string op)(rgba o) const if (op == "-") 3838 { 3839 rgba c; 3840 c.r = r - o.r; 3841 c.g = g - o.g; 3842 c.b = b - o.b; 3843 c.a = a - o.a; 3844 return c; 3845 } 3846 } 3847 3848 void fromGammaSpace(rgba[] arr, CanvasGammaCurve gammaCurve) 3849 { 3850 // Convert sRGB 0 to 1 to linear space 0 to 1 3851 static rgba linearized( rgba col ) 3852 { 3853 __m128 c = _mm_loadu_ps(cast(float*)&col); 3854 __m128 top = _mm_pow_ps( _mm_add_ps(c,_mm_set1_ps(0.055f)) * _mm_set1_ps(0.94786729857), _mm_set1_ps(2.4f)); 3855 __m128 bottom = _mm_set1_ps(0.0773993808f) * c; 3856 __m128 mask = _mm_cmplt_ps(c, _mm_set1_ps(0.04045f)); 3857 c = _mm_or_ps(_mm_and_ps(mask, bottom), _mm_andnot_ps(mask, top)); 3858 c.ptr[3] = col.a; 3859 _mm_storeu_ps(cast(float*)&col, c); 3860 return col; 3861 } 3862 3863 3864 final switch(gammaCurve) 3865 { 3866 case CanvasGammaCurve.linear: 3867 foreach(ref col; arr) 3868 col = linearized(col); 3869 break; 3870 case CanvasGammaCurve.pow2: 3871 foreach(ref col; arr) { 3872 col.r = col.r * col.r; 3873 col.g = col.g * col.g; 3874 col.b = col.b * col.b; 3875 } 3876 break; 3877 case CanvasGammaCurve.none: 3878 break; 3879 } 3880 } 3881 3882 void toGammaSpace(rgba[] arr, CanvasGammaCurve gammaCurve) 3883 { 3884 static rgba delinearized(rgba col) 3885 { 3886 __m128 c = _mm_loadu_ps(cast(float*)&col); 3887 __m128 top = _mm_pow_ps(c, _mm_set1_ps(0.41666666666f)) * _mm_set1_ps(1.055f) - _mm_set1_ps(0.055f); 3888 __m128 bottom = _mm_set1_ps(12.92f) * c; 3889 __m128 mask = _mm_cmplt_ps(c, _mm_set1_ps(0.0031308f)); 3890 c = _mm_or_ps(_mm_and_ps(mask, bottom), _mm_andnot_ps(mask, top)); 3891 c.ptr[3] = col.a; 3892 _mm_storeu_ps(cast(float*)&col, c); 3893 return col; 3894 } 3895 3896 final switch(gammaCurve) 3897 { 3898 case CanvasGammaCurve.linear: 3899 foreach(ref col; arr) 3900 col = delinearized(col); 3901 break; 3902 case CanvasGammaCurve.pow2: 3903 foreach(ref col; arr) { 3904 __m128 c = _mm_loadu_ps(cast(float*)&col); 3905 c = _mm_sqrt_ps(c); 3906 c.ptr[3] = col.a; 3907 _mm_storeu_ps(cast(float*)&col, c); 3908 } 3909 break; 3910 case CanvasGammaCurve.none: 3911 break; 3912 } 3913 } 3914 3915 rgba premultiplied( rgba col ) 3916 { 3917 return rgba(col.r*col.a, col.g*col.a, col.b*col.a, col.a); 3918 } 3919 3920 rgba unpremultiplied( rgba that ) 3921 { 3922 enum float threshold = 1.0f / 8160.0f; 3923 return that.a < threshold ? rgba( 0.0f, 0.0f, 0.0f, 0.0f ) : 3924 rgba( 1.0f / that.a * that.r, 1.0f / that.a * that.g, 3925 1.0f / that.a * that.b, that.a ); 3926 } 3927 3928 rgba clamped(rgba that) pure 3929 { 3930 if (that.r < 0) that.r = 0; 3931 if (that.r > 1) that.r = 1; 3932 if (that.g < 0) that.g = 0; 3933 if (that.g > 1) that.g = 1; 3934 if (that.b < 0) that.b = 0; 3935 if (that.b > 1) that.b = 1; 3936 if (that.a < 0) that.a = 0; 3937 if (that.a > 1) that.a = 1; 3938 return that; 3939 } 3940 3941 struct affine_matrix 3942 { 3943 pure nothrow @nogc @safe: 3944 float a, b, c, d, e, f; 3945 3946 enum identity = affine_matrix(1, 0, 0, 1, 0, 0); 3947 3948 bool isInvertible() { 3949 return (a * d - b * c) != 0.0f; 3950 } 3951 } 3952 3953 struct paint_brush 3954 { 3955 nothrow @nogc: 3956 enum types 3957 { 3958 color, 3959 linear, 3960 radial, 3961 pattern 3962 } 3963 types type; 3964 3965 @disable this(this); // copying Vec doesn't work like in C++! 3966 3967 Vec!rgba colors; 3968 Vec!float stops; 3969 xy start = xy(0.0f, 0.0f); 3970 xy end = xy(0.0f, 0.0f); 3971 float start_radius, end_radius; 3972 int width, height; 3973 repetition_style repetition; 3974 3975 void opAssign(ref const(paint_brush) other) 3976 { 3977 this.type = other.type; 3978 colors.clearContents(); 3979 Vec!rgba* pcol = cast(Vec!rgba*)&other.colors; // const_cast 3980 colors.pushBack(*pcol); 3981 stops.clearContents(); 3982 Vec!float* pstops = cast(Vec!float*)&other.stops; 3983 stops.pushBack(*pstops); 3984 this.start = other.start; 3985 this.end = other.end; 3986 this.start_radius = other.start_radius; 3987 this.end_radius = other.end_radius; 3988 this.width = other.width; 3989 this.height = other.height; 3990 this.repetition = other.repetition; 3991 } 3992 3993 } 3994 3995 void swap_scalar(T)(ref T a, ref T b) 3996 { 3997 T c = a; 3998 a = b; 3999 b = c; 4000 } 4001 4002 alias swap_xy = swap_scalar!xy; 4003 4004 // a = b; 4005 void assign_vec(T)(ref Vec!T a, ref const(Vec!T) b) 4006 { 4007 a.clearContents(); 4008 // Useful to be compatible as far as LDC 1.20 4009 Vec!T* p = cast(Vec!T*)&b; // const_cast 4010 a.pushBack(*p); 4011 } 4012 4013 void swap_brush(ref paint_brush a, 4014 ref paint_brush b, 4015 ref paint_brush tmp) 4016 { 4017 tmp = a; 4018 a = b; 4019 b = tmp; 4020 } 4021 4022 struct font_face 4023 { 4024 nothrow @nogc: 4025 @disable this(this); 4026 Vec!ubyte data; 4027 int cmap, glyf, head, hhea, hmtx, loca, maxp, os_2; 4028 float scale; 4029 4030 void opAssign(ref const(font_face) other) 4031 { 4032 data.clearContents(); 4033 Vec!ubyte* p = cast(Vec!ubyte*)&other.data; 4034 data.pushBack(*p); 4035 this.cmap = other.cmap; 4036 this.glyf = other.glyf; 4037 this.head = other.head; 4038 this.hhea = other.hhea; 4039 this.hmtx = other.hmtx; 4040 this.loca = other.loca; 4041 this.maxp = other.maxp; 4042 this.os_2 = other.os_2; 4043 this.scale = other.scale; 4044 } 4045 } 4046 4047 struct subpath_data 4048 { 4049 size_t count; 4050 bool closed; 4051 } 4052 4053 struct bezier_path 4054 { 4055 @disable this(this); // copying Vec doesn't work like in C++! 4056 Vec!xy points; 4057 Vec!subpath_data subpaths; 4058 } 4059 4060 struct line_path 4061 { 4062 @disable this(this); // copying Vec doesn't work like in C++! 4063 Vec!xy points; 4064 Vec!subpath_data subpaths; 4065 } 4066 4067 struct pixel_run 4068 { 4069 ushort x, y; 4070 float delta; 4071 } 4072 4073 float fminf(float a, float b) pure 4074 { 4075 return a < b ? a : b; 4076 } 4077 4078 float fmaxf(float a, float b) pure 4079 { 4080 return a > b ? a : b; 4081 } 4082 4083 int min_int(int a, int b) pure 4084 { 4085 return a < b ? a : b; 4086 } 4087 4088 int max_int(int a, int b) pure 4089 { 4090 return a > b ? a : b; 4091 } 4092 4093 size_t max_size_t(size_t a, size_t b) pure 4094 { 4095 return a > b ? a : b; 4096 } 4097 4098 // FUTURE: move into dplug.core 4099 void vec_insert(T)(ref Vec!T v, size_t index, T value) 4100 { 4101 v.pushBack(T.init); 4102 size_t last = v.length - 1; 4103 for (int i = last; i > index; --i) 4104 { 4105 v[i] = v[i-1]; 4106 } 4107 v[index] = value; 4108 } 4109 } 4110 4111 // .init must be valid and survive .destroyNoGC 4112 unittest 4113 { 4114 Canvasity c; 4115 destroyNoGC(c); 4116 destroyNoGC(c); 4117 }