UNPKG

@svgdotjs/svg.filter.js

Version:

A plugin for svg.js adding filter functionality

394 lines (368 loc) 12.5 kB
/*! * @svgdotjs/svg.filter.js - A plugin for svg.js adding filter functionality * @version 3.0.9 * https://github.com/svgdotjs/svg.filter.js * * @copyright Wout Fierens * @license MIT * * BUILT: Mon Feb 24 2025 17:15:33 GMT+0100 (Central European Standard Time) */; 'use strict'; var svg_js = require('@svgdotjs/svg.js'); class Filter extends svg_js.Element { constructor(node) { super(svg_js.nodeOrNew('filter', node), node); this.$source = 'SourceGraphic'; this.$sourceAlpha = 'SourceAlpha'; this.$background = 'BackgroundImage'; this.$backgroundAlpha = 'BackgroundAlpha'; this.$fill = 'FillPaint'; this.$stroke = 'StrokePaint'; this.$autoSetIn = true; } put(element, i) { element = super.put(element, i); if (!element.attr('in') && this.$autoSetIn) { element.attr('in', this.$source); } if (!element.attr('result')) { element.attr('result', element.id()); } return element; } // Unmask all masked elements and remove itself remove() { // unmask all targets this.targets().each('unfilter'); // remove mask from parent return super.remove(); } targets() { return svg_js.find('svg [filter*="' + this.id() + '"]'); } toString() { return 'url(#' + this.id() + ')'; } } // Create Effect class class Effect extends svg_js.Element { constructor(node, attr) { super(node, attr); this.result(this.id()); } in(effect) { // Act as getter if (effect == null) { const _in = this.attr('in'); const ref = this.parent() && this.parent().find(`[result="${_in}"]`)[0]; return ref || _in; } // Avr as setter return this.attr('in', effect); } // Named result result(result) { return this.attr('result', result); } // Stringification toString() { return this.result(); } } // This function takes an array with attr keys and sets for every key the // attribute to the value of one paramater // getAttrSetter(['a', 'b']) becomes this.attr({a: param1, b: param2}) const getAttrSetter = params => { return function (...args) { for (let i = params.length; i--;) { if (args[i] != null) { this.attr(params[i], args[i]); } } }; }; const updateFunctions = { blend: getAttrSetter(['in', 'in2', 'mode']), // ColorMatrix effect colorMatrix: getAttrSetter(['type', 'values']), // Composite effect composite: getAttrSetter(['in', 'in2', 'operator']), // ConvolveMatrix effect convolveMatrix: function (matrix) { matrix = new svg_js.Array(matrix).toString(); this.attr({ order: Math.sqrt(matrix.split(' ').length), kernelMatrix: matrix }); }, // DiffuseLighting effect diffuseLighting: getAttrSetter(['surfaceScale', 'lightingColor', 'diffuseConstant', 'kernelUnitLength']), // DisplacementMap effect displacementMap: getAttrSetter(['in', 'in2', 'scale', 'xChannelSelector', 'yChannelSelector']), // DropShadow effect dropShadow: getAttrSetter(['in', 'dx', 'dy', 'stdDeviation']), // Flood effect flood: getAttrSetter(['flood-color', 'flood-opacity']), // Gaussian Blur effect gaussianBlur: function (x = 0, y = x) { this.attr('stdDeviation', x + ' ' + y); }, // Image effect image: function (src) { this.attr('href', src, svg_js.namespaces.xlink); }, // Morphology effect morphology: getAttrSetter(['operator', 'radius']), // Offset effect offset: getAttrSetter(['dx', 'dy']), // SpecularLighting effect specularLighting: getAttrSetter(['surfaceScale', 'lightingColor', 'diffuseConstant', 'specularExponent', 'kernelUnitLength']), // Tile effect tile: getAttrSetter([]), // Turbulence effect turbulence: getAttrSetter(['baseFrequency', 'numOctaves', 'seed', 'stitchTiles', 'type']) }; const filterNames = ['blend', 'colorMatrix', 'componentTransfer', 'composite', 'convolveMatrix', 'diffuseLighting', 'displacementMap', 'dropShadow', 'flood', 'gaussianBlur', 'image', 'merge', 'morphology', 'offset', 'specularLighting', 'tile', 'turbulence']; // For every filter create a class filterNames.forEach(effect => { const name = svg_js.utils.capitalize(effect); const fn = updateFunctions[effect]; Filter[name + 'Effect'] = class extends Effect { constructor(node) { super(svg_js.nodeOrNew('fe' + name, node), node); } // This function takes all parameters from the factory call // and updates the attributes according to the updateFunctions update(args) { fn.apply(this, args); return this; } }; // Add factory function to filter // Allow to pass a function or object // The attr object is catched from "wrapWithAttrCheck" Filter.prototype[effect] = svg_js.wrapWithAttrCheck(function (fn, ...args) { const effect = new Filter[name + 'Effect'](); if (fn == null) return this.put(effect); // For Effects which can take children, a function is allowed if (typeof fn === 'function') { fn.call(effect, effect); } else { // In case it is not a function, add it to arguments args.unshift(fn); } return this.put(effect).update(args); }); }); // Correct factories which are not that simple svg_js.extend(Filter, { merge(arrayOrFn) { const node = this.put(new Filter.MergeEffect()); // If a function was passed, execute it // That makes stuff like this possible: // filter.merge((mergeEffect) => mergeEffect.mergeNode(in)) if (typeof arrayOrFn === 'function') { arrayOrFn.call(node, node); return node; } // Check if first child is an array, otherwise use arguments as array const children = arrayOrFn instanceof Array ? arrayOrFn : [...arguments]; children.forEach(child => { if (child instanceof Filter.MergeNode) { node.put(child); } else { node.mergeNode(child); } }); return node; }, componentTransfer(components = {}) { const node = this.put(new Filter.ComponentTransferEffect()); if (typeof components === 'function') { components.call(node, node); return node; } // If no component is set, we use the given object for all components if (!components.r && !components.g && !components.b && !components.a) { const temp = components; components = { r: temp, g: temp, b: temp, a: temp }; } for (const c in components) { // components[c] has to hold an attributes object node.add(new Filter['Func' + c.toUpperCase()](components[c])); } return node; } }); const filterChildNodes = ['distantLight', 'pointLight', 'spotLight', 'mergeNode', 'FuncR', 'FuncG', 'FuncB', 'FuncA']; filterChildNodes.forEach(child => { const name = svg_js.utils.capitalize(child); Filter[name] = class extends Effect { constructor(node) { super(svg_js.nodeOrNew('fe' + name, node), node); } }; }); const componentFuncs = ['funcR', 'funcG', 'funcB', 'funcA']; // Add an update function for componentTransfer-children componentFuncs.forEach(function (c) { const _class = Filter[svg_js.utils.capitalize(c)]; const fn = svg_js.wrapWithAttrCheck(function () { return this.put(new _class()); }); Filter.ComponentTransferEffect.prototype[c] = fn; }); const lights = ['distantLight', 'pointLight', 'spotLight']; // Add light sources factories to lightining effects lights.forEach(light => { const _class = Filter[svg_js.utils.capitalize(light)]; const fn = svg_js.wrapWithAttrCheck(function () { return this.put(new _class()); }); Filter.DiffuseLightingEffect.prototype[light] = fn; Filter.SpecularLightingEffect.prototype[light] = fn; }); svg_js.extend(Filter.MergeEffect, { mergeNode(_in) { return this.put(new Filter.MergeNode()).attr('in', _in); } }); // add .filter function svg_js.extend(svg_js.Defs, { // Define filter filter: function (block) { const filter = this.put(new Filter()); /* invoke passed block */ if (typeof block === 'function') { block.call(filter, filter); } return filter; } }); svg_js.extend(svg_js.Container, { // Define filter on defs filter: function (block) { return this.defs().filter(block); } }); svg_js.extend(svg_js.Element, { // Create filter element in defs and store reference filterWith: function (block) { const filter = block instanceof Filter ? block : this.defs().filter(block); return this.attr('filter', filter); }, // Remove filter unfilter: function (remove) { /* remove filter attribute */ return this.attr('filter', null); }, filterer() { return this.reference('filter'); } }); // chaining const chainingEffects = { // Blend effect blend: function (in2, mode) { return this.parent() && this.parent().blend(this, in2, mode); // pass this as the first input }, // ColorMatrix effect colorMatrix: function (type, values) { return this.parent() && this.parent().colorMatrix(type, values).in(this); }, // ComponentTransfer effect componentTransfer: function (components) { return this.parent() && this.parent().componentTransfer(components).in(this); }, // Composite effect composite: function (in2, operator) { return this.parent() && this.parent().composite(this, in2, operator); // pass this as the first input }, // ConvolveMatrix effect convolveMatrix: function (matrix) { return this.parent() && this.parent().convolveMatrix(matrix).in(this); }, // DiffuseLighting effect diffuseLighting: function (surfaceScale, lightingColor, diffuseConstant, kernelUnitLength) { return this.parent() && this.parent().diffuseLighting(surfaceScale, diffuseConstant, kernelUnitLength).in(this); }, // DisplacementMap effect displacementMap: function (in2, scale, xChannelSelector, yChannelSelector) { return this.parent() && this.parent().displacementMap(this, in2, scale, xChannelSelector, yChannelSelector); // pass this as the first input }, // DisplacementMap effect dropShadow: function (x, y, stdDeviation) { return this.parent() && this.parent().dropShadow(this, x, y, stdDeviation).in(this); // pass this as the first input }, // Flood effect flood: function (color, opacity) { return this.parent() && this.parent().flood(color, opacity); // this effect dont have inputs }, // Gaussian Blur effect gaussianBlur: function (x, y) { return this.parent() && this.parent().gaussianBlur(x, y).in(this); }, // Image effect image: function (src) { return this.parent() && this.parent().image(src); // this effect dont have inputs }, // Merge effect merge: function (arg) { arg = arg instanceof Array ? arg : [...arg]; return this.parent() && this.parent().merge(this, ...arg); // pass this as the first argument }, // Morphology effect morphology: function (operator, radius) { return this.parent() && this.parent().morphology(operator, radius).in(this); }, // Offset effect offset: function (dx, dy) { return this.parent() && this.parent().offset(dx, dy).in(this); }, // SpecularLighting effect specularLighting: function (surfaceScale, lightingColor, diffuseConstant, specularExponent, kernelUnitLength) { return this.parent() && this.parent().specularLighting(surfaceScale, diffuseConstant, specularExponent, kernelUnitLength).in(this); }, // Tile effect tile: function () { return this.parent() && this.parent().tile().in(this); }, // Turbulence effect turbulence: function (baseFrequency, numOctaves, seed, stitchTiles, type) { return this.parent() && this.parent().turbulence(baseFrequency, numOctaves, seed, stitchTiles, type).in(this); } }; svg_js.extend(Effect, chainingEffects); // Effect-specific extensions svg_js.extend(Filter.MergeEffect, { in: function (effect) { if (effect instanceof Filter.MergeNode) { this.add(effect, 0); } else { this.add(new Filter.MergeNode().in(effect), 0); } return this; } }); svg_js.extend([Filter.CompositeEffect, Filter.BlendEffect, Filter.DisplacementMapEffect], { in2: function (effect) { if (effect == null) { const in2 = this.attr('in2'); const ref = this.parent() && this.parent().find(`[result="${in2}"]`)[0]; return ref || in2; } return this.attr('in2', effect); } }); // Presets Filter.filter = { sepiatone: [0.343, 0.669, 0.119, 0, 0, 0.249, 0.626, 0.130, 0, 0, 0.172, 0.334, 0.111, 0, 0, 0.000, 0.000, 0.000, 1, 0] }; module.exports = Filter; //# sourceMappingURL=svg.filter.node.cjs.map