tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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