compositing.yaml (20793B)
1 - name: 2d.composite.globalAlpha.range 2 code: | 3 ctx.globalAlpha = 0.5; 4 // This may not set it to exactly 0.5 if it is rounded/quantised, so 5 // remember for future comparisons. 6 var a = ctx.globalAlpha; 7 @assert ctx.globalAlpha === a; 8 ctx.globalAlpha = 1.1; 9 @assert ctx.globalAlpha === a; 10 ctx.globalAlpha = -0.1; 11 @assert ctx.globalAlpha === a; 12 ctx.globalAlpha = 0; 13 @assert ctx.globalAlpha === 0; 14 ctx.globalAlpha = 1; 15 @assert ctx.globalAlpha === 1; 16 17 - name: 2d.composite.globalAlpha.invalid 18 code: | 19 ctx.globalAlpha = 0.5; 20 // This may not set it to exactly 0.5 if it is rounded/quantised, so 21 // remember for future comparisons. 22 var a = ctx.globalAlpha; 23 ctx.globalAlpha = Infinity; 24 @assert ctx.globalAlpha === a; 25 ctx.globalAlpha = -Infinity; 26 @assert ctx.globalAlpha === a; 27 ctx.globalAlpha = NaN; 28 @assert ctx.globalAlpha === a; 29 30 - name: 2d.composite.globalAlpha.default 31 code: | 32 @assert ctx.globalAlpha === 1.0; 33 34 - name: 2d.composite.globalAlpha.fill 35 code: | 36 ctx.fillStyle = '#0f0'; 37 ctx.fillRect(0, 0, 100, 50); 38 // Avoiding any potential alpha = 0 optimisations. 39 ctx.globalAlpha = 0.01; 40 ctx.fillStyle = '#f00'; 41 ctx.fillRect(0, 0, 100, 50); 42 @assert pixel 50,25 ==~ 2,253,0,255; 43 expected: green 44 45 - name: 2d.composite.globalAlpha.canvas 46 canvas_types: ['HtmlCanvas'] 47 code: | 48 var canvas2 = document.createElement('canvas'); 49 canvas2.width = 100; 50 canvas2.height = 50; 51 var ctx2 = canvas2.getContext('2d'); 52 ctx2.fillStyle = '#f00'; 53 ctx2.fillRect(0, 0, 100, 50); 54 55 ctx.fillStyle = '#0f0'; 56 ctx.fillRect(0, 0, 100, 50); 57 // Avoiding any potential alpha = 0 optimisations. 58 ctx.globalAlpha = 0.01; 59 ctx.drawImage(canvas2, 0, 0); 60 @assert pixel 50,25 ==~ 2,253,0,255; 61 expected: green 62 63 - name: 2d.composite.globalAlpha.canvaspattern 64 canvas_types: ['HtmlCanvas'] 65 code: | 66 var canvas2 = document.createElement('canvas'); 67 canvas2.width = 100; 68 canvas2.height = 50; 69 var ctx2 = canvas2.getContext('2d'); 70 ctx2.fillStyle = '#f00'; 71 ctx2.fillRect(0, 0, 100, 50); 72 73 ctx.fillStyle = '#0f0'; 74 ctx.fillRect(0, 0, 100, 50); 75 ctx.fillStyle = ctx.createPattern(canvas2, 'no-repeat'); 76 // Avoiding any potential alpha = 0 optimisations. 77 ctx.globalAlpha = 0.01; 78 ctx.fillRect(0, 0, 100, 50); 79 @assert pixel 50,25 ==~ 2,253,0,255; 80 expected: green 81 82 - name: 2d.composite.globalAlpha.canvascopy 83 canvas_types: ['HtmlCanvas'] 84 code: | 85 var canvas2 = document.createElement('canvas'); 86 canvas2.width = 100; 87 canvas2.height = 50; 88 var ctx2 = canvas2.getContext('2d'); 89 ctx2.fillStyle = '#0f0'; 90 ctx2.fillRect(0, 0, 100, 50); 91 92 ctx.fillStyle = '#f00'; 93 ctx.fillRect(0, 0, 100, 50); 94 95 ctx.globalCompositeOperation = 'copy' 96 ctx.globalAlpha = 0.51; 97 ctx.drawImage(canvas2, 0, 0); 98 @assert pixel 50,25 ==~ 0,255,0,130; 99 expected: green 100 101 102 - name: 2d.composite.operation.get 103 code: | 104 var modes = ['source-atop', 'source-in', 'source-out', 'source-over', 105 'destination-atop', 'destination-in', 'destination-out', 'destination-over', 106 'lighter', 'copy', 'xor']; 107 for (var i = 0; i < modes.length; ++i) 108 { 109 ctx.globalCompositeOperation = modes[i]; 110 @assert ctx.globalCompositeOperation === modes[i]; 111 } 112 113 - name: 2d.composite.operation.unrecognised 114 code: | 115 ctx.globalCompositeOperation = 'xor'; 116 ctx.globalCompositeOperation = 'nonexistent'; 117 @assert ctx.globalCompositeOperation === 'xor'; 118 119 - name: 2d.composite.operation.darker 120 code: | 121 ctx.globalCompositeOperation = 'xor'; 122 ctx.globalCompositeOperation = 'darker'; 123 @assert ctx.globalCompositeOperation === 'xor'; 124 125 - name: 2d.composite.operation.over 126 code: | 127 ctx.globalCompositeOperation = 'xor'; 128 ctx.globalCompositeOperation = 'over'; 129 @assert ctx.globalCompositeOperation === 'xor'; 130 131 - name: 2d.composite.operation.clear 132 code: | 133 ctx.globalCompositeOperation = 'xor'; 134 ctx.globalCompositeOperation = 'clear'; 135 @assert ctx.globalCompositeOperation === 'clear'; 136 137 - name: 2d.composite.operation.highlight 138 code: | 139 ctx.globalCompositeOperation = 'xor'; 140 ctx.globalCompositeOperation = 'highlight'; 141 @assert ctx.globalCompositeOperation === 'xor'; 142 143 - name: 2d.composite.operation.nullsuffix 144 code: | 145 ctx.globalCompositeOperation = 'xor'; 146 ctx.globalCompositeOperation = 'source-over\0'; 147 @assert ctx.globalCompositeOperation === 'xor'; 148 149 - name: 2d.composite.operation.casesensitive 150 code: | 151 ctx.globalCompositeOperation = 'xor'; 152 ctx.globalCompositeOperation = 'Source-over'; 153 @assert ctx.globalCompositeOperation === 'xor'; 154 155 - name: 2d.composite.operation.default 156 code: | 157 @assert ctx.globalCompositeOperation === 'source-over'; 158 159 160 - name: 2d.composite.globalAlpha.image 161 test_type: promise 162 code: | 163 ctx.fillStyle = '#0f0'; 164 ctx.fillRect(0, 0, 100, 50); 165 // Avoiding any potential alpha = 0 optimisations. 166 ctx.globalAlpha = 0.01; 167 const response = await fetch('/images/red.png'); 168 const blob = await response.blob(); 169 const bitmap = await createImageBitmap(blob); 170 171 ctx.drawImage(bitmap, 0, 0); 172 @assert pixel 50,25 ==~ 2,253,0,255; 173 expected: green 174 175 - name: 2d.composite.globalAlpha.canvas 176 canvas_types: ['OffscreenCanvas', 'Worker'] 177 code: | 178 var offscreenCanvas2 = new OffscreenCanvas(100, 50); 179 var ctx2 = offscreenCanvas2.getContext('2d'); 180 ctx2.fillStyle = '#f00'; 181 ctx2.fillRect(0, 0, 100, 50); 182 ctx.fillStyle = '#0f0'; 183 ctx.fillRect(0, 0, 100, 50); 184 // Avoiding any potential alpha = 0 optimisations. 185 ctx.globalAlpha = 0.01; 186 ctx.drawImage(offscreenCanvas2, 0, 0); 187 @assert pixel 50,25 ==~ 2,253,0,255; 188 189 - name: 2d.composite.globalAlpha.imagepattern 190 test_type: promise 191 code: | 192 ctx.fillStyle = '#0f0'; 193 ctx.fillRect(0, 0, 100, 50); 194 const response = await fetch('/images/red.png'); 195 const blob = await response.blob(); 196 const bitmap = await createImageBitmap(blob); 197 198 ctx.fillStyle = ctx.createPattern(bitmap, 'no-repeat'); 199 // Avoiding any potential alpha = 0 optimisations. 200 ctx.globalAlpha = 0.01; 201 ctx.fillRect(0, 0, 100, 50); 202 @assert pixel 50,25 ==~ 2,253,0,255; 203 expected: green 204 205 - name: 2d.composite.globalAlpha.canvaspattern 206 canvas_types: ['OffscreenCanvas', 'Worker'] 207 code: | 208 var offscreenCanvas2 = new OffscreenCanvas(100, 50); 209 var ctx2 = offscreenCanvas2.getContext('2d'); 210 ctx2.fillStyle = '#f00'; 211 ctx2.fillRect(0, 0, 100, 50); 212 ctx.fillStyle = '#0f0'; 213 ctx.fillRect(0, 0, 100, 50); 214 ctx.fillStyle = ctx.createPattern(offscreenCanvas2, 'no-repeat'); 215 // Avoiding any potential alpha = 0 optimisations. 216 ctx.globalAlpha = 0.01; 217 ctx.fillRect(0, 0, 100, 50); 218 @assert pixel 50,25 ==~ 2,253,0,255; 219 220 - name: 2d.composite.globalAlpha.canvascopy 221 canvas_types: ['OffscreenCanvas', 'Worker'] 222 code: | 223 var offscreenCanvas2 = new OffscreenCanvas(100, 50); 224 var ctx2 = offscreenCanvas2.getContext('2d'); 225 ctx2.fillStyle = '#0f0'; 226 ctx2.fillRect(0, 0, 100, 50); 227 ctx.fillStyle = '#f00'; 228 ctx.fillRect(0, 0, 100, 50); 229 ctx.globalCompositeOperation = 'copy' 230 ctx.globalAlpha = 0.51; 231 ctx.drawImage(offscreenCanvas2, 0, 0); 232 @assert pixel 50,25 ==~ 0,255,0,130; 233 234 - name: 2d.composite.grid 235 size: [80, 60] 236 code: | 237 ctx.fillStyle = 'rgba(0, 102, 240, 0.8)'; 238 ctx.fillRect(15, 15, 50, 30); 239 240 ctx.translate(25, 20); 241 ctx.rotate(Math.PI / 2); 242 ctx.scale(0.6, 1.2); 243 ctx.translate(-25, -20); 244 245 ctx.globalAlpha = 0.5; 246 247 {{ js_filter_code }} 248 {{ js_shadow_code }} 249 250 ctx.globalCompositeOperation = '{{ variant_names[0] }}'; 251 252 {{ js_draw_code }} 253 cairo_reference: | 254 # Background. 255 cr.push_group() 256 cr.set_source_rgba(0, 102/255, 240/255, 0.8) 257 cr.rectangle(15, 15, 50, 30) 258 cr.fill() 259 background = cr.pop_group() 260 261 # Foreground. 262 cr.push_group() 263 cr.translate(25, 20) 264 cr.rotate(math.pi / 2) 265 cr.scale(0.6, 1.2) 266 cr.translate(-25, -20) 267 cr.set_source_rgba(52/255, 1, 52/255, 0.5) 268 cr.rectangle(5, 5, 50, 30) 269 cr.fill() 270 foreground = cr.pop_group() 271 272 # Filtered foreground. 273 cr.push_group() 274 {{ cairo_filter_code }} 275 cr.set_source(foreground) 276 cr.paint() 277 filtered_foreground = cr.pop_group() 278 279 {% if cairo_operator != 'SOURCE' %} 280 cr.set_source(background) 281 cr.paint() 282 {% endif %} 283 284 cr.set_operator(cairo.OPERATOR_{{ cairo_operator }}) 285 286 {% if cairo_operator != 'SOURCE' %} 287 {{ cairo_shadow_code }} 288 {% endif %} 289 290 cr.set_source(filtered_foreground) 291 cr.paint() 292 fuzzy: maxDifference=0-4; totalPixels=0-33000 293 variants_layout: 294 - single_file 295 - multi_files 296 - multi_files 297 - multi_files 298 grid_width: 6 299 variants: 300 - source-over: 301 cairo_operator: OVER 302 source-in: 303 cairo_operator: IN 304 source-out: 305 cairo_operator: OUT 306 source-atop: 307 cairo_operator: ATOP 308 destination-over: 309 cairo_operator: DEST_OVER 310 destination-in: 311 cairo_operator: DEST_IN 312 destination-out: 313 cairo_operator: DEST_OUT 314 destination-atop: 315 cairo_operator: DEST_ATOP 316 lighter: 317 cairo_operator: ADD 318 copy: 319 cairo_operator: SOURCE 320 xor: 321 cairo_operator: XOR 322 multiply: 323 cairo_operator: MULTIPLY 324 screen: 325 cairo_operator: SCREEN 326 overlay: 327 cairo_operator: OVERLAY 328 darken: 329 cairo_operator: DARKEN 330 lighten: 331 cairo_operator: LIGHTEN 332 color-dodge: 333 cairo_operator: COLOR_DODGE 334 color-burn: 335 cairo_operator: COLOR_BURN 336 hard-light: 337 cairo_operator: HARD_LIGHT 338 soft-light: 339 cairo_operator: SOFT_LIGHT 340 difference: 341 cairo_operator: DIFFERENCE 342 exclusion: 343 cairo_operator: EXCLUSION 344 hue: 345 cairo_operator: HSL_HUE 346 saturation: 347 cairo_operator: HSL_SATURATION 348 color: 349 cairo_operator: HSL_COLOR 350 luminosity: 351 cairo_operator: HSL_LUMINOSITY 352 353 - no_filter: 354 js_filter_code: // No filter. 355 cairo_filter_code: "# No filter." 356 filter: 357 js_filter_code: |- 358 ctx.filter = 'drop-shadow(5px -5px 0px rgb(255, 154, 100))'; 359 cairo_filter_code: |- 360 cr.push_group() 361 cr.set_operator(cairo.OPERATOR_OVER) 362 cr.translate(5, -5) # Filter offset. 363 cr.set_source(foreground) 364 cr.paint() 365 cr.set_operator(cairo.OPERATOR_IN) 366 cr.set_source_rgb(1, 154/255, 100/255) 367 cr.paint() 368 cr.pop_group_to_source() 369 cr.paint() 370 371 - no_shadow: 372 js_shadow_code: // No shadow. 373 cairo_shadow_code: "# No shadow." 374 shadow: 375 js_shadow_code: |- 376 ctx.shadowOffsetX = 20; 377 ctx.shadowOffsetY = 20; 378 ctx.shadowColor = 'rgba(154, 0, 154, 0.8)'; 379 cairo_shadow_code: |- 380 cr.push_group() 381 cr.set_operator(cairo.OPERATOR_OVER) 382 cr.translate(20, 20) # Shadow offset. 383 cr.set_source(filtered_foreground) 384 cr.paint() 385 cr.set_operator(cairo.OPERATOR_IN) 386 cr.set_source_rgba(154/255, 0, 154/255, 0.8) 387 cr.paint() 388 cr.pop_group_to_source() 389 cr.paint() 390 391 - fillRect: 392 js_draw_code: |- 393 ctx.fillStyle = 'rgb(52, 255, 52)'; 394 ctx.fillRect(5, 5, 50, 30); 395 drawImage: 396 js_draw_code: |- 397 const img_canvas = new OffscreenCanvas({{ size[0] }}, {{ size[1] }}); 398 const img_ctx = img_canvas.getContext('2d'); 399 img_ctx.fillStyle = 'rgb(52, 255, 52)'; 400 img_ctx.fillRect(0, 0, {{ size[0] }}, {{ size[1] }}); 401 ctx.drawImage(img_canvas, 5, 5, 50, 30); 402 pattern: 403 js_draw_code: |- 404 const img_canvas = new OffscreenCanvas({{ size[0] }}, {{ size[1] }}); 405 const img_ctx = img_canvas.getContext('2d'); 406 img_ctx.fillStyle = 'rgb(52, 255, 52)'; 407 img_ctx.fillRect(0, 0, {{ size[0] }}, {{ size[1] }}); 408 ctx.fillStyle = ctx.createPattern(img_canvas, 'repeat'); 409 ctx.fillRect(5, 5, 50, 30); 410 411 # Composite operation tests 412 # <http://lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2007-March/010608.html> 413 - name: 2d.composite 414 macros: | 415 {% macro calc_output(A, B, FA, FB) %} 416 {% set RA, GA, BA, aA = A -%} 417 {% set RB, GB, BB, aB = B -%} 418 {% set rA, gA, bA = RA * aA, GA * aA, BA * aA -%} 419 {% set rB, gB, bB = RB * aB, GB * aB, BB * aB -%} 420 421 {% set FA = FA[0] + FA[1] * aA + FA[2] * aB -%} 422 {% set FB = FB[0] + FB[1] * aA + FB[2] * aB -%} 423 424 {% set rO = rA * FA + rB * FB -%} 425 {% set gO = gA * FA + gB * FB -%} 426 {% set bO = bA * FA + bB * FB -%} 427 {% set aO = aA * FA + aB * FB -%} 428 429 {% set rO = (255, rO) | min -%} 430 {% set gO = (255, gO) | min -%} 431 {% set bO = (255, bO) | min -%} 432 {% set aO = (1, aO) | min -%} 433 434 {% set RO = rO / aO if aO else 0 -%} 435 {% set GO = gO / aO if aO else 0 -%} 436 {% set BO = bO / aO if aO else 0 -%} 437 438 {{- '%f,%f,%f,%f' | format(RO, GO, BO, aO) -}} 439 {% endmacro %} 440 441 {% macro rgba_format(color) %} 442 {% set r, g, b, a = color -%} 443 rgba{{ (r, g, b, a) -}} 444 {% endmacro %} 445 446 {% macro js_format(color) %} 447 {% set r, g, b, a = color.split(',') | map('float') %} 448 {{- '%d,%d,%d,%d' | 449 format(r | round, g | round, b | round, (a * 255) | round) -}} 450 {% endmacro %} 451 452 {% macro cairo_format(color) %} 453 {% set r, g, b, a = color.split(',') | map('float') %} 454 {{- '%f,%f,%f,%f' | format(r / 255.0, g / 255.0, b / 255.0, a) -}} 455 {% endmacro %} 456 code: | 457 {% import 'macros' as m -%} 458 ctx.fillStyle = '{{ m.rgba_format(dest_color) }}'; 459 ctx.fillRect(0, 0, 100, 50); 460 ctx.globalCompositeOperation = '{{ variant_names[1] }}'; 461 {{ draw_code }} 462 {{ assertion }} 463 assertion: |- 464 {% import 'macros' as m -%} 465 @assert pixel 50,25 ==~ {{ m.js_format(expected_color) }} +/- 5; 466 expected: | 467 {% import 'macros' as m %} 468 size 100 50 469 cr.set_source_rgba({{ m.cairo_format(expected_color) }}) 470 cr.rectangle(0, 0, 100, 50) 471 cr.fill() 472 new_auxiliary_canvas: |- 473 {%- if canvas_type == 'HtmlCanvas' -%} 474 document.createElement('canvas'); 475 canvas2.width = canvas.width; 476 canvas2.height = canvas.height; 477 {%- else -%} 478 new OffscreenCanvas(canvas.width, canvas.height); 479 {%- endif -%} 480 variants: 481 - solid: 482 src_color: [255, 255, 0, 1.0] 483 dest_color: [0, 255, 255, 1.0] 484 draw_code: |- 485 {% import 'macros' as m %} 486 ctx.fillStyle = '{{ m.rgba_format(src_color) }}'; 487 ctx.fillRect(0, 0, 100, 50); 488 expected_color: | 489 {% import 'macros' as m %} 490 {{ m.calc_output(src_color, dest_color, fa, fb) }} 491 transparent: 492 src_color: [0, 0, 255, 0.75] 493 dest_color: [0, 255, 0, 0.5] 494 draw_code: |- 495 {% import 'macros' as m %} 496 ctx.fillStyle = '{{ m.rgba_format(src_color) }}'; 497 ctx.fillRect(0, 0, 100, 50); 498 expected_color: | 499 {% import 'macros' as m %} 500 {{ m.calc_output(src_color, dest_color, fa, fb) }} 501 image: 502 src_color: [255, 255, 0, 0.75] 503 dest_color: [0, 255, 255, 0.5] 504 test_type: 'promise' 505 draw_code: |- 506 const response = await fetch('/images/yellow75.png') 507 const blob = await response.blob(); 508 const bitmap = await createImageBitmap(blob); 509 ctx.drawImage(bitmap, 0, 0); 510 expected_color: | 511 {% import 'macros' as m %} 512 {{ m.calc_output(src_color, dest_color, fa, fb) }} 513 canvas: 514 src_color: [255, 255, 0, 0.75] 515 dest_color: [0, 255, 255, 0.5] 516 test_type: 'promise' 517 draw_code: |- 518 const canvas2 = {{ new_auxiliary_canvas }} 519 const ctx2 = canvas2.getContext('2d'); 520 const response = await fetch('/images/yellow75.png') 521 const blob = await response.blob(); 522 const bitmap = await createImageBitmap(blob); 523 ctx2.drawImage(bitmap, 0, 0); 524 ctx.drawImage(canvas2, 0, 0); 525 expected_color: | 526 {% import 'macros' as m %} 527 {{ m.calc_output(src_color, dest_color, fa, fb) }} 528 uncovered.fill: 529 desc: >- 530 fill() draws pixels not covered by the source object as (0,0,0,0), and 531 does not leave the pixels unchanged. 532 src_color: [0, 0, 255, 0.75] 533 dest_color: [0, 255, 0, 0.5] 534 draw_code: |- 535 {% import 'macros' as m %} 536 ctx.fillStyle = '{{ m.rgba_format(src_color) }}'; 537 ctx.translate(0, 25); 538 ctx.fillRect(0, 50, 100, 50); 539 expected_color: | 540 {% import 'macros' as m %} 541 {{ m.calc_output([0, 0, 0, 0], dest_color, fa, fb) }} 542 enabled: |- 543 {{ variant_names[1] in ['source-in', 544 'destination-in', 545 'source-out', 546 'destination-atop', 547 'copy'] }} 548 timeout: |- 549 {%- if variant_names[1] == 'destination-in' and 550 canvas_type != 'HtmlCanvas' -%} 551 long 552 {%- endif -%} 553 uncovered.image: 554 desc: >- 555 drawImage() draws pixels not covered by the source object as (0,0,0,0), 556 and does not leave the pixels unchanged. 557 src_color: [255, 255, 0, 1.0] 558 dest_color: [0, 255, 255, 0.5] 559 test_type: 'promise' 560 draw_code: |- 561 const response = await fetch('/images/yellow.png') 562 const blob = await response.blob(); 563 const bitmap = await createImageBitmap(blob); 564 ctx.drawImage(bitmap, 40, 40, 10, 10, 40, 50, 10, 10); 565 expected_color: | 566 {% import 'macros' as m %} 567 {{ m.calc_output([0, 0, 0, 0], dest_color, fa, fb) }} 568 enabled: |- 569 {{ variant_names[1] in ['source-in', 570 'destination-in', 571 'source-out', 572 'destination-atop', 573 'copy'] }} 574 uncovered.nocontext: 575 desc: >- 576 drawImage() of a canvas with no context draws pixels as (0,0,0,0), and 577 does not leave the pixels unchanged. 578 src_color: [255, 255, 0, 1.0] 579 dest_color: [0, 255, 255, 0.5] 580 draw_code: |- 581 const canvas2 = {{ new_auxiliary_canvas }} 582 ctx.drawImage(canvas2, 0, 0); 583 expected_color: | 584 {% import 'macros' as m %} 585 {{ m.calc_output([0, 0, 0, 0], dest_color, fa, fb) }} 586 enabled: |- 587 {{ variant_names[1] in ['source-in', 588 'destination-in', 589 'source-out', 590 'destination-atop', 591 'copy'] }} 592 uncovered.pattern: 593 desc: >- 594 Pattern fill() draws pixels not covered by the source object as 595 (0,0,0,0), and does not leave the pixels unchanged. 596 src_color: [255, 255, 0, 1.0] 597 dest_color: [0, 255, 255, 0.5] 598 test_type: 'promise' 599 draw_code: |- 600 const response = await fetch('/images/yellow.png') 601 const blob = await response.blob(); 602 const bitmap = await createImageBitmap(blob); 603 ctx.fillStyle = ctx.createPattern(bitmap, 'no-repeat'); 604 ctx.fillRect(0, 50, 100, 50); 605 expected_color: | 606 {% import 'macros' as m %} 607 {{ m.calc_output([0, 0, 0, 0], dest_color, fa, fb) }} 608 enabled: |- 609 {{ variant_names[1] in ['source-in', 610 'destination-in', 611 'source-out', 612 'destination-atop', 613 'copy'] }} 614 clip: 615 desc: fill() does not affect pixels outside the clip region. 616 src_color: [255, 0, 0, 1] 617 dest_color: [0, 255, 0, 1] 618 draw_code: |- 619 {% import 'macros' as m %} 620 ctx.rect(-20, -20, 10, 10); 621 ctx.clip(); 622 ctx.fillStyle = '{{ m.rgba_format(src_color) }}'; 623 ctx.fillRect(0, 0, 50, 50); 624 assertion: |- 625 @assert pixel 50,25 == 0,255,0,255; 626 expected: green 627 - source-over: 628 fa: [1, 0, 0] # 1 629 fb: [1, -1, 0] # 1-aA 630 destination-over: 631 fa: [1, 0, -1] # 1-aB 632 fb: [1, 0, 0] # 1 633 source-in: 634 fa: [0, 0, 1] # aB 635 fb: [0, 0, 0] # 0 636 destination-in: 637 fa: [0, 0, 0] # 0 638 fb: [0, 1, 0] # aA 639 source-out: 640 fa: [1, 0, -1] # 1-aB 641 fb: [0, 0, 0] # 0 642 destination-out: 643 fa: [0, 0, 0] # 0 644 fb: [1, -1, 0] # 1-aA 645 source-atop: 646 fa: [0, 0, 1] # aB 647 fb: [1, -1, 0] # 1-aA 648 destination-atop: 649 fa: [1, 0, -1] # 1-aB 650 fb: [0, 1, 0] # aA 651 xor: 652 fa: [1, 0, -1] # 1-aB 653 fb: [1, -1, 0] # 1-aA 654 copy: 655 fa: [1, 0, 0] # 1 656 fb: [0, 0, 0] # 0 657 lighter: 658 fa: [1, 0, 0] # 1 659 fb: [1, 0, 0] # 1 660 clear: 661 fa: [0, 0, 0] # 0 662 fb: [0, 0, 0] # 0