tor-browser

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

canvas-composite-modes.html (6074B)


      1 <!DOCTYPE HTML>
      2 <meta charset="utf-8">
      3 <title>Test of &lt;composite-mode&gt; values in canvas globalCompositeOperation</title>
      4 <link rel="help" href="https://html.spec.whatwg.org/multipage/C#compositing">
      5 <link rel="help" href="https://drafts.fxtf.org/compositing/#canvascompositingandblending">
      6 <script src="/resources/testharness.js"></script>
      7 <script src="/resources/testharnessreport.js"></script>
      8 
      9 <canvas id="canvas" width="2" height="2"></canvas>
     10 
     11 <script>
     12 
     13 // Test a small set of sRGB color and alpha values in the 0-255 range.
     14 const VALUES = [ 0, 47, 193, 255 ];
     15 
     16 const COMPOSITE_OPERATORS = {
     17  // Define a "color" function accepting source and destination colors
     18  // and alphas, and an "alpha" function accepting source and
     19  // destination alphas.
     20  "clear": {
     21    "color": (sa, sc, da, dc) => 0,
     22    "alpha": (sa, da) => 0
     23  },
     24  "copy": {
     25    "color": (sa, sc, da, dc) => sa * sc,
     26    "alpha": (sa, da) => sa
     27  },
     28  "destination": {
     29    // TODO(dbaron): The spec says this should work, but none of
     30    // Chromium, Gecko, or WebKit appear to implement it.
     31    "color": (sa, sc, da, dc) => da * dc,
     32    "alpha": (sa, da) => da
     33  },
     34  "source-over": {
     35    "color": (sa, sc, da, dc) => sa * sc + da * dc * (1 - sa),
     36    "alpha": (sa, da) => sa + da * (1 - sa)
     37  },
     38  "destination-over": {
     39    "color": (sa, sc, da, dc) => sa * sc * (1 - da) + da * dc,
     40    "alpha": (sa, da) => sa * (1 - da) + da
     41  },
     42  "source-in": {
     43    "color": (sa, sc, da, dc) => sa * sc * da,
     44    "alpha": (sa, da) => sa * da
     45  },
     46  "destination-in": {
     47    "color": (sa, sc, da, dc) => da * dc * sa,
     48    "alpha": (sa, da) => da * sa
     49  },
     50  "source-out": {
     51    "color": (sa, sc, da, dc) => sa * sc * (1 - da),
     52    "alpha": (sa, da) => sa * (1 - da)
     53  },
     54  "destination-out": {
     55    "color": (sa, sc, da, dc) => da * dc * (1 - sa),
     56    "alpha": (sa, da) => da * (1 - sa)
     57  },
     58  "source-atop": {
     59    "color": (sa, sc, da, dc) => sa * sc * da + da * dc * (1 - sa),
     60    "alpha": (sa, da) => sa * da + da * (1 - sa)
     61  },
     62  "destination-atop": {
     63    "color": (sa, sc, da, dc) => sa * sc * (1 - da) + da * dc * sa,
     64    "alpha": (sa, da) => sa * (1 - da) + da * sa
     65  },
     66  "xor": {
     67    "color": (sa, sc, da, dc) => sa * sc * (1 - da) + da * dc * (1 - sa),
     68    "alpha": (sa, da) => sa * (1 - da) + da * (1 - sa)
     69  },
     70  "lighter": {
     71    // TODO(https://github.com/w3c/fxtf-drafts/issues/446): All engines
     72    // actually implement 'lighter' using the formula for 'plus-lighter'
     73    // given below; we should update the spec to match!
     74    "color": (sa, sc, da, dc) => sa * sc + da * dc,
     75    "alpha": (sa, da) => sa + da
     76  },
     77  "plus-darker": {
     78    // TODO(https://github.com/w3c/fxtf-drafts/issues/447): This formula
     79    // is almost certainly wrong.  It doesn't make sense, and the one
     80    // engine that implements this value (WebKit) does something very
     81    // different.
     82    "color": (sa, sc, da, dc) => Math.max(0, 1 - sa * sc + 1 - da * dc),
     83    "alpha": (sa, da) => Math.max(0, 1 - sa + 1 - da)
     84  },
     85  "plus-lighter": {
     86    "color": (sa, sc, da, dc) => Math.min(1, sa * sc + da * dc),
     87    "alpha": (sa, da) => Math.min(1, sa + da)
     88  }
     89 };
     90 
     91 let canvas = document.getElementById("canvas");
     92 let cx = canvas.getContext("2d", { willReadFrequently: true });
     93 
     94 function roundup_255th(n) {
     95  return Math.ceil(n * 255) / 255;
     96 }
     97 
     98 function rounddown_255th(n) {
     99  return Math.floor(n * 255) / 255;
    100 }
    101 
    102 for (let op in COMPOSITE_OPERATORS) {
    103  test(function() {
    104    cx.save();
    105    this.add_cleanup(() => { cx.restore(); });
    106    for (let sc of VALUES) {
    107      for (let sa of VALUES) {
    108        for (let dc of VALUES) {
    109          for (let da of VALUES) {
    110            let desc = `g=${sc} a=${sa} ${op} g=${dc} a=${da}`;
    111            cx.restore();
    112            cx.save();
    113            cx.clearRect(0, 0, 2, 2);
    114            cx.fillStyle = `rgb(0, ${dc}, 0)`;
    115            cx.globalAlpha = da / 255;
    116            cx.fillRect(0, 0, 2, 2);
    117            cx.globalCompositeOperation = op;
    118            assert_equals(cx.globalCompositeOperation, op, "composite operation");
    119            cx.fillStyle = `rgb(0, ${sc}, 0)`;
    120            cx.globalAlpha = sa / 255;
    121            cx.fillRect(0, 0, 2, 2);
    122            let imageData = cx.getImageData(0, 0, 1, 1);
    123            assert_equals(imageData.data.length, 4, "length of ImageData");
    124            assert_equals(imageData.data[0], 0, `red: ${desc}`);
    125            assert_equals(imageData.data[2], 0, `blue: ${desc}`);
    126            let expected_color = COMPOSITE_OPERATORS[op].color(sa/255, sc/255, da/255, dc/255);
    127            let expected_alpha = COMPOSITE_OPERATORS[op].alpha(sa/255, da/255);
    128            let allowed_color_error;
    129            // undo the premultiplication:
    130            if (expected_alpha == 0) {
    131              assert_equals(expected_color, 0, `premultiplication zero check: ${desc}`);
    132              allowed_color_error = 0;
    133            } else {
    134              // We want to allow for the error in the color expectation
    135              // to increase when the expected alpha is small, because
    136              // we want to allow for roughly the same amount of error
    137              // in the (smaller) *premultiplied* value.
    138              let expected_min_color = rounddown_255th(expected_color) / roundup_255th(expected_alpha);
    139              let expected_max_color = roundup_255th(expected_color) / rounddown_255th(expected_alpha);
    140              // Set the expectation to the midpoint of the error range
    141              // rather than the actual accurate expectation.
    142              expected_color = (expected_max_color + expected_min_color) / 2;
    143              allowed_color_error = (expected_max_color - expected_min_color) / 2;
    144            }
    145            expected_color *= 255;
    146            expected_alpha *= 255;
    147            allowed_color_error *= 255;
    148            allowed_color_error += 3.5;
    149            assert_approx_equals(imageData.data[1], expected_color, allowed_color_error, `green: ${desc}`);
    150            assert_approx_equals(imageData.data[3], expected_alpha, 1.01, `alpha: ${desc}`);
    151          }
    152        }
    153      }
    154    }
    155  }, `globalCompositeOperation ${op}`);
    156 }
    157 </script>