summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/css/compositing/canvas-composite-modes.html
blob: c1aae52ce6f65fcaf1ba2a184ac1470c5f67cbfb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
<!DOCTYPE HTML>
<meta charset="utf-8">
<title>Test of &lt;composite-mode&gt; values in canvas globalCompositeOperation</title>
<link rel="help" href="https://html.spec.whatwg.org/multipage/C#compositing">
<link rel="help" href="https://drafts.fxtf.org/compositing/#canvascompositingandblending">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<canvas id="canvas" width="2" height="2"></canvas>

<script>

// Test a small set of sRGB color and alpha values in the 0-255 range.
const VALUES = [ 0, 47, 193, 255 ];

const COMPOSITE_OPERATORS = {
  // Define a "color" function accepting source and destination colors
  // and alphas, and an "alpha" function accepting source and
  // destination alphas.
  "clear": {
    "color": (sa, sc, da, dc) => 0,
    "alpha": (sa, da) => 0
  },
  "copy": {
    "color": (sa, sc, da, dc) => sa * sc,
    "alpha": (sa, da) => sa
  },
  "destination": {
    // TODO(dbaron): The spec says this should work, but none of
    // Chromium, Gecko, or WebKit appear to implement it.
    "color": (sa, sc, da, dc) => da * dc,
    "alpha": (sa, da) => da
  },
  "source-over": {
    "color": (sa, sc, da, dc) => sa * sc + da * dc * (1 - sa),
    "alpha": (sa, da) => sa + da * (1 - sa)
  },
  "destination-over": {
    "color": (sa, sc, da, dc) => sa * sc * (1 - da) + da * dc,
    "alpha": (sa, da) => sa * (1 - da) + da
  },
  "source-in": {
    "color": (sa, sc, da, dc) => sa * sc * da,
    "alpha": (sa, da) => sa * da
  },
  "destination-in": {
    "color": (sa, sc, da, dc) => da * dc * sa,
    "alpha": (sa, da) => da * sa
  },
  "source-out": {
    "color": (sa, sc, da, dc) => sa * sc * (1 - da),
    "alpha": (sa, da) => sa * (1 - da)
  },
  "destination-out": {
    "color": (sa, sc, da, dc) => da * dc * (1 - sa),
    "alpha": (sa, da) => da * (1 - sa)
  },
  "source-atop": {
    "color": (sa, sc, da, dc) => sa * sc * da + da * dc * (1 - sa),
    "alpha": (sa, da) => sa * da + da * (1 - sa)
  },
  "destination-atop": {
    "color": (sa, sc, da, dc) => sa * sc * (1 - da) + da * dc * sa,
    "alpha": (sa, da) => sa * (1 - da) + da * sa
  },
  "xor": {
    "color": (sa, sc, da, dc) => sa * sc * (1 - da) + da * dc * (1 - sa),
    "alpha": (sa, da) => sa * (1 - da) + da * (1 - sa)
  },
  "lighter": {
    // TODO(https://github.com/w3c/fxtf-drafts/issues/446): All engines
    // actually implement 'lighter' using the formula for 'plus-lighter'
    // given below; we should update the spec to match!
    "color": (sa, sc, da, dc) => sa * sc + da * dc,
    "alpha": (sa, da) => sa + da
  },
  "plus-darker": {
    // TODO(https://github.com/w3c/fxtf-drafts/issues/447): This formula
    // is almost certainly wrong.  It doesn't make sense, and the one
    // engine that implements this value (WebKit) does something very
    // different.
    "color": (sa, sc, da, dc) => Math.max(0, 1 - sa * sc + 1 - da * dc),
    "alpha": (sa, da) => Math.max(0, 1 - sa + 1 - da)
  },
  "plus-lighter": {
    "color": (sa, sc, da, dc) => Math.min(1, sa * sc + da * dc),
    "alpha": (sa, da) => Math.min(1, sa + da)
  }
};

let canvas = document.getElementById("canvas");
let cx = canvas.getContext("2d", { willReadFrequently: true });

function roundup_255th(n) {
  return Math.ceil(n * 255) / 255;
}

function rounddown_255th(n) {
  return Math.floor(n * 255) / 255;
}

for (let op in COMPOSITE_OPERATORS) {
  test(function() {
    cx.save();
    this.add_cleanup(() => { cx.restore(); });
    for (let sc of VALUES) {
      for (let sa of VALUES) {
        for (let dc of VALUES) {
          for (let da of VALUES) {
            let desc = `g=${sc} a=${sa} ${op} g=${dc} a=${da}`;
            cx.restore();
            cx.save();
            cx.clearRect(0, 0, 2, 2);
            cx.fillStyle = `rgb(0, ${dc}, 0)`;
            cx.globalAlpha = da / 255;
            cx.fillRect(0, 0, 2, 2);
            cx.globalCompositeOperation = op;
            assert_equals(cx.globalCompositeOperation, op, "composite operation");
            cx.fillStyle = `rgb(0, ${sc}, 0)`;
            cx.globalAlpha = sa / 255;
            cx.fillRect(0, 0, 2, 2);
            let imageData = cx.getImageData(0, 0, 1, 1);
            assert_equals(imageData.data.length, 4, "length of ImageData");
            assert_equals(imageData.data[0], 0, `red: ${desc}`);
            assert_equals(imageData.data[2], 0, `blue: ${desc}`);
            let expected_color = COMPOSITE_OPERATORS[op].color(sa/255, sc/255, da/255, dc/255);
            let expected_alpha = COMPOSITE_OPERATORS[op].alpha(sa/255, da/255);
            let allowed_color_error;
            // undo the premultiplication:
            if (expected_alpha == 0) {
              assert_equals(expected_color, 0, `premultiplication zero check: ${desc}`);
              allowed_color_error = 0;
            } else {
              // We want to allow for the error in the color expectation
              // to increase when the expected alpha is small, because
              // we want to allow for roughly the same amount of error
              // in the (smaller) *premultiplied* value.
              let expected_min_color = rounddown_255th(expected_color) / roundup_255th(expected_alpha);
              let expected_max_color = roundup_255th(expected_color) / rounddown_255th(expected_alpha);
              // Set the expectation to the midpoint of the error range
              // rather than the actual accurate expectation.
              expected_color = (expected_max_color + expected_min_color) / 2;
              allowed_color_error = (expected_max_color - expected_min_color) / 2;
            }
            expected_color *= 255;
            expected_alpha *= 255;
            allowed_color_error *= 255;
            allowed_color_error += 3.5;
            assert_approx_equals(imageData.data[1], expected_color, allowed_color_error, `green: ${desc}`);
            assert_approx_equals(imageData.data[3], expected_alpha, 1.01, `alpha: ${desc}`);
          }
        }
      }
    }
  }, `globalCompositeOperation ${op}`);
}
</script>