1- name: 2d.filter.value
2  desc: test if ctx.filter works correctly
3  code: |
4    @assert ctx.filter == 'none';
5    ctx.filter = 'blur(5px)';
6    @assert ctx.filter == 'blur(5px)';
7    ctx.save();
8    ctx.filter = 'none';
9    @assert ctx.filter == 'none';
10    ctx.restore();
11    @assert ctx.filter == 'blur(5px)';
12
13    ctx.filter = 'blur(10)';
14    @assert ctx.filter == 'blur(5px)';
15    ctx.filter = 'blur 10px';
16    @assert ctx.filter == 'blur(5px)';
17
18    ctx.filter = 'inherit';
19    @assert ctx.filter == 'blur(5px)';
20    ctx.filter = 'initial';
21    @assert ctx.filter == 'blur(5px)';
22    ctx.filter = 'unset';
23    @assert ctx.filter == 'blur(5px)';
24
25    ctx.filter = '';
26    @assert ctx.filter == 'blur(5px)';
27    ctx.filter = null;
28    @assert ctx.filter == 'blur(5px)';
29    ctx.filter = undefined;
30    @assert ctx.filter == 'blur(5px)';
31
32    ctx.filter = 'blur(  5px)';
33    assert_equals(ctx.filter, 'blur(  5px)');
34
35- name: 2d.filter.canvasFilterObject
36  desc: Test CanvasFilter() object
37  code: |
38    @assert ctx.filter == 'none';
39    ctx.filter = 'blur(5px)';
40    @assert ctx.filter == 'blur(5px)';
41    ctx.filter = new CanvasFilter({blur: {stdDeviation: 5}});
42    @assert ctx.filter.toString() == '[object CanvasFilter]';
43    ctx.filter = new CanvasFilter([{blur: {stdDeviation: 5}}, {blur: {stdDeviation: 10}}]);
44    @assert ctx.filter.toString() == '[object CanvasFilter]';
45    var canvas2 = document.createElement('canvas');
46    var ctx2 = canvas2.getContext('2d');
47    ctx2.filter = ctx.filter;
48    @assert ctx.filter.toString() == '[object CanvasFilter]';
49    ctx.filter = 'blur(5px)';
50    @assert ctx.filter == 'blur(5px)';
51    ctx.filter = 'none';
52    @assert ctx.filter == 'none';
53    ctx.filter = new CanvasFilter({blur: {stdDeviation: 5}});
54    ctx.filter = "this string is not a filter and should do nothing";
55    @assert ctx.filter.toString() == '[object CanvasFilter]';
56
57- name: 2d.filter.canvasFilterObject.blur.exceptions
58  desc: Test exceptions on CanvasFilter() blur.object
59  code: |
60    @assert throws TypeError ctx.filter = new CanvasFilter({blur: null});
61    @assert throws TypeError ctx.filter = new CanvasFilter({blur: {}});
62    @assert throws TypeError ctx.filter = new CanvasFilter({blur: {stdDevation: null}});
63    @assert throws TypeError ctx.filter = new CanvasFilter({blur: {stdDeviation: "foo"}});
64
65- name: 2d.filter.canvasFilterObject.colorMatrix
66  desc: Test the functionality of ColorMatrix filters in CanvasFilter objects
67  code: |
68    @assert throws TypeError new CanvasFilter({colorMatrix: {values: undefined}});
69    @assert throws TypeError new CanvasFilter({colorMatrix: {values: "foo"}});
70    @assert throws TypeError new CanvasFilter({colorMatrix: {values: null}});
71    @assert throws TypeError new CanvasFilter({colorMatrix: {values: [1, 2, 3]}});
72    @assert throws TypeError new CanvasFilter({colorMatrix: {values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, "a"]}});
73    @assert throws TypeError new CanvasFilter({colorMatrix: {values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, Infinity]}});
74    ctx.fillStyle = "#f00";
75    ctx.filter = new CanvasFilter({colorMatrix: {type: "hueRotate", values: 0}});
76    ctx.fillRect(0, 0, 100, 50);
77    @assert pixel 10,10 ==~ 255,0,0,255;
78    ctx.filter = new CanvasFilter({colorMatrix: {type: "hueRotate", values: 90}});
79    ctx.fillRect(0, 0, 100, 50);
80    @assert pixel 10,10 ==~ 0,91,0,255;
81    ctx.filter = new CanvasFilter({colorMatrix: {type: "hueRotate", values: 180}});
82    ctx.fillRect(0, 0, 100, 50);
83    @assert pixel 10,10 ==~ 0,109,109,255;
84    ctx.filter = new CanvasFilter({colorMatrix: {type: "hueRotate", values: 270}});
85    ctx.fillRect(0, 0, 100, 50);
86    @assert pixel 10,10 ==~ 109,18,255,255;
87    ctx.filter = new CanvasFilter({colorMatrix: {type: "saturate", values: 0.5}});
88    ctx.fillRect(0, 0, 100, 50);
89    @assert pixel 10,10 ==~ 155,27,27,255;
90    ctx.clearRect(0, 0, 100, 50);
91    ctx.filter = new CanvasFilter({colorMatrix: {type: "luminanceToAlpha"}});
92    ctx.fillRect(0, 0, 100, 50);
93    @assert pixel 10,10 ==~ 0,0,0,54;
94    ctx.filter = new CanvasFilter({colorMatrix: {values: [
95            0, 0, 0, 0, 0,
96            1, 1, 1, 1, 0,
97            0, 0, 0, 0, 0,
98            0, 0, 0, 1, 0
99    ]}});
100    ctx.fillRect(0, 0, 50, 25);
101    ctx.fillStyle = "#0f0";
102    ctx.fillRect(50, 0, 50, 25);
103    ctx.fillStyle = "#00f";
104    ctx.fillRect(0, 25, 50, 25);
105    ctx.fillStyle = "#fff";
106    ctx.fillRect(50, 25, 50, 25);
107    @assert pixel 10,10 ==~ 0,255,0,255;
108    @assert pixel 60,10 ==~ 0,255,0,255;
109    @assert pixel 10,30 ==~ 0,255,0,255;
110    @assert pixel 60,30 ==~ 0,255,0,255;
111  expected: green
112
113- name: 2d.filter.canvasFilterObject.convolveMatrix.exceptions
114  desc: Test exceptions on CanvasFilter() convolveMatrix
115  code: |
116    @assert throws TypeError new CanvasFilter({convolveMatrix: {}});
117    @assert throws TypeError new CanvasFilter({convolveMatrix: null});
118    @assert throws TypeError new CanvasFilter({convolveMatrix: {divisor: 2}});
119    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: null}});
120    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: 1}});
121    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], [0]]}});
122    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, "a"], [0]]}});
123    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], 0]}});
124    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], [0, Infinity]]}});
125    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: []}});
126    // This should not throw an error
127    ctx.filter = new CanvasFilter({convolveMatrix: {kernelMatrix: [[]]}});
128
129- name: 2d.filter.canvasFilterObject.componentTransfer.linear
130  desc: Test pixels on CanvasFilter() componentTransfer with linear type
131  code: |
132    // From https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement
133    function getColor(inputColor, slopes, intercepts) {
134        return [
135            Math.max(0, Math.min(1, inputColor[0]/255 * slopes[0] + intercepts[0])) * 255,
136            Math.max(0, Math.min(1, inputColor[1]/255 * slopes[1] + intercepts[1])) * 255,
137            Math.max(0, Math.min(1, inputColor[2]/255 * slopes[2] + intercepts[2])) * 255,
138        ];
139    }
140
141    const slopes = [0.5, 1.2, -0.2];
142    const intercepts = [0.25, 0, 0.5];
143    ctx.filter = new CanvasFilter({componentTransfer: {
144        funcR: {type: "linear", slope: slopes[0], intercept: intercepts[0]},
145        funcG: {type: "linear", slope: slopes[1], intercept: intercepts[1]},
146        funcB: {type: "linear", slope: slopes[2], intercept: intercepts[2]},
147    }});
148
149    const inputColors = [
150        [255, 255, 255],
151        [0, 0, 0],
152        [127, 0, 34],
153        [252, 186, 3],
154        [50, 68, 87],
155    ];
156
157    for (const color of inputColors) {
158        let outputColor = getColor(color, slopes, intercepts);
159        ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
160        ctx.fillRect(0, 0, 10, 10);
161        _assertPixelApprox(canvas, 5, 5, outputColor[0],outputColor[1],outputColor[2],255, "5,5", `${outputColor[0]},${outputColor[1]},${outputColor[2]}`, 2);
162    }
163
164- name: 2d.filter.canvasFilterObject.componentTransfer.identity
165  desc: Test pixels on CanvasFilter() componentTransfer with identity type
166  code: |
167    ctx.filter = new CanvasFilter({componentTransfer: {
168        funcR: {type: "identity"},
169        funcG: {type: "identity"},
170        funcB: {type: "identity"},
171    }});
172
173    const inputColors = [
174        [255, 255, 255],
175        [0, 0, 0],
176        [127, 0, 34],
177        [252, 186, 3],
178        [50, 68, 87],
179    ];
180
181    for (const color of inputColors) {
182        ctx.fillStyle = `rgba(${color[0]}, ${color[1]}, ${color[2]}, 1)`,
183        ctx.fillRect(0, 0, 10, 10);
184        _assertPixel(canvas, 5, 5, color[0],color[1],color[2],255, "5,5", `${color[0]},${color[1]},${color[2]}`);
185    }
186
187- name: 2d.filter.canvasFilterObject.componentTransfer.gamma
188  desc: Test pixels on CanvasFilter() componentTransfer with gamma type
189  code: |
190    // From https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement
191    function getColor(inputColor, amplitude, exponent, offset) {
192        return [
193            Math.max(0, Math.min(1, Math.pow(inputColor[0]/255, exponent[0]) * amplitude[0] + offset[0])) * 255,
194            Math.max(0, Math.min(1, Math.pow(inputColor[1]/255, exponent[1]) * amplitude[1] + offset[1])) * 255,
195            Math.max(0, Math.min(1, Math.pow(inputColor[2]/255, exponent[2]) * amplitude[2] + offset[2])) * 255,
196        ];
197    }
198
199    const amplitudes = [2, 1.1, 0.5];
200    const exponents = [5, 3, 1];
201    const offsets = [0.25, 0, 0.5];
202    ctx.filter = new CanvasFilter({componentTransfer: {
203        funcR: {type: "gamma", amplitude: amplitudes[0], exponent: exponents[0], offset: offsets[0]},
204        funcG: {type: "gamma", amplitude: amplitudes[1], exponent: exponents[1], offset: offsets[1]},
205        funcB: {type: "gamma", amplitude: amplitudes[2], exponent: exponents[2], offset: offsets[2]},
206    }});
207
208    const inputColors = [
209        [255, 255, 255],
210        [0, 0, 0],
211        [127, 0, 34],
212        [252, 186, 3],
213        [50, 68, 87],
214    ];
215
216    for (const color of inputColors) {
217        let outputColor = getColor(color, amplitudes, exponents, offsets);
218        ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
219        ctx.fillRect(0, 0, 10, 10);
220        _assertPixelApprox(canvas, 5, 5, outputColor[0],outputColor[1],outputColor[2],255, "5,5", `${outputColor[0]},${outputColor[1]},${outputColor[2]}`, 2);
221    }
222
223- name: 2d.filter.canvasFilterObject.componentTransfer.table
224  desc: Test pixels on CanvasFilter() componentTransfer with table type
225  code: |
226    // From https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement
227    function getTransformedValue(C, V) {
228        // Get the right interval
229        const n = V.length - 1;
230        const k = C == 1 ? n - 1 : Math.floor(C * n);
231        return V[k] + (C - k/n) * n * (V[k + 1] - V[k]);
232    }
233
234    function getColor(inputColor, tableValues) {
235        const result = [0, 0, 0];
236        for (const i in inputColor) {
237            const C = inputColor[i]/255;
238            const Cprime = getTransformedValue(C, tableValues[i]);
239            result[i] = Math.max(0, Math.min(1, Cprime)) * 255;
240        }
241        return result;
242    }
243
244    tableValuesR = [0, 0, 1, 1];
245    tableValuesG = [2, 0, 0.5, 3];
246    tableValuesB = [1, -1, 5, 0];
247    ctx.filter = new CanvasFilter({componentTransfer: {
248        funcR: {type: "table", tableValues: tableValuesR},
249        funcG: {type: "table", tableValues: tableValuesG},
250        funcB: {type: "table", tableValues: tableValuesB},
251    }});
252
253    const inputColors = [
254        [255, 255, 255],
255        [0, 0, 0],
256        [127, 0, 34],
257        [252, 186, 3],
258        [50, 68, 87],
259    ];
260
261    for (const color of inputColors) {
262        let outputColor = getColor(color, [tableValuesR, tableValuesG, tableValuesB]);
263        ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
264        ctx.fillRect(0, 0, 10, 10);
265        _assertPixelApprox(canvas, 5, 5, outputColor[0],outputColor[1],outputColor[2],255, "5,5", `${outputColor[0]},${outputColor[1]},${outputColor[2]}`, 2);
266    }
267
268- name: 2d.filter.canvasFilterObject.componentTransfer.discrete
269  desc: Test pixels on CanvasFilter() componentTransfer with discrete type
270  code: |
271    // From https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement
272    function getTransformedValue(C, V) {
273        // Get the right interval
274        const n = V.length;
275        const k = C == 1 ? n - 1 : Math.floor(C * n);
276        return V[k];
277    }
278
279    function getColor(inputColor, tableValues) {
280        const result = [0, 0, 0];
281        for (const i in inputColor) {
282            const C = inputColor[i]/255;
283            const Cprime = getTransformedValue(C, tableValues[i]);
284            result[i] = Math.max(0, Math.min(1, Cprime)) * 255;
285        }
286        return result;
287    }
288
289    tableValuesR = [0, 0, 1, 1];
290    tableValuesG = [2, 0, 0.5, 3];
291    tableValuesB = [1, -1, 5, 0];
292    ctx.filter = new CanvasFilter({componentTransfer: {
293        funcR: {type: "discrete", tableValues: tableValuesR},
294        funcG: {type: "discrete", tableValues: tableValuesG},
295        funcB: {type: "discrete", tableValues: tableValuesB},
296    }});
297
298    const inputColors = [
299        [255, 255, 255],
300        [0, 0, 0],
301        [127, 0, 34],
302        [252, 186, 3],
303        [50, 68, 87],
304    ];
305
306    for (const color of inputColors) {
307        let outputColor = getColor(color, [tableValuesR, tableValuesG, tableValuesB]);
308        ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
309        ctx.fillRect(0, 0, 10, 10);
310        _assertPixelApprox(canvas, 5, 5, outputColor[0],outputColor[1],outputColor[2],255, "5,5", `${outputColor[0]},${outputColor[1]},${outputColor[2]}`, 2);
311    }
312