diff options
Diffstat (limited to 'testing/web-platform/tests/html/canvas/tools')
5 files changed, 854 insertions, 279 deletions
diff --git a/testing/web-platform/tests/html/canvas/tools/gentestutilsunion.py b/testing/web-platform/tests/html/canvas/tools/gentestutilsunion.py index cf141f2f07..d7042810be 100644 --- a/testing/web-platform/tests/html/canvas/tools/gentestutilsunion.py +++ b/testing/web-platform/tests/html/canvas/tools/gentestutilsunion.py @@ -28,7 +28,7 @@ # # * Test the tests, add new ones to Git, remove deleted ones from Git, etc. -from typing import Any, List, Mapping, Optional, Set, Tuple +from typing import Any, DefaultDict, List, Mapping, Optional, Set, Tuple import re import collections @@ -184,6 +184,10 @@ def _remove_extra_newlines(text: str) -> str: return text def _expand_test_code(code: str) -> str: + code = re.sub(r' @moz-todo', '', code) + + code = re.sub(r'@moz-UniversalBrowserRead;', '', code) + code = _remove_extra_newlines(code) # Unroll expressions with a cross-product-style parameter expansion. @@ -202,11 +206,13 @@ def _expand_test_code(code: str) -> str: code = re.sub(r'@assert pixel (\d+,\d+) ==~ (\d+,\d+,\d+,\d+) \+/- (\d+);', r'_assertPixelApprox(canvas, \1, \2, \3);', code) - code = re.sub(r'@assert throws (\S+_ERR) (.*);', - r'assert_throws_dom("\1", function() { \2; });', code) + code = re.sub(r'@assert throws (\S+_ERR) (.*?);$', + r'assert_throws_dom("\1", function() { \2; });', code, + flags=re.MULTILINE | re.DOTALL) - code = re.sub(r'@assert throws (\S+Error) (.*);', - r'assert_throws_js(\1, function() { \2; });', code) + code = re.sub(r'@assert throws (\S+Error) (.*?);$', + r'assert_throws_js(\1, function() { \2; });', code, + flags=re.MULTILINE | re.DOTALL) code = re.sub( r'@assert (.*) === (.*);', lambda m: '_assertSame(%s, %s, "%s", "%s");' @@ -226,10 +232,6 @@ def _expand_test_code(code: str) -> str: r'@assert (.*);', lambda m: '_assert(%s, "%s");' % (m.group( 1), _escapeJS(m.group(1))), code) - code = re.sub(r' @moz-todo', '', code) - - code = re.sub(r'@moz-UniversalBrowserRead;', '', code) - assert ('@' not in code) return code @@ -376,45 +378,50 @@ def _write_testharness_test(jinja_env: jinja2.Environment, 'utf-8') +def _generate_expected_image(expected: str, name: str, sub_dir: str, + enabled_canvas_types: Set[CanvasType], + html_canvas_cfg: TestConfig, + offscreen_canvas_cfg: TestConfig) -> str: + """Creates a reference image using Cairo and returns the file location.""" + if expected == 'green': + return '/images/green-100x50.png' + if expected == 'clear': + return '/images/clear-100x50.png' + if ';' in expected: + print('Found semicolon in %s' % name) + expected = re.sub( + r'^size (\d+) (\d+)', + r'surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, \1, \2)' + r'\ncr = cairo.Context(surface)', expected) + + if CanvasType.HTML_CANVAS in enabled_canvas_types: + expected_canvas = ( + expected + "\nsurface.write_to_png('%s.png')\n" % + os.path.join(html_canvas_cfg.image_out_dir, sub_dir, name)) + eval(compile(expected_canvas, '<test %s>' % name, 'exec'), {}, + {'cairo': cairo}) + + if {CanvasType.OFFSCREEN_CANVAS, CanvasType.WORKER} & enabled_canvas_types: + expected_offscreen = ( + expected + "\nsurface.write_to_png('%s.png')\n" % + os.path.join(offscreen_canvas_cfg.image_out_dir, sub_dir, name)) + eval(compile(expected_offscreen, '<test %s>' % name, 'exec'), {}, + {'cairo': cairo}) + + return '%s.png' % name + + def _generate_test(test: Mapping[str, Any], jinja_env: jinja2.Environment, - sub_dir: str, enabled_tests: Set[CanvasType], + name_to_sub_dir: Mapping[str, str], + used_tests: DefaultDict[str, Set[CanvasType]], html_canvas_cfg: TestConfig, offscreen_canvas_cfg: TestConfig) -> None: _validate_test(test) name = test['name'] - expected_img = None - if 'expected' in test and test['expected'] is not None: - expected = test['expected'] - if expected == 'green': - expected_img = '/images/green-100x50.png' - elif expected == 'clear': - expected_img = '/images/clear-100x50.png' - else: - if ';' in expected: - print('Found semicolon in %s' % name) - expected = re.sub( - r'^size (\d+) (\d+)', - r'surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, \1, \2)' - r'\ncr = cairo.Context(surface)', expected) - - if CanvasType.HTML_CANVAS in enabled_tests: - expected_canvas = ( - expected + "\nsurface.write_to_png('%s.png')\n" % - os.path.join(html_canvas_cfg.image_out_dir, sub_dir, name)) - eval(compile(expected_canvas, '<test %s>' % name, 'exec'), {}, - {'cairo': cairo}) - - if {CanvasType.OFFSCREEN_CANVAS, CanvasType.WORKER} & enabled_tests: - expected_offscreen = ( - expected + - "\nsurface.write_to_png('%s.png')\n" % os.path.join( - offscreen_canvas_cfg.image_out_dir, sub_dir, name)) - eval(compile(expected_offscreen, '<test %s>' % name, 'exec'), - {}, {'cairo': cairo}) - - expected_img = '%s.png' % name + sub_dir = _get_test_sub_dir(name, name_to_sub_dir) + enabled_canvas_types = _get_enabled_canvas_types(test) # Defaults: params = { @@ -423,11 +430,29 @@ def _generate_test(test: Mapping[str, Any], jinja_env: jinja2.Environment, } params.update(test) + + # Render parameters used in the test name. + name = jinja_env.from_string(name).render(params) + print('\r(%s)' % name, ' ' * 32, '\t') + + expected_img = None + if 'expected' in test and test['expected'] is not None: + expected_img = _generate_expected_image(test['expected'], name, + sub_dir, enabled_canvas_types, + html_canvas_cfg, + offscreen_canvas_cfg) + params.update({ 'code': _expand_test_code(test['code']), 'expected_img': expected_img }) + already_tested = used_tests[name].intersection(enabled_canvas_types) + if already_tested: + raise InvalidTestDefinitionError( + f'Test {name} is defined twice for types {already_tested}') + used_tests[name].update(enabled_canvas_types) + canvas_path = os.path.join(html_canvas_cfg.out_dir, sub_dir, name) offscreen_path = os.path.join(offscreen_canvas_cfg.out_dir, sub_dir, name) if 'manual' in test: @@ -435,11 +460,55 @@ def _generate_test(test: Mapping[str, Any], jinja_env: jinja2.Environment, offscreen_path += '-manual' if 'reference' in test or 'html_reference' in test: - _write_reference_test(jinja_env, params, enabled_tests, + _write_reference_test(jinja_env, params, enabled_canvas_types, canvas_path, offscreen_path) else: - _write_testharness_test(jinja_env, params, enabled_tests, canvas_path, - offscreen_path) + _write_testharness_test(jinja_env, params, enabled_canvas_types, + canvas_path, offscreen_path) + + +def _recursive_expand_variant_matrix(test_list: List[Mapping[str, Any]], + variant_matrix: List[Mapping[str, Any]], + current_selection: List[Tuple[str, Any]], + original_test: Mapping[str, Any]): + if len(current_selection) == len(variant_matrix): + # Selection for each variant is done, so add a new test to test_list. + test = original_test.copy() + variant_name_list = [] + should_append_variant_names = original_test.get( + 'append_variants_to_name', True) + for variant_name, variant_params in current_selection: + variant_name_list.append(variant_name) + # Append variant name. Variant names starting with '_' are + # not appended, which is useful to create variants with the same + # name in different folders (element vs. offscreen). + if (should_append_variant_names + and not variant_name.startswith('_')): + test['name'] += '.' + variant_name + test.update(variant_params) + # Expose variant names as a list so they can be used from the yaml + # files, which helps with better naming of tests. + test.update({'variant_names': variant_name_list}) + test_list.append(test) + else: + # Continue the recursion with each possible selection for the current + # variant. + variant = variant_matrix[len(current_selection)] + for variant_options in variant.items(): + current_selection.append(variant_options) + _recursive_expand_variant_matrix(test_list, variant_matrix, + current_selection, original_test) + current_selection.pop() + + +def _expand_variant_matrix( + variant_matrix: List[Mapping[str, Any]], + original_test: Mapping[str, Any]) -> List[Mapping[str, Any]]: + current_selection = [] + matrix_tests = [] + _recursive_expand_variant_matrix(matrix_tests, variant_matrix, + current_selection, original_test) + return matrix_tests def genTestUtils_union(NAME2DIRFILE: str) -> None: @@ -495,41 +564,25 @@ def genTestUtils_union(NAME2DIRFILE: str) -> None: pass # Ignore if it already exists, used_tests = collections.defaultdict(set) - for original_test in tests: - variants = original_test.get('variants', {'': dict()}) - for variant_name, variant_params in variants.items(): - test = original_test.copy() - if variant_name or variant_params: - # Append variant name. Variant names starting with '_' are - # not appended, which is useful to create variants with the same - # name in different folders (element vs. offscreen). - if not variant_name.startswith('_'): - test['name'] += '.' + variant_name - test.update(variant_params) - - name = test['name'] - print('\r(%s)' % name, ' ' * 32, '\t') - - enabled_canvas_types = _get_enabled_canvas_types(test) - - already_tested = used_tests[name].intersection( - enabled_canvas_types) - if already_tested: - raise InvalidTestDefinitionError( - f'Test {name} is defined twice for types {already_tested}') - used_tests[name].update(enabled_canvas_types) - - sub_dir = _get_test_sub_dir(name, name_to_sub_dir) - _generate_test( - test, - jinja_env, - sub_dir, - enabled_canvas_types, - html_canvas_cfg=TestConfig( - out_dir=CANVASOUTPUTDIR, - image_out_dir=CANVASIMAGEOUTPUTDIR), - offscreen_canvas_cfg=TestConfig( - out_dir=OFFSCREENCANVASOUTPUTDIR, - image_out_dir=OFFSCREENCANVASIMAGEOUTPUTDIR)) + for test in tests: + if 'variant_matrix' in test: + variants = _expand_variant_matrix(test['variant_matrix'], test) + elif 'variants' in test: + variant_matrix = [test['variants']] + variants = _expand_variant_matrix(variant_matrix, test) + else: + variants = [test] + + for variant in variants: + _generate_test(variant, + jinja_env, + name_to_sub_dir, + used_tests, + html_canvas_cfg=TestConfig( + out_dir=CANVASOUTPUTDIR, + image_out_dir=CANVASIMAGEOUTPUTDIR), + offscreen_canvas_cfg=TestConfig( + out_dir=OFFSCREENCANVASOUTPUTDIR, + image_out_dir=OFFSCREENCANVASIMAGEOUTPUTDIR)) print() diff --git a/testing/web-platform/tests/html/canvas/tools/name2dir-offscreen.yaml b/testing/web-platform/tests/html/canvas/tools/name2dir-offscreen.yaml index c52acb793b..6e4b3f42b2 100644 --- a/testing/web-platform/tests/html/canvas/tools/name2dir-offscreen.yaml +++ b/testing/web-platform/tests/html/canvas/tools/name2dir-offscreen.yaml @@ -19,4 +19,4 @@ 2d.missingargs: "conformance-requirements" 2d.voidreturn: "conformance-requirements" 2d.canvas.host: "canvas-host" -2d.canvas.context: "canvas-context"
\ No newline at end of file +2d.canvas.context: "canvas-context" diff --git a/testing/web-platform/tests/html/canvas/tools/yaml-new/drawing-images-to-the-canvas.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/drawing-images-to-the-canvas.yaml index 93c556288d..09e9e00186 100644 --- a/testing/web-platform/tests/html/canvas/tools/yaml-new/drawing-images-to-the-canvas.yaml +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/drawing-images-to-the-canvas.yaml @@ -638,3 +638,11 @@ @nonfinite ctx.drawImage(<bitmap>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <100 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <100 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>); @assert pixel 50,25 == 0,255,0,255; expected: green + +- name: 2d.drawImage.detachedcanvas + desc: drawImage with detached OffscreenCanvas as the source should throw exception + canvasType: ['HTMLCanvas'] + code: | + var canvas2 = new OffscreenCanvas(80, 80); + (new MessageChannel()).port1.postMessage(canvas2, [canvas2]); + @assert throws INVALID_STATE_ERR ctx.drawImage(canvas2, 0, 0); diff --git a/testing/web-platform/tests/html/canvas/tools/yaml-new/filters.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/filters.yaml index 01c83a33e2..f327b9fe94 100644 --- a/testing/web-platform/tests/html/canvas/tools/yaml-new/filters.yaml +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/filters.yaml @@ -89,51 +89,102 @@ ctx.filter = 'this string is not a filter and should do nothing'; @assert ctx.filter.toString() == '[object CanvasFilter]'; -- name: 2d.filter.canvasFilterObject.blur.exceptions.tentative - desc: Test exceptions on CanvasFilter() blur.object +- name: 2d.filter.{{ variant_names[0] }}.blur.exceptions{{ tentative }} + desc: Test exceptions on gaussianBlur filter code: | - @assert throws TypeError ctx.filter = new CanvasFilter({name: 'gaussianBlur'}); - @assert throws TypeError ctx.filter = new CanvasFilter({name: 'gaussianBlur', stdDeviation: undefined}); - @assert throws TypeError ctx.filter = new CanvasFilter({name: 'gaussianBlur', stdDeviation: 'foo'}); - @assert throws TypeError ctx.filter = new CanvasFilter({name: 'gaussianBlur', stdDeviation: [1,2,3]}); - @assert throws TypeError ctx.filter = new CanvasFilter({name: 'gaussianBlur', stdDeviation: NaN}); - @assert throws TypeError ctx.filter = new CanvasFilter({name: 'gaussianBlur', stdDeviation: {}}); - -- name: 2d.filter.canvasFilterObject.colorMatrix.tentative - desc: Test the functionality of ColorMatrix filters in CanvasFilter objects + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'gaussianBlur'}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'gaussianBlur', stdDeviation: undefined}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'gaussianBlur', stdDeviation: 'foo'}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'gaussianBlur', stdDeviation: [1,2,3]}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'gaussianBlur', stdDeviation: NaN}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'gaussianBlur', stdDeviation: {}}") }}; + append_variants_to_name: false + variants: + layers: + filter_declaration: |- + ctx.beginLayer({filter: + param}) + canvasFilterObject: + filter_declaration: |- + ctx.filter = new CanvasFilter( + param) + tentative: .tentative + +- name: 2d.filter.{{ variant_names[0] }}.colorMatrix{{ tentative }} + desc: Test the functionality of ColorMatrix filters code: | - @assert throws TypeError new CanvasFilter({name: 'colorMatrix', values: undefined}); - @assert throws TypeError new CanvasFilter({name: 'colorMatrix', values: 'foo'}); - @assert throws TypeError new CanvasFilter({name: 'colorMatrix', values: null}); - @assert throws TypeError new CanvasFilter({name: 'colorMatrix', values: [1, 2, 3]}); - @assert throws TypeError new CanvasFilter({name: 'colorMatrix', values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 'a']}); - @assert throws TypeError new CanvasFilter({name: 'colorMatrix', values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, Infinity]}); + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'colorMatrix', values: undefined}") }}; + + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'colorMatrix', values: 'foo'}") }}; + + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'colorMatrix', values: null}") }}; + + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'colorMatrix', values: [1, 2, 3]}") }}; + + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'colorMatrix', + values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 'a']}") }}; + + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'colorMatrix', + values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, Infinity]}") }}; + ctx.fillStyle = '#f00'; - ctx.filter = new CanvasFilter({name: 'colorMatrix', type: 'hueRotate', values: 0}); + {{ filter_declaration | replace("param", + "{name: 'colorMatrix', type: 'hueRotate', values: 0}") }}; ctx.fillRect(0, 0, 100, 50); + {{ close_layer -}} @assert pixel 10,10 ==~ 255,0,0,255; - ctx.filter = new CanvasFilter({name: 'colorMatrix', type: 'hueRotate', values: 90}); + + {{ filter_declaration | replace("param", + "{name: 'colorMatrix', type: 'hueRotate', values: 90}") }}; ctx.fillRect(0, 0, 100, 50); + {{ close_layer -}} @assert pixel 10,10 ==~ 0,91,0,255; - ctx.filter = new CanvasFilter({name: 'colorMatrix', type: 'hueRotate', values: 180}); + + {{ filter_declaration | replace("param", + "{name: 'colorMatrix', type: 'hueRotate', values: 180}") }}; ctx.fillRect(0, 0, 100, 50); + {{ close_layer -}} @assert pixel 10,10 ==~ 0,109,109,255; - ctx.filter = new CanvasFilter({name: 'colorMatrix', type: 'hueRotate', values: 270}); + + {{ filter_declaration | replace("param", + "{name: 'colorMatrix', type: 'hueRotate', values: 270}") }}; ctx.fillRect(0, 0, 100, 50); + {{ close_layer -}} @assert pixel 10,10 ==~ 109,18,255,255; - ctx.filter = new CanvasFilter({name: 'colorMatrix', type: 'saturate', values: 0.5}); + + {{ filter_declaration | replace("param", + "{name: 'colorMatrix', type: 'saturate', values: 0.5}") }}; ctx.fillRect(0, 0, 100, 50); + {{ close_layer -}} @assert pixel 10,10 ==~ 155,27,27,255; + ctx.clearRect(0, 0, 100, 50); - ctx.filter = new CanvasFilter({name: 'colorMatrix', type: 'luminanceToAlpha'}); + {{ filter_declaration | replace("param", + "{name: 'colorMatrix', type: 'luminanceToAlpha'}") }}; ctx.fillRect(0, 0, 100, 50); + {{ close_layer -}} @assert pixel 10,10 ==~ 0,0,0,54; - ctx.filter = new CanvasFilter({name: 'colorMatrix', values: [ - 0, 0, 0, 0, 0, - 1, 1, 1, 1, 0, - 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]}); + + {{ filter_declaration | replace("param", "{name: 'colorMatrix', values: [ + 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]}") }}; ctx.fillRect(0, 0, 50, 25); ctx.fillStyle = '#0f0'; ctx.fillRect(50, 0, 50, 25); @@ -141,51 +192,120 @@ ctx.fillRect(0, 25, 50, 25); ctx.fillStyle = '#fff'; ctx.fillRect(50, 25, 50, 25); + {{ close_layer -}} @assert pixel 10,10 ==~ 0,255,0,255; @assert pixel 60,10 ==~ 0,255,0,255; @assert pixel 10,30 ==~ 0,255,0,255; @assert pixel 60,30 ==~ 0,255,0,255; - -- name: 2d.filter.canvasFilterObject.convolveMatrix.exceptions.tentative + append_variants_to_name: false + variants: + layers: + filter_declaration: |- + ctx.beginLayer({filter: + param}) + close_layer: | + ctx.endLayer(); + canvasFilterObject: + filter_declaration: |- + ctx.filter = new CanvasFilter( + param) + tentative: .tentative + +- name: 2d.filter.{{ variant_names[0] }}.convolveMatrix.exceptions{{ tentative }} desc: Test exceptions on CanvasFilter() convolveMatrix code: | - @assert throws TypeError new CanvasFilter({name: 'convolveMatrix'}); - @assert throws TypeError new CanvasFilter({name: 'convolveMatrix', divisor: 2}); - @assert throws TypeError new CanvasFilter({name: 'convolveMatrix', kernelMatrix: null}); - @assert throws TypeError new CanvasFilter({name: 'convolveMatrix', kernelMatrix: 1}); - @assert throws TypeError new CanvasFilter({name: 'convolveMatrix', kernelMatrix: [[1, 0], [0]]}); - @assert throws TypeError new CanvasFilter({name: 'convolveMatrix', kernelMatrix: [[1, 'a'], [0]]}); - @assert throws TypeError new CanvasFilter({name: 'convolveMatrix', kernelMatrix: [[1, 0], 0]}); - @assert throws TypeError new CanvasFilter({name: 'convolveMatrix', kernelMatrix: [[1, 0], [0, Infinity]]}); - @assert throws TypeError new CanvasFilter({name: 'convolveMatrix', kernelMatrix: []}); - @assert throws TypeError new CanvasFilter({name: 'convolveMatrix', kernelMatrix: [1]}); - @assert throws TypeError new CanvasFilter({name: 'convolveMatrix', kernelMatrix: [1, 2, 3, 4]}); - @assert throws TypeError new CanvasFilter({name: 'convolveMatrix', kernelMatrix: [[], []]}); - @assert throws TypeError new CanvasFilter({name: 'convolveMatrix', kernelMatrix: [[1, 2], []]}); - @assert throws TypeError new CanvasFilter({name: 'convolveMatrix', kernelMatrix: [[], [1, 2]]}); + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'convolveMatrix'}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'convolveMatrix', divisor: 2}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'convolveMatrix', kernelMatrix: null}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'convolveMatrix', kernelMatrix: 1}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'convolveMatrix', kernelMatrix: [[1, 0], [0]]}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'convolveMatrix', kernelMatrix: [[1, 'a'], [0]]}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'convolveMatrix', kernelMatrix: [[1, 0], 0]}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'convolveMatrix', kernelMatrix: [[1, 0], [0, Infinity]]}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'convolveMatrix', kernelMatrix: []}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'convolveMatrix', kernelMatrix: [1]}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'convolveMatrix', kernelMatrix: [1, 2, 3, 4]}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'convolveMatrix', kernelMatrix: [[], []]}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'convolveMatrix', kernelMatrix: [[1, 2], []]}") }}; + @assert throws TypeError {{ filter_declaration | replace("param", + "{name: 'convolveMatrix', kernelMatrix: [[], [1, 2]]}") }}; // This should not throw an error - ctx.filter = new CanvasFilter({name: 'convolveMatrix', kernelMatrix: [[]]}); - ctx.filter = new CanvasFilter({name: 'convolveMatrix', kernelMatrix: [[1]]}); - -- name: 2d.filter.canvasFilterObject.componentTransfer.linear.tentative + {{ filter_declaration | replace("param", + "{name: 'convolveMatrix', kernelMatrix: [[]]}") }}; + {{ close_layer -}} + {{ filter_declaration | replace("param", + "{name: 'convolveMatrix', kernelMatrix: [[1]]}") }}; + {{ close_layer -}} + append_variants_to_name: false + variants: + layers: + filter_declaration: |- + ctx.beginLayer({filter: + param}) + close_layer: | + ctx.endLayer(); + canvasFilterObject: + filter_declaration: |- + ctx.filter = new CanvasFilter( + param) + tentative: .tentative + +- name: >- + 2d.filter.{{ variant_names[0] }}.componentTransfer.linear{{ tentative }} desc: Test pixels on CanvasFilter() componentTransfer with linear type + size: [100, 100] + fuzzy: maxDifference=0-2; totalPixels=0-500 code: | + const slopes = [0.5, 1.2, -0.2]; + const intercepts = [0.25, 0, 0.5]; + {{ filter_declaration | replace("param", "{name: 'componentTransfer', + funcR: {type: 'linear', slope: slopes[0], intercept: intercepts[0]}, + funcG: {type: 'linear', slope: slopes[1], intercept: intercepts[1]}, + funcB: {type: 'linear', slope: slopes[2], intercept: intercepts[2]}, + }") }}; + + const inputColors = [ + [255, 255, 255], + [0, 0, 0], + [127, 0, 34], + [252, 186, 3], + [50, 68, 87], + ]; + + for (let i = 0 ; i < inputColors.length ; ++i) { + const color = inputColors[i]; + ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; + ctx.fillRect(i * 10, i * 10, 10, 10); + } + {{ close_layer }} + reference: | // From https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement function getColor(inputColor, slopes, intercepts) { return [ - Math.max(0, Math.min(1, inputColor[0]/255 * slopes[0] + intercepts[0])) * 255, - Math.max(0, Math.min(1, inputColor[1]/255 * slopes[1] + intercepts[1])) * 255, - Math.max(0, Math.min(1, inputColor[2]/255 * slopes[2] + intercepts[2])) * 255, + Math.max(0, Math.min(1, + inputColor[0]/255 * slopes[0] + intercepts[0])) * 255, + Math.max(0, Math.min(1, + inputColor[1]/255 * slopes[1] + intercepts[1])) * 255, + Math.max(0, Math.min(1, + inputColor[2]/255 * slopes[2] + intercepts[2])) * 255, ]; } const slopes = [0.5, 1.2, -0.2]; const intercepts = [0.25, 0, 0.5]; - ctx.filter = new CanvasFilter({name: 'componentTransfer', - funcR: {type: 'linear', slope: slopes[0], intercept: intercepts[0]}, - funcG: {type: 'linear', slope: slopes[1], intercept: intercepts[1]}, - funcB: {type: 'linear', slope: slopes[2], intercept: intercepts[2]}, - }); const inputColors = [ [255, 255, 255], @@ -195,21 +315,35 @@ [50, 68, 87], ]; - for (const color of inputColors) { + for (let i = 0 ; i < inputColors.length ; ++i) { + const color = inputColors[i]; let outputColor = getColor(color, slopes, intercepts); - ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; - ctx.fillRect(0, 0, 10, 10); - _assertPixelApprox(canvas, 5, 5, outputColor[0],outputColor[1],outputColor[2],255, 2); + ctx.fillStyle = `rgb(${outputColor[0]}, ${outputColor[1]}, + ${outputColor[2]})`; + ctx.fillRect(i * 10, i * 10, 10, 10); } - -- name: 2d.filter.canvasFilterObject.componentTransfer.identity.tentative + {{ close_layer }} + append_variants_to_name: false + variants: + layers: + filter_declaration: |- + ctx.beginLayer({filter: param}) + close_layer: ctx.endLayer(); + canvasFilterObject: + filter_declaration: |- + ctx.filter = new CanvasFilter(param) + tentative: .tentative + +- name: >- + 2d.filter.{{ variant_names[0] }}.componentTransfer.identity{{ tentative }} desc: Test pixels on CanvasFilter() componentTransfer with identity type + size: [100, 100] code: | - ctx.filter = new CanvasFilter({name: 'componentTransfer', + {{ filter_declaration | replace("param", "{name: 'componentTransfer', funcR: {type: 'identity'}, funcG: {type: 'identity'}, funcB: {type: 'identity'}, - }); + }") }}; const inputColors = [ [255, 255, 255], @@ -219,32 +353,86 @@ [50, 68, 87], ]; - for (const color of inputColors) { - ctx.fillStyle = `rgba(${color[0]}, ${color[1]}, ${color[2]}, 1)`, - ctx.fillRect(0, 0, 10, 10); - _assertPixel(canvas, 5, 5, color[0],color[1],color[2],255); + for (let i = 0 ; i < inputColors.length ; ++i) { + const color = inputColors[i]; + ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; + ctx.fillRect(i * 10, i * 10, 10, 10); } + {{ close_layer }} + reference: | + const inputColors = [ + [255, 255, 255], + [0, 0, 0], + [127, 0, 34], + [252, 186, 3], + [50, 68, 87], + ]; -- name: 2d.filter.canvasFilterObject.componentTransfer.gamma.tentative + for (let i = 0 ; i < inputColors.length ; ++i) { + let outputColor = inputColors[i]; + ctx.fillStyle = `rgb(${outputColor[0]}, ${outputColor[1]}, + ${outputColor[2]})`; + ctx.fillRect(i * 10, i * 10, 10, 10); + } + append_variants_to_name: false + variants: + layers: + filter_declaration: |- + ctx.beginLayer({filter: param}) + close_layer: ctx.endLayer(); + canvasFilterObject: + filter_declaration: |- + ctx.filter = new CanvasFilter(param) + tentative: .tentative + +- name: >- + 2d.filter.{{ variant_names[0] }}.componentTransfer.gamma{{ tentative }} desc: Test pixels on CanvasFilter() componentTransfer with gamma type + size: [100, 100] + fuzzy: maxDifference=0-2; totalPixels=0-500 code: | + const amplitudes = [2, 1.1, 0.5]; + const exponents = [5, 3, 1]; + const offsets = [0.25, 0, 0.5]; + {{ filter_declaration | replace("param", "{name: 'componentTransfer', + funcR: {type: 'gamma', amplitude: amplitudes[0], + exponent: exponents[0], offset: offsets[0]}, + funcG: {type: 'gamma', amplitude: amplitudes[1], + exponent: exponents[1], offset: offsets[1]}, + funcB: {type: 'gamma', amplitude: amplitudes[2], + exponent: exponents[2], offset: offsets[2]}, + }") }}; + + const inputColors = [ + [255, 255, 255], + [0, 0, 0], + [127, 0, 34], + [252, 186, 3], + [50, 68, 87], + ]; + + for (let i = 0 ; i < inputColors.length ; ++i) { + const color = inputColors[i]; + ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; + ctx.fillRect(i * 10, i * 10, 10, 10); + } + {{ close_layer }} + reference: | // From https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement function getColor(inputColor, amplitude, exponent, offset) { return [ - Math.max(0, Math.min(1, Math.pow(inputColor[0]/255, exponent[0]) * amplitude[0] + offset[0])) * 255, - Math.max(0, Math.min(1, Math.pow(inputColor[1]/255, exponent[1]) * amplitude[1] + offset[1])) * 255, - Math.max(0, Math.min(1, Math.pow(inputColor[2]/255, exponent[2]) * amplitude[2] + offset[2])) * 255, + Math.max(0, Math.min(1, Math.pow(inputColor[0]/255, + exponent[0]) * amplitude[0] + offset[0])) * 255, + Math.max(0, Math.min(1, Math.pow(inputColor[1]/255, + exponent[1]) * amplitude[1] + offset[1])) * 255, + Math.max(0, Math.min(1, Math.pow(inputColor[2]/255, + exponent[2]) * amplitude[2] + offset[2])) * 255, ]; } const amplitudes = [2, 1.1, 0.5]; const exponents = [5, 3, 1]; const offsets = [0.25, 0, 0.5]; - ctx.filter = new CanvasFilter({name: 'componentTransfer', - funcR: {type: 'gamma', amplitude: amplitudes[0], exponent: exponents[0], offset: offsets[0]}, - funcG: {type: 'gamma', amplitude: amplitudes[1], exponent: exponents[1], offset: offsets[1]}, - funcB: {type: 'gamma', amplitude: amplitudes[2], exponent: exponents[2], offset: offsets[2]}, - }); const inputColors = [ [255, 255, 255], @@ -254,16 +442,54 @@ [50, 68, 87], ]; - for (const color of inputColors) { + for (let i = 0 ; i < inputColors.length ; ++i) { + const color = inputColors[i]; let outputColor = getColor(color, amplitudes, exponents, offsets); - ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; - ctx.fillRect(0, 0, 10, 10); - _assertPixelApprox(canvas, 5, 5, outputColor[0],outputColor[1],outputColor[2],255, 2); + ctx.fillStyle = `rgb(${outputColor[0]}, ${outputColor[1]}, + ${outputColor[2]})`; + ctx.fillRect(i * 10, i * 10, 10, 10); } - -- name: 2d.filter.canvasFilterObject.componentTransfer.table.tentative + append_variants_to_name: false + variants: + layers: + filter_declaration: |- + ctx.beginLayer({filter: param}) + close_layer: ctx.endLayer(); + canvasFilterObject: + filter_declaration: |- + ctx.filter = new CanvasFilter(param) + tentative: .tentative + +- name: >- + 2d.filter.{{ variant_names[0] }}.componentTransfer.table{{ tentative }} desc: Test pixels on CanvasFilter() componentTransfer with table type + size: [100, 100] + fuzzy: maxDifference=0-2; totalPixels=0-500 code: | + tableValuesR = [0, 0, 1, 1]; + tableValuesG = [2, 0, 0.5, 3]; + tableValuesB = [1, -1, 5, 0]; + {{ filter_declaration | replace("param", "{name: 'componentTransfer', + funcR: {type: 'table', tableValues: tableValuesR}, + funcG: {type: 'table', tableValues: tableValuesG}, + funcB: {type: 'table', tableValues: tableValuesB}, + }") }}; + + const inputColors = [ + [255, 255, 255], + [0, 0, 0], + [127, 0, 34], + [252, 186, 3], + [50, 68, 87], + ]; + + for (let i = 0 ; i < inputColors.length ; ++i) { + const color = inputColors[i]; + ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; + ctx.fillRect(i * 10, i * 10, 10, 10); + } + {{ close_layer }} + reference: | // From https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement function getTransformedValue(C, V) { // Get the right interval @@ -285,11 +511,6 @@ tableValuesR = [0, 0, 1, 1]; tableValuesG = [2, 0, 0.5, 3]; tableValuesB = [1, -1, 5, 0]; - ctx.filter = new CanvasFilter({name: 'componentTransfer', - funcR: {type: 'table', tableValues: tableValuesR}, - funcG: {type: 'table', tableValues: tableValuesG}, - funcB: {type: 'table', tableValues: tableValuesB}, - }); const inputColors = [ [255, 255, 255], @@ -299,16 +520,55 @@ [50, 68, 87], ]; - for (const color of inputColors) { - let outputColor = getColor(color, [tableValuesR, tableValuesG, tableValuesB]); - ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; - ctx.fillRect(0, 0, 10, 10); - _assertPixelApprox(canvas, 5, 5, outputColor[0],outputColor[1],outputColor[2],255, 2); + for (let i = 0 ; i < inputColors.length ; ++i) { + const color = inputColors[i]; + let outputColor = getColor( + color, [tableValuesR, tableValuesG, tableValuesB]); + ctx.fillStyle = `rgb(${outputColor[0]}, ${outputColor[1]}, + ${outputColor[2]})`; + ctx.fillRect(i * 10, i * 10, 10, 10); } - -- name: 2d.filter.canvasFilterObject.componentTransfer.discrete.tentative + append_variants_to_name: false + variants: + layers: + filter_declaration: |- + ctx.beginLayer({filter: param}) + close_layer: ctx.endLayer(); + canvasFilterObject: + filter_declaration: |- + ctx.filter = new CanvasFilter(param) + tentative: .tentative + +- name: >- + 2d.filter.{{ variant_names[0] }}.componentTransfer.discrete{{ tentative }} desc: Test pixels on CanvasFilter() componentTransfer with discrete type + size: [100, 100] + fuzzy: maxDifference=0-2; totalPixels=0-500 code: | + tableValuesR = [0, 0, 1, 1]; + tableValuesG = [2, 0, 0.5, 3]; + tableValuesB = [1, -1, 5, 0]; + {{ filter_declaration | replace("param", "{name: 'componentTransfer', + funcR: {type: 'discrete', tableValues: tableValuesR}, + funcG: {type: 'discrete', tableValues: tableValuesG}, + funcB: {type: 'discrete', tableValues: tableValuesB}, + }") }}; + + const inputColors = [ + [255, 255, 255], + [0, 0, 0], + [127, 0, 34], + [252, 186, 3], + [50, 68, 87], + ]; + + for (let i = 0 ; i < inputColors.length ; ++i) { + const color = inputColors[i]; + ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; + ctx.fillRect(i * 10, i * 10, 10, 10); + } + {{ close_layer }} + reference: | // From https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement function getTransformedValue(C, V) { // Get the right interval @@ -330,11 +590,6 @@ tableValuesR = [0, 0, 1, 1]; tableValuesG = [2, 0, 0.5, 3]; tableValuesB = [1, -1, 5, 0]; - ctx.filter = new CanvasFilter({name: 'componentTransfer', - funcR: {type: 'discrete', tableValues: tableValuesR}, - funcG: {type: 'discrete', tableValues: tableValuesG}, - funcB: {type: 'discrete', tableValues: tableValuesB}, - }); const inputColors = [ [255, 255, 255], @@ -343,24 +598,38 @@ [252, 186, 3], [50, 68, 87], ]; - - for (const color of inputColors) { - let outputColor = getColor(color, [tableValuesR, tableValuesG, tableValuesB]); - ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; - ctx.fillRect(0, 0, 10, 10); - _assertPixelApprox(canvas, 5, 5, outputColor[0],outputColor[1],outputColor[2],255, 2); + for (let i = 0 ; i < inputColors.length ; ++i) { + const color = inputColors[i]; + let outputColor = getColor( + color, [tableValuesR, tableValuesG, tableValuesB]); + ctx.fillStyle = `rgb(${outputColor[0]}, ${outputColor[1]}, + ${outputColor[2]})`; + ctx.fillRect(i * 10, i * 10, 10, 10); } - -- name: 2d.filter.canvasFilterObject.gaussianBlur.tentative + append_variants_to_name: false + variants: + layers: + filter_declaration: |- + ctx.beginLayer({filter: param}) + close_layer: ctx.endLayer(); + canvasFilterObject: + filter_declaration: |- + ctx.filter = new CanvasFilter(param) + tentative: .tentative + +- name: >- + 2d.filter.{{ variant_names[0] }}.gaussianBlur.{{ variant_names[1] }}{{ + tentative }} desc: Test CanvasFilter() with gaussianBlur. size: [100, 100] code: | ctx.fillStyle = 'teal'; - ctx.filter = new CanvasFilter({ + {{ filter_declaration | replace("param", "{ name: 'gaussianBlur', stdDeviation: [{{ blur_x }}, {{blur_y}}], - }); + }") }} ctx.fillRect(25, 25, 50, 50); + {{ close_layer }} html_reference: | <svg xmlns="http://www.w3.org/2000/svg" width="{{ size[0] }}" height="{{ size[1] }}" @@ -371,24 +640,33 @@ <rect x="25" y="25" width="50" height="50" fill="teal" filter="url(#blur)" /> </svg> - variants: - x-only: - blur_x: 4 - blur_y: 0 - mostly-x: - blur_x: 4 - blur_y: 1 - isotropic: - blur_x: 4 - blur_y: 4 - mostly-y: - blur_x: 1 - blur_y: 4 - y-only: - blur_x: 0 - blur_y: 4 - -- name: 2d.filter.canvasFilterObject.dropShadow.tentative + append_variants_to_name: false + variant_matrix: + - layers: + filter_declaration: |- + ctx.beginLayer({filter: param}); + close_layer: ctx.endLayer(); + canvasFilterObject: + filter_declaration: |- + ctx.filter = new CanvasFilter(param); + tentative: .tentative + - x-only: + blur_x: 4 + blur_y: 0 + mostly-x: + blur_x: 4 + blur_y: 1 + isotropic: + blur_x: 4 + blur_y: 4 + mostly-y: + blur_x: 1 + blur_y: 4 + y-only: + blur_x: 0 + blur_y: 4 + +- name: 2d.filter.{{ variant_names[0] }}.dropShadow{{ tentative }} desc: Test CanvasFilter() dropShadow object. size: [520, 420] code: | @@ -401,90 +679,91 @@ ctx.fillStyle = 'crimson'; // Parameter defaults. - ctx.filter = new CanvasFilter({name: 'dropShadow'}); + {{ filter_declaration | replace("param", "{name: 'dropShadow'}") }} ctx.fillRect(10, 10, 80, 80); + {{ close_layer -}} // All parameters specified. - ctx.filter = new CanvasFilter( - {name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 5, - floodColor: 'purple', floodOpacity: 0.7}); + {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 5, + floodColor: 'purple', floodOpacity: 0.7}") }} ctx.fillRect(110, 10, 80, 80); + {{ close_layer -}} // Named color. - ctx.filter = new CanvasFilter( - {name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, - floodColor: 'purple'}); + {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, + floodColor: 'purple'}") }} ctx.fillRect(10, 110, 80, 80); + {{ close_layer -}} // System color. - ctx.filter = new CanvasFilter( - {name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, - floodColor: 'LinkText'}); + {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, + floodColor: 'LinkText'}") }} ctx.fillRect(110, 110, 80, 80); + {{ close_layer -}} // Numerical color. - ctx.filter = new CanvasFilter( - {name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, - floodColor: 'rgba(20, 50, 130, 1)'}); + {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, + floodColor: 'rgba(20, 50, 130, 1)'}") }} ctx.fillRect(210, 110, 80, 80); + {{ close_layer -}} // Transparent floodColor. - ctx.filter = new CanvasFilter( - {name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, - floodColor: 'rgba(20, 50, 130, 0.7)'}); + {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, + floodColor: 'rgba(20, 50, 130, 0.7)'}") }} ctx.fillRect(310, 110, 80, 80); + {{ close_layer -}} // Transparent floodColor and floodOpacity. - ctx.filter = new CanvasFilter( - {name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, - floodColor: 'rgba(20, 50, 130, 0.7)', floodOpacity: 0.7}); + {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, + floodColor: 'rgba(20, 50, 130, 0.7)', floodOpacity: 0.7}") }} ctx.fillRect(410, 110, 80, 80); + {{ close_layer -}} // No blur. - ctx.filter = new CanvasFilter( - {name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 0, - floodColor: 'purple'}); + {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 0, + floodColor: 'purple'}") }} ctx.fillRect(10, 210, 80, 80); + {{ close_layer -}} // Single float blur. - ctx.filter = new CanvasFilter( - {name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 5, - floodColor: 'purple'}); + {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 5, + floodColor: 'purple'}") }} ctx.fillRect(110, 210, 80, 80); + {{ close_layer -}} // Single negative float blur. - ctx.filter = new CanvasFilter( - {name: 'dropShadow', dx: 9, dy: 12, stdDeviation: -5, - floodColor: 'purple'}); + {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: 9, dy: 12, stdDeviation: -5, + floodColor: 'purple'}") }} ctx.fillRect(210, 210, 80, 80); + {{ close_layer -}} // Two floats (X&Y) blur. - ctx.filter = new CanvasFilter( - {name: 'dropShadow', dx: 9, dy: 12, stdDeviation: [3, 5], - floodColor: 'purple'}); + {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: 9, dy: 12, stdDeviation: [3, 5], + floodColor: 'purple'}") }} ctx.fillRect(310, 210, 80, 80); + {{ close_layer -}} // Two negative floats (X&Y) blur. - ctx.filter = new CanvasFilter( - {name: 'dropShadow', dx: 9, dy: 12, stdDeviation: [-3, -5], - floodColor: 'purple'}); + {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: 9, dy: 12, stdDeviation: [-3, -5], + floodColor: 'purple'}") }} ctx.fillRect(410, 210, 80, 80); + {{ close_layer -}} // Degenerate parameter values. - ctx.filter = new CanvasFilter( - {name: 'dropShadow', dx: [-5], dy: [], stdDeviation: null, - floodColor: 'purple', floodOpacity: [2]}); + {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: [-5], dy: [], stdDeviation: null, + floodColor: 'purple', floodOpacity: [2]}") }} ctx.fillRect(10, 310, 80, 80); + {{ close_layer -}} - ctx.filter = new CanvasFilter( - {name: 'dropShadow', dx: null, dy: '5', stdDeviation: [[-5], ['3']], - floodColor: 'purple', floodOpacity: '0.8'}); + {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: null, dy: '5', stdDeviation: [[-5], ['3']], + floodColor: 'purple', floodOpacity: '0.8'}") }} ctx.fillRect(110, 310, 80, 80); + {{ close_layer -}} - ctx.filter = new CanvasFilter( - {name: 'dropShadow', dx: true, dy: ['10'], stdDeviation: false, - floodColor: 'purple', floodOpacity: ['0.4']}); + {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: true, dy: ['10'], stdDeviation: false, + floodColor: 'purple', floodOpacity: ['0.4']}") }} ctx.fillRect(210, 310, 80, 80); + {{ close_layer -}} html_reference: | <svg xmlns="http://www.w3.org/2000/svg" width={{ size[0] }} height={{ size[1] }} @@ -537,41 +816,70 @@ <rect x=210 y=310 width=80 height=80 fill="crimson" filter="drop-shadow(1px 10px 0px rgba(128, 0, 128, 0.4))"/> </svg> - -- name: 2d.filter.canvasFilterObject.dropShadow.exceptions.tentative + append_variants_to_name: false + variants: + layers: + filter_declaration: |- + ctx.beginLayer({filter: param}); + close_layer: | + ctx.endLayer(); + canvasFilterObject: + filter_declaration: |- + ctx.filter = new CanvasFilter(param); + tentative: .tentative + +- name: 2d.filter.{{ variant_names[0] }}.dropShadow.exceptions{{ tentative }} desc: Test exceptions on CanvasFilter() dropShadow object code: | - @unroll @assert new CanvasFilter({\- + // Should not throw an error. + @unroll {{ filter_declaration | replace("param", "{\- name: 'dropShadow', \- <dx | dy | floodOpacity>: \- - <10 | -1 | 0.5 | null | true | false | [] | [20] | '30'>}); - @unroll @assert new CanvasFilter({\- + <10 | -1 | 0.5 | null | true | false | [] | [20] | '30'>}") }}; + @unroll {{ filter_declaration | replace("param", "{\- + name: 'dropShadow', \- + <dx | dy | floodOpacity>: \- + <10 | -1 | 0.5 | null | true | false | [] | [20] | '30'>}") }}; + @unroll {{ filter_declaration | replace("param", "{\- name: 'dropShadow', \- <stdDeviation>: \- <10 | -1 | 0.5 | null | true | false | [] | [20] | '30' | \- [10, -1] | [0.5, null] | [true, false] | [[], [20]] | \- - ['30', ['40']]>}); - @unroll @assert new CanvasFilter({\- + ['30', ['40']]>}") }}; + @unroll {{ filter_declaration | replace("param", "{\- name: 'dropShadow', \- <floodColor>: \- - <'red' | 'canvas' | 'rgba(4, -3, 0.5, 1)' | '#aabbccdd' | '#abcd'>}); + <'red' | 'canvas' | 'rgba(4, -3, 0.5, 1)' | '#aabbccdd' | + '#abcd'>}") }}; - @unroll @assert throws TypeError new CanvasFilter({\- - name: 'dropShadow', \- + // Should throw a TypeError. + @unroll @assert throws TypeError {{ filter_declaration | replace("param", \- + "{name: 'dropShadow', \- <dx | dy | floodOpacity>: \- - <NaN | Infinity | -Infinity | undefined | 'test' | {} | [1, 2]>}); - @unroll @assert throws TypeError new CanvasFilter({\- - name: 'dropShadow', \- + <NaN | Infinity | -Infinity | undefined | 'test' | {} | [1, 2]>}") }}; + @unroll @assert throws TypeError {{ filter_declaration | replace("param", \- + "{name: 'dropShadow', \- <stdDeviation>: \- <NaN | Infinity | -Infinity | undefined | 'test' | {} | [1, 2, 3] | \- [1, NaN] | [1, Infinity] | [1, -Infinity] | [1, undefined] | \- - [1, 'test'] | [1, {}] | [1, [2, 3]]>}); - @unroll @assert throws TypeError new CanvasFilter({\- - name: 'dropShadow', \- + [1, 'test'] | [1, {}] | [1, [2, 3]]>}") }}; + @unroll @assert throws TypeError {{ filter_declaration | replace("param", \- + "{name: 'dropShadow', \- <floodColor>: \- - <'test' | 'rgba(NaN, 3, 2, 1)' | 10 | undefined | null | NaN>}); - -- name: 2d.filter.canvasFilterObject.turbulence.inputTypes.tentative + <'test' | 'rgba(NaN, 3, 2, 1)' | 10 | undefined | null | NaN>}") }}; + append_variants_to_name: false + variants: + layers: + filter_declaration: |- + ctx.beginLayer({filter: + param}); ctx.endLayer() + canvasFilterObject: + filter_declaration: |- + ctx.filter = new CanvasFilter( + param) + tentative: .tentative + +- name: 2d.filter.{{ variant_names[0] }}.turbulence.inputTypes{{ tentative }} desc: Test exceptions on CanvasFilter() turbulence object code: | const errorTestCases = [ @@ -671,10 +979,22 @@ for (testCase of errorTestCases) { const filterOptions = {...{name: 'turbulence'}, ...testCase}; - @assert throws TypeError new CanvasFilter(filterOptions); + @assert throws TypeError {{ filter_declaration | + replace("param", "filterOptions") }}; } for (testCase of workingTestCases) { const filterOptions = {...{name: 'turbulence'}, ...testCase}; - @assert new CanvasFilter(filterOptions) != null; + {{ filter_declaration | replace("param", "filterOptions") }}; + {{- close_layer }} } + append_variants_to_name: false + variants: + layers: + filter_declaration: |- + ctx.beginLayer({filter: param}) + close_layer: "\n ctx.endLayer();" + canvasFilterObject: + filter_declaration: |- + ctx.filter = new CanvasFilter(param) + tentative: .tentative diff --git a/testing/web-platform/tests/html/canvas/tools/yaml-new/layers.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/layers.yaml index a44cb2ea2c..437a70c3f7 100644 --- a/testing/web-platform/tests/html/canvas/tools/yaml-new/layers.yaml +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/layers.yaml @@ -44,7 +44,7 @@ variants: &global-state-variants no-global-states: render_states: // No global states. - alpha: + alpha: &global-state-alpha render_states: ctx.globalAlpha = 0.6; blending: render_states: ctx.globalCompositeOperation = 'multiply'; @@ -56,15 +56,15 @@ ctx.shadowOffsetY = 10; ctx.shadowColor = 'rgba(255, 165, 0, 0.5)'; ctx.shadowBlur = 3; - alpha.blending: + alpha.blending: &global-state-alpha-blending render_states: |- ctx.globalAlpha = 0.6; ctx.globalCompositeOperation = 'multiply'; - alpha.composite: + alpha.composite: &global-state-alpha-composite render_states: |- ctx.globalAlpha = 0.6; ctx.globalCompositeOperation = 'source-in'; - alpha.shadow: + alpha.shadow: &global-state-alpha-shadow render_states: |- ctx.globalAlpha = 0.5; ctx.shadowOffsetX = -10; @@ -167,7 +167,20 @@ ctx.drawImage(img, 0, 0); }; img.src = 'data:image/svg+xml;base64,' + btoa(svg); - variants: *global-state-variants + variants: + <<: *global-state-variants + alpha: + <<: *global-state-alpha + fuzzy: maxDifference=0-2; totalPixels=0-6766 + alpha.blending: + <<: *global-state-alpha-blending + fuzzy: maxDifference=0-1; totalPixels=0-2453 + alpha.composite: + <<: *global-state-alpha-composite + fuzzy: maxDifference=0-1; totalPixels=0-5204 + alpha.shadow: + <<: *global-state-alpha-shadow + fuzzy: maxDifference=0-2; totalPixels=0-6311 - name: 2d.layer.global-filter desc: Tests that layers ignore the global context filter. @@ -334,6 +347,144 @@ ctx.drawImage(canvas2, 0, 0); +- name: 2d.layer.ctm.filter + desc: Checks that parent transforms affect layer filters. + size: [200, 200] + code: | + // Transforms inside the layer should not apply to the layer's filter. + ctx.beginLayer({filter: 'drop-shadow(5px 5px 0px grey)'}); + ctx.translate(30, 90); + ctx.scale(2, 2); + ctx.rotate(Math.PI / 2); + ctx.fillRect(-30, -5, 60, 10); + ctx.endLayer(); + + // Transforms in the layer's parent should apply to the layer's filter. + ctx.translate(80, 90); + ctx.scale(2, 2); + ctx.rotate(Math.PI / 2); + ctx.beginLayer({filter: 'drop-shadow(5px 5px 0px grey)'}); + ctx.fillRect(-30, -5, 60, 10); + ctx.endLayer(); + html_reference: | + <svg xmlns="http://www.w3.org/2000/svg" + width="{{ size[0] }}" height="{{ size[1] }}" + color-interpolation-filters="sRGB"> + <filter id="filter" x="-100%" y="-100%" width="300%" height="300%"> + <feDropShadow dx="5" dy="5" stdDeviation="0" flood-color="grey" /> + </filter> + + <g filter="url(#filter)"> + <g transform="translate(30, 90) scale(2) rotate(90)"> + <rect x="-30" y="-5" width=60 height=10></rect> + </g> + </g> + + <g transform="translate(80, 90) scale(2) rotate(90)"> + <g filter="url(#filter)"> + <rect x="-30" y="-5" width=60 height=10></rect> + </g> + </g> + </svg> + +- name: 2d.layer.ctm.shadow-in-transformed-layer + desc: Check shadows inside of a transformed layer. + size: [200, 200] + code: | + ctx.translate(80, 90); + ctx.scale(2, 2); + ctx.rotate(Math.PI / 2); + + ctx.beginLayer(); + ctx.shadowOffsetX = 10; + ctx.shadowOffsetY = 10; + ctx.shadowColor = 'grey'; + ctx.fillRect(-30, -5, 60, 10); + + const canvas2 = new OffscreenCanvas(100, 100); + const ctx2 = canvas2.getContext('2d'); + ctx2.fillStyle = 'blue'; + ctx2.fillRect(0, 0, 40, 10); + ctx.drawImage(canvas2, -30, -30); + + ctx.endLayer(); + reference: | + ctx.translate(80, 90); + ctx.scale(2, 2); + ctx.rotate(Math.PI / 2); + + ctx.shadowOffsetX = 10; + ctx.shadowOffsetY = 10; + ctx.shadowColor = 'grey'; + ctx.fillRect(-30, -5, 60, 10); + + const canvas2 = new OffscreenCanvas(100, 100); + const ctx2 = canvas2.getContext('2d'); + ctx2.fillStyle = 'blue'; + ctx2.fillRect(0, 0, 40, 10); + ctx.drawImage(canvas2, -30, -30); + +- name: 2d.layer.ctm.getTransform + desc: Tests getTransform inside layers. + code: | + ctx.translate(10, 20); + ctx.beginLayer(); + ctx.scale(2, 3); + const m = ctx.getTransform(); + assert_array_equals([m.a, m.b, m.c, m.d, m.e, m.f], [2, 0, 0, 3, 10, 20]); + ctx.endLayer(); + +- name: 2d.layer.ctm.setTransform + desc: Tests setTransform inside layers. + code: | + ctx.translate(80, 0); + + ctx.beginLayer(); + ctx.rotate(2); + ctx.beginLayer(); + ctx.scale(5, 6); + ctx.setTransform(4, 0, 0, 2, 20, 10); + ctx.fillStyle = 'blue'; + ctx.fillRect(0, 0, 10, 10); + ctx.endLayer(); + ctx.endLayer(); + + ctx.fillStyle = 'green'; + ctx.fillRect(0, 0, 20, 20); + reference: | + ctx.translate(80, 0); + ctx.fillStyle = 'green'; + ctx.fillRect(0, 0, 20, 20); + + ctx.setTransform(4, 0, 0, 2, 20, 10); + ctx.fillStyle = 'blue'; + ctx.fillRect(0, 0, 10, 10); + +- name: 2d.layer.ctm.resetTransform + desc: Tests resetTransform inside layers. + code: | + ctx.translate(40, 0); + + ctx.beginLayer(); + ctx.rotate(2); + ctx.beginLayer(); + ctx.scale(5, 6); + ctx.resetTransform(); + ctx.fillStyle = 'blue'; + ctx.fillRect(0, 0, 20, 20); + ctx.endLayer(); + ctx.endLayer(); + + ctx.fillStyle = 'green'; + ctx.fillRect(0, 0, 20, 20); + reference: | + ctx.fillStyle = 'blue'; + ctx.fillRect(0, 0, 20, 20); + + ctx.translate(40, 0); + ctx.fillStyle = 'green'; + ctx.fillRect(0, 0, 20, 20); + - name: 2d.layer.clip-inside desc: Check clipping set inside the layer size: [100, 100] @@ -627,6 +778,49 @@ ctx.fillStyle = 'blue'; ctx.fillRect(10, 10, 80, 50); +- name: 2d.layer.drawImage + size: [200, 200] + desc: >- + Checks that drawImage writes the image to the layer and not the parent + directly. + code: | + ctx.fillStyle = 'skyblue'; + ctx.fillRect(0, 0, 100, 100); + + ctx.beginLayer({filter: {name: 'dropShadow', dx: -10, dy: -10, + stdDeviation: 0, floodColor: 'navy'}}); + + ctx.fillStyle = 'maroon'; + ctx.fillRect(20, 20, 50, 50); + + ctx.globalCompositeOperation = 'xor'; + + // The image should xor only with the layer content, not the parents'. + const canvas_image = new OffscreenCanvas(200,200); + const ctx_image = canvas_image.getContext("2d"); + ctx_image.fillStyle = 'pink'; + ctx_image.fillRect(40, 40, 50, 50); + ctx.drawImage(canvas_image, 0, 0); + + ctx.endLayer(); + reference: | + ctx.fillStyle = 'skyblue'; + ctx.fillRect(0, 0, 100, 100); + + ctx.beginLayer({filter: {name: 'dropShadow', dx: -10, dy: -10, + stdDeviation: 0, floodColor: 'navy'}}); + + ctx.fillStyle = 'maroon'; + ctx.fillRect(20, 20, 50, 50); + + ctx.globalCompositeOperation = 'xor'; + + // Should xor only with the layer content, not the parents'. + ctx.fillStyle = 'pink'; + ctx.fillRect(40, 40, 50, 50); + + ctx.endLayer(); + - name: 2d.layer.valid-calls desc: No exception raised on {{ variant_desc }}. variants: |