- name: 2d.filter.value desc: test if ctx.filter works correctly code: | @assert ctx.filter == 'none'; ctx.filter = 'blur(5px)'; @assert ctx.filter == 'blur(5px)'; ctx.save(); ctx.filter = 'none'; @assert ctx.filter == 'none'; ctx.restore(); @assert ctx.filter == 'blur(5px)'; ctx.filter = 'blur(10)'; @assert ctx.filter == 'blur(5px)'; ctx.filter = 'blur 10px'; @assert ctx.filter == 'blur(5px)'; ctx.filter = 'inherit'; @assert ctx.filter == 'blur(5px)'; ctx.filter = 'initial'; @assert ctx.filter == 'blur(5px)'; ctx.filter = 'unset'; @assert ctx.filter == 'blur(5px)'; ctx.filter = ''; @assert ctx.filter == 'blur(5px)'; ctx.filter = null; @assert ctx.filter == 'blur(5px)'; ctx.filter = undefined; @assert ctx.filter == 'blur(5px)'; ctx.filter = 'blur( 5px)'; assert_equals(ctx.filter, 'blur( 5px)'); - name: 2d.filter.canvasFilterObject.tentative desc: Test CanvasFilter() object canvas_types: ['HtmlCanvas'] code: | @assert ctx.filter == 'none'; ctx.filter = 'blur(5px)'; @assert ctx.filter == 'blur(5px)'; ctx.filter = new CanvasFilter({name: 'gaussianBlur', stdDeviation: 5}); @assert ctx.filter.toString() == '[object CanvasFilter]'; ctx.filter = new CanvasFilter({name: 'gaussianBlur', stdDeviation: [1, 2]}); @assert ctx.filter.toString() == '[object CanvasFilter]'; ctx.filter = new CanvasFilter([ {name: 'gaussianBlur', stdDeviation: 5}, {name: 'gaussianBlur', stdDeviation: 10} ]); @assert ctx.filter.toString() == '[object CanvasFilter]'; var canvas2 = document.createElement('canvas'); var ctx2 = canvas2.getContext('2d'); ctx2.filter = ctx.filter; @assert ctx.filter.toString() == '[object CanvasFilter]'; ctx.filter = 'blur(5px)'; @assert ctx.filter == 'blur(5px)'; ctx.filter = 'none'; @assert ctx.filter == 'none'; ctx.filter = new CanvasFilter({name: 'gaussianBlur', stdDeviation: 5}); ctx.filter = 'this string is not a filter and should do nothing'; @assert ctx.filter.toString() == '[object CanvasFilter]'; - name: 2d.filter.canvasFilterObject.tentative desc: Test CanvasFilter() object canvas_types: ['OffscreenCanvas', 'Worker'] code: | @assert ctx.filter == 'none'; ctx.filter = 'blur(5px)'; @assert ctx.filter == 'blur(5px)'; ctx.filter = new CanvasFilter({name: 'gaussianBlur', stdDeviation: 5}); @assert ctx.filter.toString() == '[object CanvasFilter]'; ctx.filter = new CanvasFilter({name: 'gaussianBlur', stdDeviation: [1, 2]}); @assert ctx.filter.toString() == '[object CanvasFilter]'; ctx.filter = new CanvasFilter([ {name: 'gaussianBlur', stdDeviation: 5}, {name: 'gaussianBlur', stdDeviation: 10} ]); @assert ctx.filter.toString() == '[object CanvasFilter]'; var canvas2 = new OffscreenCanvas(100, 50); var ctx2 = canvas2.getContext('2d'); ctx2.filter = ctx.filter; @assert ctx.filter.toString() == '[object CanvasFilter]'; ctx.filter = 'blur(5px)'; @assert ctx.filter == 'blur(5px)'; ctx.filter = 'none'; @assert ctx.filter == 'none'; ctx.filter = new CanvasFilter({name: 'gaussianBlur', stdDeviation: 5}); ctx.filter = 'this string is not a filter and should do nothing'; @assert ctx.filter.toString() == '[object CanvasFilter]'; - name: 2d.filter.{{ variant_names[0] }}.blur.exceptions{{ tentative }} desc: Test exceptions on gaussianBlur filter code: | @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 {{ 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'; {{ 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; {{ 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; {{ 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; {{ 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; {{ 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); {{ filter_declaration | replace("param", "{name: 'colorMatrix', type: 'luminanceToAlpha'}") }}; ctx.fillRect(0, 0, 100, 50); {{ close_layer -}} @assert pixel 10,10 ==~ 0,0,0,54; {{ 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); ctx.fillStyle = '#00f'; 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; 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 {{ 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 {{ 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, ]; } const slopes = [0.5, 1.2, -0.2]; const intercepts = [0.25, 0, 0.5]; 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]; let outputColor = getColor(color, slopes, intercepts); ctx.fillStyle = `rgb(${outputColor[0]}, ${outputColor[1]}, ${outputColor[2]})`; ctx.fillRect(i * 10, i * 10, 10, 10); } {{ 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: | {{ filter_declaration | replace("param", "{name: 'componentTransfer', funcR: {type: 'identity'}, funcG: {type: 'identity'}, funcB: {type: 'identity'}, }") }}; 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: | 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) { 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, ]; } const amplitudes = [2, 1.1, 0.5]; const exponents = [5, 3, 1]; const offsets = [0.25, 0, 0.5]; 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]; let outputColor = getColor(color, amplitudes, exponents, offsets); 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.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 const n = V.length - 1; const k = C == 1 ? n - 1 : Math.floor(C * n); return V[k] + (C - k/n) * n * (V[k + 1] - V[k]); } function getColor(inputColor, tableValues) { const result = [0, 0, 0]; for (const i in inputColor) { const C = inputColor[i]/255; const Cprime = getTransformedValue(C, tableValues[i]); result[i] = Math.max(0, Math.min(1, Cprime)) * 255; } return result; } tableValuesR = [0, 0, 1, 1]; tableValuesG = [2, 0, 0.5, 3]; tableValuesB = [1, -1, 5, 0]; 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]; let outputColor = getColor( color, [tableValuesR, tableValuesG, tableValuesB]); 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.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 const n = V.length; const k = C == 1 ? n - 1 : Math.floor(C * n); return V[k]; } function getColor(inputColor, tableValues) { const result = [0, 0, 0]; for (const i in inputColor) { const C = inputColor[i]/255; const Cprime = getTransformedValue(C, tableValues[i]); result[i] = Math.max(0, Math.min(1, Cprime)) * 255; } return result; } tableValuesR = [0, 0, 1, 1]; tableValuesG = [2, 0, 0.5, 3]; tableValuesB = [1, -1, 5, 0]; 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]; let outputColor = getColor( color, [tableValuesR, tableValuesG, tableValuesB]); 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] }}.gaussianBlur.{{ variant_names[1] }}{{ tentative }} desc: Test CanvasFilter() with gaussianBlur. size: [100, 100] code: | ctx.fillStyle = 'teal'; {{ filter_declaration | replace("param", "{ name: 'gaussianBlur', stdDeviation: [{{ blur_x }}, {{blur_y}}], }") }} ctx.fillRect(25, 25, 50, 50); {{ close_layer }} html_reference: | 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 - 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: | ctx.fillStyle = 'teal'; ctx.fillRect(0, 0, {{ size[0] }}, 50); ctx.fillRect(0, 100, {{ size[0] }}, 50); ctx.fillRect(0, 200, {{ size[0] }}, 50); ctx.fillRect(0, 300, {{ size[0] }}, 50); ctx.fillStyle = 'crimson'; // Parameter defaults. {{ filter_declaration | replace("param", "{name: 'dropShadow'}") }} ctx.fillRect(10, 10, 80, 80); {{ close_layer -}} // All parameters specified. {{ 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. {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, floodColor: 'purple'}") }} ctx.fillRect(10, 110, 80, 80); {{ close_layer -}} // System color. {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, floodColor: 'LinkText'}") }} ctx.fillRect(110, 110, 80, 80); {{ close_layer -}} // Numerical color. {{ 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. {{ 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. {{ 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. {{ 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. {{ 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. {{ 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. {{ 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. {{ 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. {{ filter_declaration | replace("param", "{name: 'dropShadow', dx: [-5], dy: [], stdDeviation: null, floodColor: 'purple', floodOpacity: [2]}") }} ctx.fillRect(10, 310, 80, 80); {{ close_layer -}} {{ 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 -}} {{ 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: | 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: | // Should not throw an error. @unroll {{ filter_declaration | replace("param", "{\- name: 'dropShadow', \- : \- <10 | -1 | 0.5 | null | true | false | [] | [20] | '30'>}") }}; @unroll {{ filter_declaration | replace("param", "{\- name: 'dropShadow', \- : \- <10 | -1 | 0.5 | null | true | false | [] | [20] | '30' | \- [10, -1] | [0.5, null] | [true, false] | [[], [20]] | \- ['30', ['40']]>}") }}; @unroll {{ filter_declaration | replace("param", "{\- name: 'dropShadow', \- : \- <'red' | 'canvas' | 'rgba(4, -3, 0.5, 1)' | '#aabbccdd' | '#abcd'>}") }}; // Should throw a TypeError. @unroll @assert throws TypeError {{ filter_declaration | replace("param", \- "{name: 'dropShadow', \- : \- }") }}; @unroll @assert throws TypeError {{ filter_declaration | replace("param", \- "{name: 'dropShadow', \- : \- }") }}; @unroll @assert throws TypeError {{ filter_declaration | replace("param", \- "{name: 'dropShadow', \- : \- <'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 = [ {baseFrequency: {}}, {baseFrequency: -1}, {baseFrequency: [0, -1]}, {baseFrequency: NaN}, {baseFrequency: Infinity}, {baseFrequency: undefined}, {baseFrequency: -Infinity}, {baseFrequency: 'test'}, {numOctaves: {}}, {numOctaves: -1}, {numOctaves: NaN}, {numOctaves: Infinity}, {numOctaves: undefined}, {numOctaves: -Infinity}, {numOctaves: [1, 1]}, {numOctaves: 'test'}, {seed: {}}, {seed: NaN}, {seed: Infinity}, {seed: undefined}, {seed: -Infinity}, {seed: [1, 1]}, {seed: 'test'}, {stitchTiles: {}}, {stitchTiles: NaN}, {stitchTiles: Infinity}, {stitchTiles: undefined}, {stitchTiles: -Infinity}, {stitchTiles: [1, 1]}, {stitchTiles: 'test'}, {stitchTiles: null}, {stitchTiles: []}, {stitchTiles: [10]}, {stitchTiles: 30}, {stitchTiles: false}, {stitchTiles: true}, {stitchTiles: '10'}, {stitchTiles: -1}, {type: {}}, {type: NaN}, {type: Infinity}, {type: undefined}, {type: -Infinity}, {type: [1, 1]}, {type: 'test'}, {type: null}, {type: []}, {type: [10]}, {type: 30}, {type: false}, {type: true}, {type: '10'}, {type: -1}, ] // null and [] = 0 when parsed as number const workingTestCases = [ {baseFrequency: null}, {baseFrequency: []}, {baseFrequency: [10]}, {baseFrequency: [10, 3]}, {baseFrequency: 30}, {baseFrequency: false}, {baseFrequency: true}, {baseFrequency: '10'}, {numOctaves: null}, {numOctaves: []}, {numOctaves: [10]}, {numOctaves: 30}, {numOctaves: false}, {numOctaves: true}, {numOctaves: '10'}, {seed: null}, {seed: []}, {seed: [10]}, {seed: 30}, {seed: false}, {seed: true}, {seed: '10'}, {seed: -1}, {stitchTiles: 'stitch'}, {stitchTiles: 'noStitch'}, {type: 'fractalNoise'}, {type: 'turbulence'}, ] for (testCase of errorTestCases) { const filterOptions = {...{name: 'turbulence'}, ...testCase}; @assert throws TypeError {{ filter_declaration | replace("param", "filterOptions") }}; } for (testCase of workingTestCases) { const filterOptions = {...{name: 'turbulence'}, ...testCase}; {{ 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