UNPKG

scrawl-canvas

Version:
810 lines (695 loc) 83.1 kB
// # Filter factory // Filters take in an image representation of an [entity](../mixin/entity.html), [Group](./group.html) of entitys or a [Cell](./cell.html) display and, by manipulating the image's data, return an updated image which replaces those entitys or Cell in the final output display. // // Scrawl-canvas defines its filters in __Filter objects__, detailed in this module. The functionality to make use of these objects is coded up in the [filter mixin](../mixin/filter.html), which is used by the Cell, Group and all entity factories. // // The Scrawl-canvas filter engine implements a number of common filter algorithms. These algorithms - __called filter actions__ - can be combined in a wide variety of ways, including the use of multiple pathways, to create complex filter results. // // #### Filter engine actions // `alpha-to-channels` - Copies the alpha channel value over to the selected value or, alternatively, sets that channels value to zero, or leaves the channel's value unchanged. Setting the appropriate "includeChannel" flags will copy the alpha channel value to that channel; when that flag is false, setting the appropriate "excludeChannel" flag will set that channel's value to zero. Object attributes: `action, lineIn, lineOut, opacity, includeRed, includeGreen, includeBlue, excludeRed, excludeGreen, excludeBlue`. // // `area-alpha` - Places a tile schema across the input, quarters each tile and then sets the alpha channels of the pixels in selected quarters of each tile to values set in the `areaAlphaLevels` array. Can be used to create horizontal or vertical bars, or chequerboard effects. Object attributes: `action, lineIn, lineOut, opacity, tileWidth, tileHeight, offsetX, offsetY, gutterWidth, gutterHeight, areaAlphaLevels`. // // `average-channels` - Calculates an average value from each pixel's included channels and applies that value to all channels that have not been specifically excluded; excluded channels have their values set to 0. Object attributes: `action, lineIn, lineOut, opacity, includeRed, includeGreen, includeBlue, excludeRed, excludeGreen, excludeBlue`. // // `binary` - DEPRECATED - functionality moved over to `threshold`. // // `blend` - Using two source images (from the "lineIn" and "lineMix" arguments), combine their color information using various separable and non-separable blend modes (as defined by the W3C Compositing and Blending Level 1 recommendations). The blending method is determined by the String value supplied in the `blend` argument; permitted values are: 'color-burn', 'color-dodge', 'darken', 'difference', 'exclusion', 'hard-light', 'lighten', 'lighter', 'multiply', 'overlay', 'screen', 'soft-light', 'color', 'hue', 'luminosity', and 'saturation'. Note that the source images may be of different sizes: the output (lineOut) image size will be the same as the source (NOT lineIn) image; the lineMix image can be moved relative to the lineIn image using the "offsetX" and "offsetY" arguments. Object attributes: `action, lineIn, lineOut, lineMix, opacity, blend, offsetX, offsetY`. // // `blur` - Performs a multi-loop, two-step 'horizontal-then-vertical averaging sweep' calculation across all pixels to create a blur effect. Note that this filter is expensive, thus much slower to complete compared to other filter effects. Object attributes: `action, lineIn, lineOut, opacity, radius, passes, processVertical, processHorizontal, includeRed, includeGreen, includeBlue, includeAlpha, step`. // // `channels-to-alpha` - Calculates an average value from each pixel's included channels and applies that value to the alpha channel. Object attributes: `action, lineIn, lineOut, opacity, includeRed, includeGreen, includeBlue`. // // `chroma` - A chroma-key effect. Using an array of 'range' color arrays, determine whether a pixel's values lie entirely within a range's values and, if true, sets that pixel's alpha channel value to zero. Each 'range' color array comprises six Numbers representing [minimum-red, minimum-green, minimum-blue, maximum-red, maximum-green, maximum-blue] values. Object attributes: `action, lineIn, lineOut, opacity, ranges`. // // `clamp-channels` - Clamp each color channel to a range set by lowColor and highColor values. Object attributes: `action, lineIn, lineOut, opacity, lowRed, lowGreen, lowBlue, highRed, highGreen, highBlue`. // // `colors-to-alpha` - A chroma-key effect. Determine the alpha channel value for each pixel depending on the closeness to that pixel's color channel values to a reference color supplied in the "red", "green" and "blue" arguments. The sensitivity of the effect can be manipulated using the "transparentAt" and "opaqueAt" values, both of which lie in the range 0-1. Object attributes: `action, lineIn, lineOut, opacity, red, green, blue, opaqueAt, transparentAt`; pseudo-arguments for the convenience method include `reference` - any valid CSS color string from which the channel values will be calculated. // // `compose` - Using two source images (from the "lineIn" and "lineMix" arguments), combine their color information using alpha compositing rules (as defined by Porter/Duff). The compositing method is determined by the String value supplied in the "compose" argument; permitted values are: 'destination-only', 'destination-over', 'destination-in', 'destination-out', 'destination-atop', 'source-only', 'source-over' (default), 'source-in', 'source-out', 'source-atop', 'clear', 'xor', or 'lighter'. Note that the source images may be of different sizes: the output (lineOut) image size will be the same as the source (NOT lineIn) image; the lineMix image can be moved relative to the lineIn image using the "offsetX" and "offsetY" arguments. Object attributes: `action, lineIn, lineOut, lineMix, opacity, compose, offsetX, offsetY`. // // `corrode` - Performs a special form of matrix operation on each pixel's color and alpha channels, calculating the new value using neighbouring pixel values. Note that this filter is expensive, thus much slower to complete compared to other filter effects. The matrix dimensions can be set using the "width" and "height" arguments, while setting the home pixel's position within the matrix can be set using the "offsetX" and "offsetY" arguments. The operation will set the pixel's channel value to match either the 'lowest', 'highest' or 'average' (default) values as dictated by its neighbours - this value is set in the "level" attribute. Channels can be selected by setting the "includeRed", "includeGreen", "includeBlue" (all false by default) and "includeAlpha" (default: true) flags. Object attributes: `action, lineIn, lineOut, opacity, includeRed, includeGreen, includeBlue, includeAlpha, width, height, offsetX, offsetY, operation`. // // `displace` - Shift pixels around the image, based on the values supplied in a displacement process-image. Common source images can be generated from the various Noise assets, though any image will create an effect. Object attributes: `action, lineIn, lineOut, lineMix, opacity, channelX, channelY, scaleX, scaleY, transparentEdges, offsetX, offsetY`. // // `emboss` - A 3x3 matrix transform; the matrix weights are calculated internally from the values of two arguments: "strength", and "angle" - which is a value measured in degrees, with 0 degrees pointing to the right of the origin (along the positive x axis). Post-processing options include removing unchanged pixels, or setting then to mid-gray. The convenience method includes additional arguments which will add a choice of grayscale, then channel clamping, then blurring actions before passing the results to this emboss action. Object attributes: `action, lineIn, lineOut, opacity, strength, angle, tolerance, keepOnlyChangedAreas, postProcessResults`; pseudo-arguments for the convenience method include `useNaturalGrayscale, clamp, smoothing`. // // `flood` - Set all pixels to the channel values supplied in the "red", "green", "blue" and "alpha" arguments. Object attributes: `action, lineIn, lineOut, opacity, red, green, blue, alpha`; pseudo-arguments for the convenience method include `reference` - any valid CSS color string from which the channel values will be calculated. // // `gaussianblur` - a fast linear gaussian blur algorithm taken from this GitHub repository: https://github.com/nodeca/glur/blob/master/index.js (code accessed 1 June 2021). Object attributes: `action, lineIn, lineOut, opacity, radius`. // // `glitch` - CRT television/monitor glitching effect - shifts rows horizontally by a random amount (mediated by limits). Object attributes: `action, lineIn, lineOut, opacity, useMixedChannel, seed, step, offsetMin, offsetMax, offsetRedMin, offsetRedMax, offsetGreenMin, offsetGreenMax, offsetBlueMin, offsetBlueMax, offsetAlphaMin, offsetAlphaMax, transparentEdges, level`. // // `grayscale` - For each pixel, averages the weighted color channels and applies the result across all the color channels. This gives a more realistic monochrome effect. Object attributes: `action, lineIn, lineOut, opacity`. // // `invert-channels` - For each pixel, subtracts its current channel values - when included - from 255. Object attributes: `action, lineIn, lineOut, opacity, includeRed, includeGreen, includeBlue, includeAlpha`. // // `lock-channels-to-levels` - Produces a posterize effect. Takes in four arguments - "red", "green", "blue" and "alpha" - each of which is an Array of zero or more integer Numbers (between 0 and 255). The filter works by looking at each pixel's channel value and determines which of the corresponding Array's Number values it is closest to; it then sets the channel value to that Number value. Object attributes: `action, lineIn, lineOut, opacity, red, green, blue, alpha`. // // `map-to-gradient` - maps the colors in the supplied (complex) gradient to a grayscaled input. Object attributes: `action, lineIn, lineOut, opacity, useNaturalGrayscale, gradient`. // // `matrix` - Performs a matrix operation on each pixel's channels, calculating the new value using neighbouring pixel weighted values. Also known as a convolution matrix, kernel or mask operation. Note that this filter is expensive, thus much slower to complete compared to other filter effects. The matrix dimensions can be set using the "width" and "height" arguments, while setting the home pixel's position within the matrix can be set using the "offsetX" and "offsetY" arguments. The weights to be applied need to be supplied in the "weights" argument - an Array listing the weights row-by-row starting from the top-left corner of the matrix. By default all color channels are included in the calculations while the alpha channel is excluded. The 'edgeDetect', 'emboss' and 'sharpen' convenience filter methods all use the matrix action, pre-setting the required weights. Object attributes: `action, lineIn, lineOut, opacity, includeRed, includeGreen, includeBlue, includeAlpha, width, height, offsetX, offsetY, weights`. // // `modulate-channels` - Multiplies each channel's value by the supplied argument value. A channel-argument's value of '0' will set that channel's value to zero; a value of '1' will leave the channel value unchanged. If the "saturation" flag is set to 'true' the calculation changes to start at the color range mid point. The 'brightness' and 'saturation' filters are special forms of the 'channels' filter which use a single "levels" argument to set all three color channel arguments to the same value. Object attributes: `action, lineIn, lineOut, opacity, red, green, blue, alpha, saturation`; pseudo-argument: `level` // // `offset` - Offset the input image in the output image. Object attributes: `action, lineIn, lineOut, opacity, offsetRedX, offsetRedY, offsetGreenX, offsetGreenY, offsetBlueX, offsetBlueY, offsetAlphaX, offsetAlphaY; pseudo-argument: offsetX, offsetY`. // // `pixelate` - Pixelizes the input image by creating a grid of tiles across it and then averaging the color values of each pixel in a tile and setting its value to the average. Tile width and height, and their offset from the top left corner of the image, are set via the "tileWidth", "tileHeight", "offsetX" and "offsetY" arguments. Object attributes: `action, lineIn, lineOut, opacity, tileWidth, tileHeight, offsetX, offsetY, includeRed, includeGreen, includeBlue, includeAlpha`. // // `process-image` - Add an asset image to the filter process chain. The asset - the String name of the asset object - must be pre-loaded before it can be included in the filter. The "width" and "height" arguments are measured in integer Number pixels; the "copy" arguments can be either percentage Strings (relative to the asset's natural dimensions) or absolute Number values (in pixels). The "lineOut" argument is required - be aware that the filter action does not check for any pre-existing assets cached under this name and, if they exist, will overwrite them with this asset's data. Object attributes: `action, lineOut, asset, width, height, copyWidth, copyHeight, copyX, copyY`. // // `reduce-palette` - Reduce the number of colors in an image palette. The palette attribute can be: a Number (for the commonest colors); an Array of CSS color Strings to use as the palette; or the String name of a pre-defined palette - default: 'black-white'. All internal color comparisons to match pixels to the closest palette color happen in the LAB color space. Dithering is applied to select the closest, or second closest, color when setting each pixel's final output color; dithering can be random (default), or blue-noise. When calculating commonest colors, a minimum color distance can be set to get a better representative spread for images which have a predominant color (eg: images containing sky, clouds, vegetation or a plain background); calculating the commonest colors can take place in either the RGB space, or the LAB space. The Object attributes: `action, lineIn, lineOut, lineMix, opacity, palette, useBluenoise, minimumColorDistance, useLabForPaletteDistance`. // // `set-channel-to-level` - Sets the value of each pixel's included channel to the value supplied in the "level" argument. Object attributes: `action, lineIn, lineOut, opacity, includeRed, includeGreen, includeBlue, includeAlpha, level`. // // `step-channels` - Takes three divisor values - "red", "green", "blue". For each pixel, its color channel values are divided by the corresponding color divisor, rounded down or up to the integer value and then multiplied by the divisor. For example a divisor value of '50' applied to a channel value of '120' will give a result of '100' (if clamp is set to "down" or "round") or '150' (clamp set to "up"). The output is a form of posterization. Object attributes: `action, lineIn, lineOut, opacity, red, green, blue, clamp`. // // `swirl` - For each pixel, move the pixel radially according to its distance from a given coordinate and associated angle for that coordinate. Object attributes: `action, lineIn, lineOut, opacity, swirls (array of arrays)`; pseudo-arguments (for one swirl): startX, startY, innerRadius, outerRadius, angle, easing, staticSwirls`. Note that the start paramenters can be absolute (Number) or relative (String) values, with values relative to the host Cell's dimensions; inner and outer radius values can also be Strings relative to the host Cell's width. Supported easing values are included in demo Filters-026. // // `threshold` - Grayscales the input then, for each pixel, checks the color channel values against a "level" argument: pixels with channel values above the level value are assigned to the 'high' color; otherwise they are updated to the 'low' color. The "high" and "low" arguments are [red, green, blue] integer Number Arrays. The convenience function will accept the pseudo-attributes "highRed", "lowRed" etc in place of the "high" and "low" Arrays. Object attributes: `action, lineIn, lineOut, opacity, low, high; pseudo-arguments: lowRed, lowGreen, lowBlue, highRed, highGreen, highBlue`; this filter also accepts `lowColor` and `highColor` pseudo-attributes - CSS color Strings in place of the `lowRed, lowGreen, lowBlue, highRed, highGreen, highBlue` values. // // `tint-channels` - Has similarities to the SVG <feColorMatrix> filter element, but excludes the alpha channel from calculations. Rather than set a matrix, we set nine arguments to determine how the value of each color channel in a pixel will affect both itself and its fellow color channels. The 'sepia' convenience filter presets these values to create a sepia effect. Object attributes: `action, lineIn, lineOut, opacity, redInRed, redInGreen, redInBlue, greenInRed, greenInGreen, greenInBlue, blueInRed, blueInGreen, blueInBlue`; pseudo-arguments for the convenience method include `reference` - any valid CSS color string from which the channel values will be calculated; pseudo-arguments for the convenience method include `redColor, greenColor, blueColor` - any valid CSS color string from which the rgbIn values will be calculated. // // `user-defined-legacy` - Previous to Scrawl-canvas version 8.4.0, filters could be defined with an argument which passed a function string to a filter web worker, which the worker would then run against the source input image as-and-when required. This functionality has been removed from the new filter system. All such filters will now return the input image unchanged. Object attributes: `action, lineIn, lineOut, opacity`. // // `vary-channels-by-weights` - curves filter (for image processing tonality). The weights array must by 1024 elements long, with each element defaulting to a value of `1.0`. Object attributes: `action, lineIn, lineOut, opacity, weights, useMixedChannel`. // // ``` // // Example: the following code creates a filter that applies a thick red border around the entitys // // it is applied to; if used on a group then it will outline the outside of the group's entitys, // // ignoring overlaps between entitys: // scrawl.makeFilter({ // name: 'redBorder', // actions: [ // { // action: 'blur', // lineIn: 'source-alpha', // lineOut: 'shadow', // radius: 3, // passes: 2, // includeRed: false, // includeGreen: false, // includeBlue: false, // includeAlpha: true, // }, // { // action: 'binary', // lineIn: 'shadow', // lineOut: 'shadow', // alpha: 1, // }, // { // action: 'flood', // lineIn: 'shadow', // lineOut: 'red-flood', // red: 255, // }, // { // action: 'compose', // lineIn: 'shadow', // lineMix: 'red-flood', // lineOut: 'colorized', // compose: 'destination-in', // }, // { // action: 'compose', // lineIn: 'source', // lineMix: 'colorized', // } // ], // }); // ``` // #### Demos: // + [Canvas-007](../../demo/canvas-007.html) - Apply filters at the entity, group and cell level // + [Canvas-020](../../demo/canvas-020.html) - Testing createImageFromXXX functionality // + [Canvas-027](../../demo/canvas-027.html) - Video control and manipulation; chroma-based hit zone // + [Packets-002](../../demo/packets-002.html) - Scrawl-canvas packets; save and load a range of different entitys // + [Filters-019](../../demo/filters-019.html) - Using a Noise asset with a displace filter // #### Imports import { constructors, cell, group, entity, asset, styles } from '../core/library.js'; import { mergeOver, removeItem, λnull, Ωempty } from '../core/utilities.js'; import { makeGradient } from './gradient.js'; import baseMix from '../mixin/base.js'; // #### Filter constructor const Filter = function (items = Ωempty) { this.makeName(items.name); this.register(); this.actions = []; this.set(items); return this; }; // #### Filter prototype let P = Filter.prototype = Object.create(Object.prototype); P.type = 'Filter'; P.lib = 'filter'; P.isArtefact = false; P.isAsset = false; // #### Mixins P = baseMix(P); // #### Filter attributes // + Attributes defined in the [base mixin](../mixin/base.html): __name__. // + ___Note:__ unlike other Scrawl-canvas factory functions, the Filter factory does not set all its default attributes as part of its constructors. The reason for this is that these attributes are often specific to just one or a few filter actions or methods; not setting these defaults help save some object memory. let defaultAttributes = { // ##### How the filter factory builds filters // Filter actions are defined in action objects - which are vanilla Javascript Objects collected together in the __actions__ array. Each action object is processed sequentially by the filter engine to produce the final output for that filter. actions: null, // The __method__ attribute is a String which, in legacy filters, determines the actions which that filter will take on the image. An entity, Group or Cell can include more than one filter object in its `filters` Array. // + Filter factory invocations which include the `method` attribute in their argument object do not need to include an `actions` attribute; the factory will build the action objects for us. // + When using the `method` attribute, other attributes can be included alongside it. The filter factory will automatically transpose these attributes to the action object. // + The following Strings are valid methods: `'alphaToChannels', 'areaAlpha', 'binary', 'blend', 'blue', 'blur', 'brightness', 'channelLevels', 'channels', 'channelstep', 'channelsToAlpha', 'chroma', 'chromakey', 'clampChannels', 'compose', 'corrode', 'curveWeights', 'cyan', 'displace', 'edgeDetect', 'emboss', 'flood', 'gaussianBlur', 'gray', 'grayscale', 'green', 'image', 'invert', 'magenta', 'mapToGradient', 'matrix', 'matrix5', 'notblue', 'notgreen', 'notred', 'offset', 'offsetChannels', 'pixelate', 'randomNoise', 'red', 'reducePalette', 'saturation', 'sepia', 'sharpen', 'swirl', 'threshold', 'tint', 'userDefined', 'yellow'`. method: '', // ##### How filters process data // The Scrawl-canvas filter engine can use ___multiple pathways___ to process a filter's Array of action objects. In essence, a filter action can store the results of its work in a named cache, which can then be used by other filter actions further down the line. // + When Scrawl-canvas applies filters to an entity, Group or Cell it will go through the entity's `filters` Array looking for any `process-image` actions; when it finds one it will add a snapshot of the associated asset's current image state to the filter object (thus Cell and Noise assets are naturally dynamic). // + Then the system sends all of the filter's action objects over to the filter engine, alongside a snapshot of the entity, Group or Cell to be processed. // + If the entity, Group or Cell is acting as a stencil (its `isStencil` flag has been set to true) then a snapshot of the background behind the entity/Group/Cell is sent instead of the entitys themselves. // + When the filter engine recieves the data packet, it stores the snapshot in its `source` cache, and replicates it into the `work` object. // + All filter actions require a __lineIn__ string which references a cached image or snapshot. If this is not supplied, then the action will process the data stored in the `work` object. // + Additional sources for the lineIn (or lineMix) data are `source`, which copies the original source image data and delivers it to the action; and `source-alpha` which copies the original source's alpha channel data to each of the color channels and delivers it to the action. // + Similarly all filter actions require a __lineOut__ which identifies the name of a cache in which the processed data can be stored. If this is not supplied, then the action will copy the processed data back into the `work` object // + Some filter actions - specifically `blend`, `compose` and `displace` - use two inputs, combining the data referenced by the __lineIn__ and __lineMix__ attribute into __lineOut__ cache or work object. // + When processing completes, the updated data held in the work object is returned by the filter engine, which Scrawl-canvas then uses to stamp the entity/Group/Cell into the display canvas. // + If the entity, Group or Cell has requested that the filters applied to it be memoized, the final result will be stored in a key:value cache; the next time the entity/Group/Cell requests the filters, and it supplies a key matching one in the cache, the cached result is returned instantly // + memoized results are time limited and expire, by default, after 1 second. lineIn: '', lineMix: '', lineOut: '', // Every action includes an __opacity__ attribute which defines how much of the incoming image data (`lineIn`) and how much of the processed results gets included in the output data (`lineOut`) // + When set to `0`, the output consists entirely of input data // + When set to `1` (the default), the output consists entirely of processed data // + Values between these limits instruct the action to combine input and processed data proportionately in a linear fashion: a value of `0.7` leads to a result consisting of 30% input data and 70% processed data opacity: 1, // ##### Other attributes used by various filters // The attributes below are used by specific filter actions and/or methods, and the values they take may change according to the filter's requirements. Check out the following demos to see these attributes in action with each filter method: // + [Filters-001](../../demo/filters-001.html) - Parameters for: blur filter // + [Filters-002](../../demo/filters-002.html) - Parameters for: red, green, blue, cyan, magenta, yellow, notred, notgreen, notblue, grayscale, sepia, invert filters // + [Filters-003](../../demo/filters-003.html) - Parameters for: brightness, saturation filters // + [Filters-004](../../demo/filters-004.html) - Parameters for: threshold filter // + [Filters-005](../../demo/filters-005.html) - Parameters for: channelstep filter // + [Filters-006](../../demo/filters-006.html) - Parameters for: channelLevels filter // + [Filters-007](../../demo/filters-007.html) - Parameters for: channels filter // + [Filters-008](../../demo/filters-008.html) - Parameters for: tint filter // + [Filters-009](../../demo/filters-009.html) - Parameters for: pixelate filter // + [Filters-010](../../demo/filters-010.html) - Parameters for: chroma filter // + [Filters-011](../../demo/filters-011.html) - Parameters for: chromakey filter // + [Filters-012](../../demo/filters-012.html) - Parameters for: matrix, matrix5 filters // + [Filters-013](../../demo/filters-013.html) - Parameters for: flood filter // + [Filters-014](../../demo/filters-014.html) - Parameters for: areaAlpha filter // + [Filters-015](../../demo/filters-015.html) - Using assets in the filter stream; filter compositing // + [Filters-016](../../demo/filters-016.html) - Filter blend operation // + [Filters-017](../../demo/filters-017.html) - Parameters for: displace filter // + [Filters-018](../../demo/filters-018.html) - Parameters for: emboss filter // + [Filters-019](../../demo/filters-019.html) - Parameters for: edgeDetect, sharpen filters // + [Filters-020](../../demo/filters-020.html) - Parameters for: clampChannels filter // + [Filters-021](../../demo/filters-021.html) - Parameters for: corrode filter // + [Filters-022](../../demo/filters-022.html) - Parameters for: mapToGradient filter // + [Filters-023](../../demo/filters-023.html) - Parameters for: randomNoise filter // + [Filters-024](../../demo/filters-024.html) - Parameters for: curveNoise filter // + [Filters-025](../../demo/filters-025.html) - Parameters for: glitch filter // + [Filters-026](../../demo/filters-026.html) - Parameters for: swirl filter // + [Filters-027](../../demo/filters-027.html) - Parameters for: reducePalette filter alpha: 255, angle: 0, areaAlphaLevels: null, asset: '', blend: 'normal', blue: 0, blueInBlue: 0, blueColor: 'rgba(0 0 0 / 1)', blueInGreen: 0, blueInRed: 0, channelX: 'red', channelY: 'green', clamp: 0, compose: 'source-over', copyHeight: 1, copyWidth: 1, copyX: 0, copyY: 0, concurrent: false, easing: 'linear', excludeAlpha: true, excludeBlue: false, excludeGreen: false, excludeRed: false, excludeTransparentPixels: true, gradient: null, green: 0, greenInBlue: 0, greenColor: 'rgba(0 0 0 / 1)', greenInGreen: 0, greenInRed: 0, gutterHeight: 1, gutterWidth: 1, height: 1, highAlpha: 255, highBlue: 255, highColor: 'rgba(255 255 255 / 1)', highGreen: 255, highRed: 255, includeAlpha: false, includeBlue: true, includeGreen: true, includeRed: true, innerRadius: 0, keepOnlyChangedAreas: false, level: 0, lowAlpha: 0, lowBlue: 0, lowColor: 'rgba(0 0 0 / 1)', lowGreen: 0, lowRed: 0, minimumColorDistance: 1000, noWrap: false, offsetAlphaX: 0, offsetAlphaY: 0, offsetBlueX: 0, offsetBlueY: 0, offsetGreenX: 0, offsetGreenY: 0, offsetRedX: 0, offsetRedY: 0, offsetX: 0, offsetY: 0, offsetAlphaMin: 0, offsetAlphaMax: 0, offsetBlueMin: 0, offsetBlueMax: 0, offsetGreenMin: 0, offsetGreenMax: 0, offsetRedMin: 0, offsetRedMax: 0, offsetMin: 0, offsetMax: 0, opaqueAt: 1, operation: 'mean', outerRadius: '30%', palette: 'black-white', passes: 1, points: null, postProcessResults: true, processHorizontal: true, processVertical: true, radius: 1, ranges: null, red: 0, redInBlue: 0, redColor: 'rgba(0 0 0 / 1)', redInGreen: 0, redInRed: 0, reference: 'black', scaleX: 1, scaleY: 1, seed: 'some-random-string-or-other', smoothing: 0, startX: '50%', startY: '50%', step: 1, strength: 1, staticSwirls: null, tileHeight: 1, tileWidth: 1, tileRadius: 1, tolerance: 0, transparentAt: 0, transparentEdges: false, useBluenoise: false, useLabForPaletteDistance: false, useMixedChannel: true, useNaturalGrayscale: false, weights: null, width: 1, }; P.defs = mergeOver(P.defs, defaultAttributes); // #### Packet management // No additional packet functionality required // #### Clone management // No additional clone functionality required // #### Kill management // Overwrites ./mixin/base.js P.kill = function () { let myname = this.name; // Remove filter from all entity filters attribute Object.entries(entity).forEach(([name, ent]) => { let f = ent.filters; if (f && f.indexOf(myname) >= 0) removeItem(f, myname); }); // Remove filter from all group filters attribute Object.entries(group).forEach(([name, grp]) => { let f = grp.filters; if (f && f.indexOf(myname) >= 0) removeItem(f, myname); }); // Remove filter from all cell filters attribute Object.entries(cell).forEach(([name, c]) => { let f = c.filters; if (f && f.indexOf(myname) >= 0) removeItem(f, myname); }); // Remove filter from the Scrawl-canvas library this.deregister(); return this; }; // #### Get, Set, deltaSet let S = P.setters, D = P.deltaSetters; P.set = function (items = Ωempty) { const keys = Object.keys(items), keysLen = keys.length; if (keysLen) { const setters = this.setters, defs = this.defs; let predefined, i, key, value; for (i = 0; i < keysLen; i++) { key = keys[i]; value = items[key]; if (key && key !== 'name' && value != null) { predefined = setters[key]; if (predefined) predefined.call(this, value); else if (typeof defs[key] !== 'undefined') this[key] = value; } } } if (this.method && setActionsArray[this.method]) setActionsArray[this.method](this); this.dirtyFilterIdentifier = true; return this; }; P.setDelta = function (items = Ωempty) { const keys = Object.keys(items), keysLen = keys.length; if (keysLen) { const setters = this.deltaSetters, defs = this.defs; let predefined, i, iz, key, value; for (i = 0; i < keysLen; i++) { key = keys[i]; value = items[key]; if (key && key !== 'name' && value != null) { predefined = setters[key]; if (predefined) predefined.call(this, value); else if (typeof defs[key] !== 'undefined') this[key] = addStrings(this[key], value); } } } if (this.method && setActionsArray[this.method]) setActionsArray[this.method](this); this.dirtyFilterIdentifier = true; return this; }; S.actions = function (item) { if (item != null) this.actions = item; }; // #### Compatibility with Scrawl-canvas legacy filters functionality // The Scrawl-canvas filters code was rewritten from scratch for version 8.4.0. The new functionality introduced the concept of "line processing" - `lineIn`, `lineMix`, `lineOut` (analagous to SVG `in`, `in2` and `result` attributes) - alongside the addition of more sophisticated image processing tools such as blend modes, compositing, more adaptable matrices, image loading, displacement mapping, etc. // // The legacy system - defining filters using `method` String attributes - has been adapted to use the new system behind the scenes. As a result all legacy filters will continue to work as expected - with one exception: user-defined filters, which allowed the user to code a function string to pass on to the filter engine, will no longer function in Scrawl-canvas v8.4.0+. // // Note that there are no plans to deprecate the legacy method of defining/creating Filters. The following code will continue to work: // ``` // // __Brightness__ filter // scrawl.makeFilter({ // name: 'my-bright-filter', // method: 'brightness', // level: 0.5, // // // __Threshhold__ filter // }).clone({ // name: 'my-duotone-filter', // method: 'threshold', // level: 127, // lowRed: 100, // lowGreen: 0, // lowBlue: 0, // highRed: 220, // highGreen: 60, // highBlue: 60, // }); // ``` // `setActionsArray` - an object containing a large number of functions which will convert legacy factory function invocations (using `method` strings) into modern Filter objects (using `actions` arrays): const setActionsArray = { // __alphaToChannels__ (new in v8.4.0) - copies the alpha channel value over to the selected value or, alternatively, sets that channels value to zero, or leaves the channel's value unchanged. Setting the appropriate `includeChannel` flags will copy the alpha channel value to that channel; when that flag is false, setting the appropriate `excludeChannel` flag will set that channel's value to zero. alphaToChannels: function (f) { f.actions = [{ action: 'alpha-to-channels', lineIn: (f.lineIn != null) ? f.lineIn : '', lineOut: (f.lineOut != null) ? f.lineOut : '', opacity: (f.opacity != null) ? f.opacity : 1, includeRed: (f.includeRed != null) ? f.includeRed : true, includeGreen: (f.includeGreen != null) ? f.includeGreen : true, includeBlue: (f.includeBlue != null) ? f.includeBlue : true, excludeRed: (f.excludeRed != null) ? f.excludeRed : true, excludeGreen: (f.excludeGreen != null) ? f.excludeGreen : true, excludeBlue: (f.excludeBlue != null) ? f.excludeBlue : true, }]; }, // __areaAlpha__ (new in v8.4.0) - places a tile schema across the input, quarters each tile and then sets the alpha channels of the pixels in selected quarters of each tile to zero. Can be used to create horizontal or vertical bars, or chequerboard effects. areaAlpha: function (f) { f.actions = [{ action: 'area-alpha', lineIn: (f.lineIn != null) ? f.lineIn : '', lineOut: (f.lineOut != null) ? f.lineOut : '', opacity: (f.opacity != null) ? f.opacity : 1, tileWidth: (f.tileWidth != null) ? f.tileWidth : 1, tileHeight: (f.tileHeight != null) ? f.tileHeight : 1, offsetX: (f.offsetX != null) ? f.offsetX : 0, offsetY: (f.offsetY != null) ? f.offsetY : 0, gutterWidth: (f.gutterWidth != null) ? f.gutterWidth : 1, gutterHeight: (f.gutterHeight != null) ? f.gutterHeight : 1, areaAlphaLevels: (f.areaAlphaLevels != null) ? f.areaAlphaLevels : [255,0,0,0], }]; }, // DEPRECATED! __binary__ - use the updated threshold filter instead, as this functionality has been added to it. binary: function (f) { let lowRed = (f.lowRed != null) ? f.lowRed : 0, lowGreen = (f.lowGreen != null) ? f.lowGreen : 0, lowBlue = (f.lowBlue != null) ? f.lowBlue : 0, lowAlpha = (f.lowAlpha != null) ? f.lowAlpha : 255, highRed = (f.highRed != null) ? f.highRed : 255, highGreen = (f.highGreen != null) ? f.highGreen : 255, highBlue = (f.highBlue != null) ? f.highBlue : 255, highAlpha = (f.highAlpha != null) ? f.highAlpha : 255; let low = (f.low != null) ? f.low : [lowRed, lowGreen, lowBlue, lowAlpha], high = (f.high != null) ? f.high : [highRed, highGreen, highBlue, highAlpha]; f.actions = [{ action: 'threshold', lineIn: (f.lineIn != null) ? f.lineIn : '', lineOut: (f.lineOut != null) ? f.lineOut : '', opacity: (f.opacity != null) ? f.opacity : 1, level: (f.level != null) ? f.level : 128, red: (f.red != null) ? f.red : 128, green: (f.green != null) ? f.green : 128, blue: (f.blue != null) ? f.blue : 128, alpha: (f.alpha != null) ? f.alpha : 128, low, high, includeRed: (f.includeRed != null) ? f.includeRed : true, includeGreen: (f.includeGreen != null) ? f.includeGreen : true, includeBlue: (f.includeBlue != null) ? f.includeBlue : true, includeAlpha: (f.includeAlpha != null) ? f.includeAlpha : false, useMixedChannel: (f.useMixedChannel != null) ? f.useMixedChannel : false, }]; }, // __blend__ (new in v8.4.0) - perform a blend operation on two images; available blend options include: `'color-burn', 'color-dodge', 'darken', 'difference', 'exclusion', 'hard-light', 'lighten', 'lighter', 'multiply', 'overlay', 'screen', 'soft-light', 'color', 'hue', 'luminosity', and 'saturation'` - see [W3C Compositing and Blending recommendations](https://www.w3.org/TR/compositing-1/#blending) blend: function (f) { f.actions = [{ action: 'blend', lineIn: (f.lineIn != null) ? f.lineIn : '', lineOut: (f.lineOut != null) ? f.lineOut : '', lineMix: (f.lineMix != null) ? f.lineMix : '', blend: (f.blend != null) ? f.blend : 'normal', offsetX: (f.offsetX != null) ? f.offsetX : 0, offsetY: (f.offsetY != null) ? f.offsetY : 0, opacity: (f.opacity != null) ? f.opacity : 1, }]; }, // __blue__ - removes red and green channel color from the image blue: function (f) { f.actions = [{ action: 'average-channels', lineIn: (f.lineIn != null) ? f.lineIn : '', lineOut: (f.lineOut != null) ? f.lineOut : '', opacity: (f.opacity != null) ? f.opacity : 1, excludeRed: true, excludeGreen: true, }]; }, // __blur__ - blurs the image // A bespoke blur function. Creates visual artefacts with various settings that might be useful. Strongly advise to memoize the results from this filter as it is resource-intensive. // + Use the gaussian blur filter for a smoother result. blur: function (f) { f.actions = [{ action: 'blur', lineIn: (f.lineIn != null) ? f.lineIn : '', lineOut: (f.lineOut != null) ? f.lineOut : '', opacity: (f.opacity != null) ? f.opacity : 1, includeRed: (f.includeRed != null) ? f.includeRed : true, includeGreen: (f.includeGreen != null) ? f.includeGreen : true, includeBlue: (f.includeBlue != null) ? f.includeBlue : true, includeAlpha: (f.includeAlpha != null) ? f.includeAlpha : false, excludeTransparentPixels: (f.excludeTransparentPixels != null) ? f.excludeTransparentPixels : false, processHorizontal: (f.processHorizontal != null) ? f.processHorizontal : true, processVertical: (f.processVertical != null) ? f.processVertical : true, radius: (f.radius != null) ? f.radius : 1, passes: (f.passes != null) ? f.passes : 1, step: (f.step != null) ? f.step : 1, }]; }, // __brightness__ - adjusts the brightness of the image brightness: function (f) { let level = (f.level != null) ? f.level : 1; f.actions = [{ action: 'modulate-channels', lineIn: (f.lineIn != null) ? f.lineIn : '', lineOut: (f.lineOut != null) ? f.lineOut : '', opacity: (f.opacity != null) ? f.opacity : 1, red: level, green: level, blue: level, }]; }, // __channelLevels__ (new in v8.4.0) - produces a posterize effect. Takes in four arguments - `red`, `green`, `blue` and `alpha` - each of which is an Array of zero or more integer Numbers (between 0 and 255). The filter works by looking at each pixel's channel value and determines which of the corresponding Array's Number values it is closest to; it then sets the channel value to that Number value // + TODO - add in a `reference` attribute, which will be a valid CSS color string. Use these colors to generate the red, green and blue Arrays. channelLevels: function (f) { f.actions = [{ action: 'lock-channels-to-levels', lineIn: (f.lineIn != null) ? f.lineIn : '', lineOut: (f.lineOut != null) ? f.lineOut : '', opacity: (f.opacity != null) ? f.opacity : 1, red: (f.red != null) ? f.red : [0], green: (f.green != null) ? f.green : [0], blue: (f.blue != null) ? f.blue : [0], alpha: (f.alpha != null) ? f.alpha : [255], }]; }, // __channels__ - adjust the value of each channel by a specified multiplier channels: function (f) { f.actions = [{ action: 'modulate-channels', lineIn: (f.lineIn != null) ? f.lineIn : '', lineOut: (f.lineOut != null) ? f.lineOut : '', opacity: (f.opacity != null) ? f.opacity : 1, red: (f.red != null) ? f.red : 1, green: (f.green != null) ? f.green : 1, blue: (f.blue != null) ? f.blue : 1, alpha: (f.alpha != null) ? f.alpha : 1, }]; }, // __channelstep__ - restrict the number of color values that each channel can set by imposing regular bands on each channel channelstep: function (f) { let clamp = (f.clamp != null) ? f.clamp : 'down' if (!['down', 'round', 'up'].includes(clamp)) clamp = 'down'; f.actions = [{ action: 'step-channels', lineIn: (f.lineIn != null) ? f.lineIn : '', lineOut: (f.lineOut != null) ? f.lineOut : '', opacity: (f.opacity != null) ? f.opacity : 1, clamp, red: (f.red != null) ? f.red : 1, green: (f.green != null) ? f.green : 1, blue: (f.blue != null) ? f.blue : 1, }]; }, // __channelsToAlpha__ (new in v8.4.0) - calculates an average value from each pixel's included channels and applies that value to the alpha channel. channelsToAlpha: function (f) { f.actions = [{ action: 'channels-to-alpha', lineIn: (f.lineIn != null) ? f.lineIn : '', lineOut: (f.lineOut != null) ? f.lineOut : '', opacity: (f.opacity != null) ? f.opacity : 1, includeRed: (f.includeRed != null) ? f.includeRed : true, includeGreen: (f.includeGreen != null) ? f.includeGreen : true, includeBlue: (f.includeBlue != null) ? f.includeBlue : true, }]; }, // __chroma__ - using an array of `range` arrays, determine whether a pixel's values lie entirely within a range's values and, if true, sets that pixel's alpha channel value to zero. Each range array comprises six Numbers representing `[minimum-red, minimum-green, minimum-blue, maximum-red, maximum-green, maximum-blue]` values. // + Since v8.7.0 we can also define range Arrays as `[CSS-color-string, CSS-color-string]` chroma: function (f) { const processedRanges = [], res = []; if ((f.ranges != null)) { f.ranges.forEach(range => { if (range.length === 6) processedRanges.push(range); else if (range.length === 2) { if (range[0].substring && range[1].substring) { res.length = 0; range.forEach(col => { let [r, g, b] = window.scrawlEnvironmentColorChecker.extractRGBfromColor(col); res.push(r, g, b); }); } processedRanges.push(res); } }) } f.actions = [{ action: 'chroma', lineIn: (f.lineIn != null) ? f.lineIn : '', lineOut: (f.lineOut != null) ? f.lineOut : '', opacity: (f.opacity != null) ? f.opacity : 1, ranges: processedRanges, }]; }, // __chromakey__ (new in v8.4.0) - determine the alpha channel value for each pixel depending on the closeness to that pixel's color channel values to a reference color supplied in the `red`, `green` and `blue` arguments. The sensitivity of the effect can be manipulated using the `transparentAt` and `opaqueAt` values, both of which lie in the range 0-1. // + Since v8.7.0, this filter also accepts a `reference` color string in place of the `red, green, blue` values chromakey: function (f) { let red = (f.red != null) ? f.red : 0, green = (f.green != null) ? f.green : 255, blue = (f.blue != null) ? f.blue : 0; if (f.reference != null) { [red, green, blue] = window.scrawlEnvironmentColorChecker.extractRGBfromColor(f.reference); f.red = red; f.green = green; f.blue = blue; delete f.reference; } f.actions = [{ action: 'colors-to-alpha', lineIn: (f.lineIn != null) ? f.lineIn : '', lineOut: (f.lineOut != null) ? f.lineOut : '', opacity: (f.opacity != null) ? f.opacity : 1, red, green, blue, transparentAt: (f.transparentAt != null) ? f.transparentAt : 0, opaqueAt: (f.opaqueAt != null) ? f.opaqueAt : 1, }]; }, // __clampChannels__ (new in v8.4.0) - clamp each color channel to a range set by `lowColor` and `highColor` values // + Since v8.7.0, this filter also accepts `lowColor` and `highColor` CSS color Strings in place of the `lowRed, lowGreen, lowBlue, highRed, highGreen, highBlue` values clampChannels: function (f) { let lowRed = (f.lowRed != null) ? f.lowRed : 0, lowGreen = (f.lowGreen != null) ? f.lowGreen : 0, lowBlue = (f.lowBlue != null) ? f.lowBlue : 0, highRed = (f.highRed != null) ? f.highRed : 255, highGreen = (f.highGreen != null) ? f.highGreen : 255, highBlue = (f.highBlue != null) ? f.highBlue : 255; if (f.lowColor != null) { [lowRed, lowGreen, lowBlue] = window.scrawlEnvironmentColorChecker.extractRGBfromColor(f.lowColor); f.lowRed = lowRed; f.lowGreen = lowGreen; f.lowBlue = lowBlue; delete f.lowColor; } if (f.highColor != null) { [highRed, highGreen, highBlue] = window.scrawlEnvironmentColorChecker.extractRGBfromColor(f.highColor); f.highRed = highRed; f.highGreen = highGreen; f.highBlue = highBlue; delete f.highColor; } f.actions = [{ action: 'clamp-channels', lineIn: (f.lineIn != null) ? f.lineIn : '', lineOut: (f.lineOut != null) ? f.lineOut : '', opacity: (f.opacity != null) ? f.opacity : 1, lowRed, lowGreen, lowBlue, highRed, highGreen, highBlue, }]; }, // __compose__ (new in v8.4.0) - perform a composite operation on two images; available `compose` options include: `'destination-only', 'destination-over', 'destination-in', 'destination-out', 'destination-atop', 'source-only', 'source-over' (default), 'source-in', 'source-out', 'source-atop', 'clear', and 'xor'` - see [W3C Compositing and Blending recommendations](https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators) compose: function (f) { f.actions = [{ action: 'compose', lineIn: (f.lineIn != null) ? f.lineIn : '', lineOut: (f.lineOut !=